#pragma once

// [grid-id.h]: grid data identifier
//
// -------------------------------------------------------------------------------------------- //

#include "nse-sys.h"
#include "mpi-com.h"

#include <string.h>
#ifdef USE_CXX_11
#include <initializer_list>
#endif

//#define _USE_DEPRECATED_WST_FORMAT

namespace nse
{
	enum maskType {
		solidCell = 0, fluidCell = 1, externalCell = 2
	};

	template< typename T >
	class GridId {
	public:

		GridId();
		GridId(const GridId& id);
		~GridId();

		void init();	// initialize id 

						// set:
		void set_grid_type(const int grid_type);
		void set_dim_num(const int ndim);
		void set_domain_dim(const int dim, const T x, const T length);
		void set_grid_dim(const int dim, const int nx, const int gcx);

		void reset_data_type_size();	// to: [sizeof(T)]

										// set specs:
#ifdef USE_CXX_11
		void set_domain_specs(const std::initializer_list<T> specs);
		void set_grid_specs(const std::initializer_list<int> specs);
#else
		void set_domain_specs(int nspecs, const T* specs);
		void set_grid_specs(int nspecs, const int* specs);
#endif

		// header:
		int key() const;
		int grid_type() const;
		int dim_num() const;
		int data_type_size() const;

		// get grid, domain by dim:
		void domain_dim(const int dim, T* x, T* length) const;
		void grid_dim(const int dim, int* nx, int* gcx) const;

		// get specs:
		T domain_spec(int idx) const;
		int grid_spec(int idx) const;

		// check id based on header values and input num dims
		bool check(const int ndim) const;

		// broadcast GridId object on communicator
		void mpi_broadcast(const int host, const MPI_Comm comm);
		// ------------------------------------------------------------------------------------ //


		// static constants
		// ------------------------------------------------------------------------------------ //
		static const int max_dim = 3;	// using <3> to comply with WST-format

		static const int hsize = 4;
		static const int dsize = 24;
		static const int gsize = 24;

		static const int id_byte_size =
			hsize * sizeof(int) + dsize * sizeof(T) + gsize * sizeof(int);

		// static constants (read 3D data only), switch: comply with WST-format
		// ------------------------------------------------------------------------------------ //
#ifndef _USE_DEPRECATED_WST_FORMAT
		static const int hsize_r3d = hsize;
		static const int dsize_r3d = dsize;
		static const int gsize_r3d = gsize;
#else
		static const int hsize_r3d = 4;
		static const int dsize_r3d = 7;
		static const int gsize_r3d = 6;
#endif

		static const int id_byte_size_r3d =
			hsize_r3d * sizeof(int) + dsize_r3d * sizeof(T) + gsize_r3d * sizeof(int);


	public:
		// data triple [header,domain,grid]
		// ------------------------------------------------------------------------------------ //

		int header[hsize];
		T domain[dsize];
		int grid[gsize];
	};
}

// Implementation
// -------------------------------------------------------------------------------------------- //
template< typename T >
nse::GridId<T>::GridId()
{
	memset(header, 0, GridId<T>::hsize * sizeof(int));
	memset(domain, 0, GridId<T>::dsize * sizeof(T));
	memset(grid, 0, GridId<T>::gsize * sizeof(int));
}

template< typename T >
nse::GridId<T>::GridId(const GridId& id)
{
	memcpy(header, id.header, GridId<T>::hsize * sizeof(int));
	memcpy(domain, id.domain, GridId<T>::dsize * sizeof(T));
	memcpy(grid, id.grid, GridId<T>::gsize * sizeof(int));
}

template< typename T >
nse::GridId<T>::~GridId() { }

template< typename T >
void nse::GridId<T>::init()
{
	memset(header, 0, GridId<T>::hsize * sizeof(int));
	memset(domain, 0, GridId<T>::dsize * sizeof(T));
	memset(grid, 0, GridId<T>::gsize * sizeof(int));

	header[0] = 'n' + 's' + 'e';	// file identifier //
	header[3] = sizeof(T);			// data type size //
}

template< typename T >
void nse::GridId<T>::set_grid_type(const int grid_type)
{
	if (grid_type >= 0)
		header[1] = grid_type;
}

template< typename T >
void nse::GridId<T>::set_dim_num(const int ndim)
{
	if ((ndim <= 0) || (ndim > max_dim)) return;

	header[2] = ndim;
}

template< typename T >
void nse::GridId<T>::set_domain_dim(const int dim, const T x, const T length)
{
	if ((dim <= 0) || (dim > dim_num())) return;

	domain[dim - 1] = x;
	domain[max_dim + dim - 1] = length;
}

template< typename T >
void nse::GridId<T>::set_grid_dim(const int dim, const int nx, const int gcx)
{
	if ((dim <= 0) || (dim > dim_num())) return;

	grid[dim - 1] = nx;
	grid[max_dim + dim - 1] = gcx;
}

template< typename T >
void nse::GridId<T>::reset_data_type_size()
{
	header[3] = sizeof(T);
}

#ifdef USE_CXX_11
template< typename T >
void nse::GridId<T>::set_domain_specs(const std::initializer_list<T> specs)
{
	int ptr = 2 * max_dim;
	for (auto sp : specs) {
		domain[ptr] = sp;
		ptr++;

		if (ptr >= GridId<T>::dsize) break;
	}
}

template< typename T >
void nse::GridId<T>::set_grid_specs(const std::initializer_list<int> specs)
{
	int ptr = 2 * max_dim;
	for (auto sp : specs) {
		grid[ptr] = sp;
		ptr++;

		if (ptr >= GridId<T>::gsize) break;
	}
}
#else
template< typename T >
void nse::GridId<T>::set_domain_specs(int nspecs, const T* specs)
{
	const int ptr = 2 * max_dim;
	for (int k = 0; k < nspecs; k++) {
		if (ptr + k >= GridId<T>::dsize) break;

		domain[ptr + k] = specs[k];
	}
}

template< typename T >
void nse::GridId<T>::set_grid_specs(int nspecs, const int* specs)
{
	const int ptr = 2 * max_dim;
	for (int k = 0; k < nspecs; k++) {
		if (ptr + k >= GridId<T>::gsize) break;

		grid[ptr + k] = specs[k];
	}
}
#endif

template< typename T >
inline int nse::GridId<T>::key() const { return header[0]; }

template< typename T >
inline int nse::GridId<T>::grid_type() const { return header[1]; }

template< typename T >
inline int nse::GridId<T>::dim_num() const { return header[2]; }

template< typename T >
inline int nse::GridId<T>::data_type_size() const { return header[3]; }

template< typename T >
void nse::GridId<T>::domain_dim(const int dim, T* x, T* length) const
{
	if ((dim <= 0) || (dim > dim_num())) return;

	(*x) = domain[dim - 1];
	(*length) = domain[max_dim + dim - 1];
}

template< typename T >
void nse::GridId<T>::grid_dim(const int dim, int* nx, int* gcx) const
{
	if ((dim <= 0) || (dim > dim_num())) return;

	(*nx) = grid[dim - 1];
	(*gcx) = grid[max_dim + dim - 1];
}

template< typename T >
T nse::GridId<T>::domain_spec(int idx) const
{
	return domain[2 * max_dim + idx];
}

template< typename T >
int nse::GridId<T>::grid_spec(int idx) const
{
	return grid[2 * max_dim + idx];
}

template< typename T >
bool nse::GridId<T>::check(const int ndim) const
{
	return (
		(key() == 'n' + 's' + 'e') &&			// file identifier
		(dim_num() == ndim) &&					// dims - strict
		((data_type_size() == sizeof(float)) ||
		(data_type_size() == sizeof(double)))	// appropriate data type
		);
}

template< typename T >
void nse::GridId<T>::mpi_broadcast(const int host, const MPI_Comm comm)
{
	nse::mpi_broadcast(header, GridId<T>::hsize, host, comm);
	nse::mpi_broadcast(domain, GridId<T>::dsize, host, comm);
	nse::mpi_broadcast(grid, GridId<T>::gsize, host, comm);
}
// -------------------------------------------------------------------------------------------- //