#ifndef GELIB_IMPL_INDEXEDDATA_H_
#define GELIB_IMPL_INDEXEDDATA_H_

#include <vector>
#include <array>
#include <memory>
#include "../1_dimensional/d_vector.h"

namespace gelib_impl
{

	template <const size_t N>
	class IndexedData : public std::vector<double>
	{
	protected:
		std::array<size_t, N> m_dimension_sizes{};
		std::array<size_t, N + 1> m_data_gaps{};

	public:
		IndexedData()
		{}

		IndexedData(std::initializer_list<double> data, std::initializer_list<size_t> dimension_sizes)
			:
			std::vector<double>(data)
		{
			construct(dimension_sizes);
		}

		IndexedData(std::initializer_list<size_t> dimension_sizes)
		{
			construct(dimension_sizes);
		}

		void construct(std::initializer_list<size_t> dimension_sizes)
		{
			std::copy(dimension_sizes.begin(), dimension_sizes.end(), m_dimension_sizes.begin());
			calculate_data_gaps();
		}

		void calculate_data_gaps()
		{
			// TODO Note this function only works in 3-dimensional case
			assert(m_dimension_sizes.size() == 3);

			// rows
			m_data_gaps[0] = m_dimension_sizes[1];
			// cols
			m_data_gaps[1] = 1;
			// layers
			m_data_gaps[2] = m_dimension_sizes[1] * m_dimension_sizes[0];
			// buffer
			m_data_gaps[3] = 0;

			for (size_t i = 0; i < m_dimension_sizes.size(); ++i)
					if (m_dimension_sizes[i] == 1)
						m_data_gaps[i] = 0;

		}

		static bool matching_or_unit_dimensions(IndexedData<N> const& D1, IndexedData<N> const& D2)
		{
			for (size_t i = 0; i < D1.m_dimension_sizes.size(); ++i)
				if ((D1.m_dimension_sizes[i] != D2.m_dimension_sizes[i]) && (D2.m_dimension_sizes[i] != 1))
					return false;

			return true;
		}

		size_t get_data_gap(size_t i) const
		{
			return m_data_gaps[i];
		}

		size_t get_dimension_size(size_t i) const
		{
			return  m_dimension_sizes[i];
		}

		///////////////////////////////////////////////////////////////////////////////
		// swap (not copy) vector in to indexed data 
		///////////////////////////////////////////////////////////////////////////////
		virtual void insert_vector(std::array<int, N>& indices, d_vector& in) = 0;

		static void insert_vector(d_vector& in, double* data_start, size_t data_gap)
		{
			double* data_ptr = data_start;
			for (size_t i = 0; i < in.size(); ++i)
			{
				std::swap(in[i], *data_ptr);
				data_ptr += data_gap;
			}
		}

		///////////////////////////////////////////////////////////////////////////////
		// copy vector out of indexed data
		///////////////////////////////////////////////////////////////////////////////
		virtual void extract_vector(std::array<int, N>& indices, d_vector& out) const = 0;

		static void extract_vector(d_vector& out, const double* data_start, size_t data_gap)
		{
			const double* data_ptr = data_start;
			for (size_t i = 0; i < out.size(); ++i)
			{
				out[i] = *data_ptr;
				data_ptr += data_gap;
			}
		}

	};

}

#endif