/*
 * shift_vect_converter.hpp
 *
 *  Created on: Feb 8, 2018
 *      Author: i-bird
 */

#ifndef SRC_DECOMPOSITION_SHIFT_VECT_CONVERTER_HPP_
#define SRC_DECOMPOSITION_SHIFT_VECT_CONVERTER_HPP_

#include "Space/Shape/HyperCube.hpp"

/*! \brief in case of high dimensions shift vector converter
 *
 * In case of high-dimensions the number of shift vectors explode, this class
 * handle such case
 *
 */
template<unsigned int dim, typename T, typename Memory, template<typename> class layout_base>
class shift_vect_converter
{
	//! Indicate which indexes are non_periodic
	size_t red_shift_v[dim];

	// indexes
	size_t tmp[dim];

	// Dimension
	int dim_r = 0;

	/*! \brief Here we generare the shift vectors for the low dimension case
	 *
	 * \param domain box that describe the domain
	 *
	 */
	void generateShiftVectors_ld(const Box<dim,T> & domain, size_t (& bc)[dim],
			                     openfpm::vector<Point<dim,T>,Memory,layout_base> & shifts)
	{
		shifts.resize(openfpm::math::pow(3,dim));

		HyperCube<dim> hyp;

		for (long int i = dim ; i >= 0 ; i--)
		{
			std::vector<comb<dim>> cmbs = hyp.getCombinations_R(i);

			for (size_t j = 0 ; j < cmbs.size() ; j++)
			{
				for (size_t k = 0 ; k < dim ; k++)
				{
					switch (cmbs[j][k])
					{
					case 1:
						shifts.get(cmbs[j].lin()).template get<0>()[k] = -(domain.getHigh(k) - domain.getLow(k));
						break;
					case 0:
						shifts.get(cmbs[j].lin()).template get<0>()[k] = 0;
						break;
					case (char)-1:
						shifts.get(cmbs[j].lin()).template get<0>()[k] = (domain.getHigh(k) - domain.getLow(k));
						break;
					}
				}
			}
		}
	}

	/*! \brief Here we generare the shift vectors for the high dimension case
	 *
	 * \param domain box that describe the domain
	 *
	 */
	void generateShiftVectors_hd(const Box<dim,T> & domain, size_t (& bc)[dim],
			                     openfpm::vector<Point<dim,T>,Memory,layout_base> & shifts)
	{
		// get the indexes of the free degree of freedom
		for (size_t i = 0 ; i < dim ; i++)
		{
			if (bc[i] == PERIODIC)
			{
				red_shift_v[dim_r] = i;
				dim_r++;
			}
		}

		HyperCube<dim> hyp;

		// precalculate the nuber of shift vectors
		size_t nsv = 0;
		for (long int i = dim-1 ; i >= 0 ; i--)
		{nsv += hyp.getCombinations_R_bc(i,bc).size();}
		shifts.resize(nsv+1);

		for (long int i = dim-1 ; i >= 0 ; i--)
		{
			std::vector<comb<dim>> cmbs = hyp.getCombinations_R_bc(i,bc);

			for (size_t j = 0 ; j < cmbs.size() ; j++)
			{
				size_t lin_cmb = linId_hd(cmbs[j]);

				for (size_t k = 0 ; k < dim ; k++)
				{
					switch (cmbs[j][k])
					{
					case 1:
						shifts.get(lin_cmb).template get<0>()[k] = -(domain.getHigh(k) - domain.getLow(k));
						break;
					case 0:
						shifts.get(lin_cmb).template get<0>()[k] = 0;
						break;
					case (char)-1:
						shifts.get(lin_cmb).template get<0>()[k] = (domain.getHigh(k) - domain.getLow(k));
						break;
					}
				}
			}
		}
	}

public:

	/*! \brief Here we generare the shift vectors for the low dimension case
	 *
	 * \param domain box that describe the domain
	 *
	 */
	void generateShiftVectors(const Box<dim,T> & domain, size_t (& bc)[dim],
			                  openfpm::vector<Point<dim,T>,Memory,layout_base> & shifts)
	{
		if (dim < 10)
		{generateShiftVectors_ld(domain,bc,shifts);}
		else
		{generateShiftVectors_hd(domain,bc,shifts);}
	}

	/*! \brief Initialize
	 *
	 * \param bc boundary conditions
	 *
	 */
	void Initialize(size_t (& bc)[dim])
	{
		// get the indexes of the free degree of freedom
		for (size_t i = 0 ; i < dim ; i++)
		{
			if (bc[i] == PERIODIC)
			{
				red_shift_v[dim] = i;
				dim_r++;
			}
		}
	}

	/*! \brief linearize the combination in case of high dimension
	 *
	 * \param cmb combination
	 *
	 */
	size_t linId_hd(const comb<dim> & cmb)
	{
		size_t cul = 1;
		size_t lin = 0;
		for (long int i = 0 ; i < dim_r ; i++)
		{
			lin += cul*(cmb.c[red_shift_v[i]] + 1);
			cul *= 3;
		}

		return lin;
	}

	/*! \brief linearize the combination in case of low dimensions
	 *
	 * \param cmb combination
	 *
	 */
	inline size_t linId_ld(const comb<dim> & cmb)
	{
		return cmb.lin();
	}

	/*! \brief linearize the combination in case of high dimensions
	 *
	 * \param cmb combination
	 *
	 */
	inline size_t linId(const comb<dim> & cmb)
	{
		if (dim < 10)
		{return linId_ld(cmb);}

		return linId_hd(cmb);
	}

};


#endif /* SRC_DECOMPOSITION_SHIFT_VECT_CONVERTER_HPP_ */