#pragma once

// [bin-named-stamp.h]: binNamedStamp data structure for file I/O
//
// -------------------------------------------------------------------------------------------- //

#include "grid-id.h"
#include <stdio.h>
#include <string>

namespace nse
{
	template< typename T >
	class binNamedStamp {
	public:
		binNamedStamp();
		~binNamedStamp();


		bool get(const int idx, T* out) const;
		bool get(const std::string& name, T* out) const;

		int get_size() const { return size; }

		int get_record_size() const;

		bool update(const int idx, T* out) const;
		bool update(const std::string& name, T* out) const;

		void push(const std::string& name, const T in);

		int fwrite(FILE *ptr) const;
		int fread(FILE* ptr);

		void mpi_broadcast(const int host, const MPI_Comm comm);


		void debug_print() const;

	private:
		int mem_size, size;
		T *value;
		std::string *name;

		static const int c_alloc_step = 16;

		void allocate(const int msize);
		void resize(const int msize);
	};
}
// -------------------------------------------------------------------------------------------- //

// Implementation
// -------------------------------------------------------------------------------------------- //
template< typename T >
nse::binNamedStamp< T >::binNamedStamp() : mem_size(0), size(0) {}
template< typename T >
nse::binNamedStamp< T >::~binNamedStamp()
{
	if (mem_size > 0) delete[] value;

	mem_size = 0;
	size = 0;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
bool nse::binNamedStamp< T >::get(const int idx, T* out) const
{
	if ((idx < 0) || (idx >= size)) return false;

	(*out) = value[idx];
	return true;
}
template< typename T >
bool nse::binNamedStamp< T >::get(const std::string& _name, T* out) const
{
	for (int k = 0; k < size; k++) {
		if (name[k] == _name) {
			(*out) = value[k];
			return true;
		}
	}
	return false;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
bool nse::binNamedStamp< T >::update(const int idx, T* out) const
{
	if ((idx < 0) || (idx >= size)) return false;

	(*out) += value[idx];
	return true;
}

template< typename T >
bool nse::binNamedStamp< T >::update(const std::string& _name, T* out) const
{
	for (int k = 0; k < size; k++) {
		if (name[k] == _name) {
			(*out) += value[k];
			return true;
		}
	}

	return false;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
void nse::binNamedStamp< T >::push(const std::string& _name, const T in)
{
	if (_name.empty()) return;
	for (int k = 0; k < size; k++) {
		if (name[k] == _name) {
			value[k] = in;
			return;
		}
	}


	resize(size + 1);

	value[size] = in;
	name[size] = _name;
	size++;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
void nse::binNamedStamp< T >::allocate(const int req_size)
{
	if (req_size > mem_size)
	{
		int alloc_size = (req_size > mem_size + c_alloc_step) ?
			req_size : mem_size + c_alloc_step;

		if (mem_size > 0) {
			delete[] value;
			delete[] name;
		}
		value = new T[alloc_size];
		name = new std::string[alloc_size];
		mem_size = alloc_size;
	}
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
void nse::binNamedStamp< T >::resize(const int req_size)
{
	if (req_size > mem_size)
	{
		int alloc_size = (req_size > mem_size + c_alloc_step) ?
			req_size : mem_size + c_alloc_step;

		T *cpval = new T[alloc_size];
		std::string *cpname = new std::string[alloc_size];
		if (size > 0)
			memcpy(cpval, value, size * sizeof(T));
		for (int k = 0; k < size; k++)
			cpname[k] = name[k];
		if (mem_size > 0) {
			delete[] value;
			delete[] name;
		}

		value = cpval;
		name = cpname;
		mem_size = alloc_size;
	}
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
int nse::binNamedStamp< T >::fwrite(FILE *ptr) const
{
	int status = 0;

	status += ::fwrite(&size, sizeof(int), 1, ptr);
	if (size > 0) {
		status += ::fwrite(value, sizeof(T), size, ptr);
	}

	for (int k = 0; k < size; k++) {
		int c_length = strlen(name[k].c_str());
		status += ::fwrite(&c_length, sizeof(int), 1, ptr);
		status += ::fwrite(name[k].c_str(), sizeof(char), c_length, ptr);
	}

	return status;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
int nse::binNamedStamp< T >::fread(FILE* ptr)
{
	int status = 0;

	status += ::fread(&size, sizeof(int), 1, ptr);
	allocate(size);

	if (size > 0) {
		status += ::fread(value, sizeof(T), size, ptr);
	}

	for (int k = 0; k < size; k++)
	{
		int c_length;
		status += ::fread(&c_length, sizeof(int), 1, ptr);

		char *c_buf = new char[c_length + 1];
		status += ::fread(c_buf, sizeof(char), c_length, ptr);
		c_buf[c_length] = '\0';

		name[k] = std::string(c_buf);

		delete[] c_buf;
	}

	return status;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
int nse::binNamedStamp< T >::get_record_size() const
{
	int recsize = size + 1;

	for (int k = 0; k < size; k++) {
		int name_length = strlen(name[k].c_str());
		recsize += name_length + 1;
	}

	return recsize;
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
void nse::binNamedStamp< T >::mpi_broadcast(
	const int host, const MPI_Comm comm)
{
	int mpi_rank;
	MPI_Comm_rank(comm, &mpi_rank);

	MPI_Bcast(&size, 1, MPI_INT, host, comm);
	if (mpi_rank != host) allocate(size);

	if (size > 0) {
		MPI_Bcast(value, size, mpi_type< T >(), host, comm);
	}

	for (int k = 0; k < size; k++) {
		int c_length = 0;
		if (mpi_rank == host) c_length = strlen(name[k].c_str());
		MPI_Bcast(&c_length, 1, MPI_INT, host, comm);

		char *c_buf = new char[c_length + 1];
		strcpy(c_buf, name[k].c_str());
		MPI_Bcast(c_buf, c_length + 1, MPI_CHAR, host, comm);

		if (mpi_rank != host) {
			name[k] = std::string(c_buf);
		}
		delete[] c_buf;
	}
}
// -------------------------------------------------------------------------------------------- //

template< typename T >
void nse::binNamedStamp< T >::debug_print() const
{
	for (int k = 0; k < size; k++) {
		printf(" idx = %i, name = %s, value = %.5f\n", k, name[k].c_str(), value[k]);
	}
}
// -------------------------------------------------------------------------------------------- //