/*
  ==============================================================================

    LayerArray.hpp
    Created: 21 Sep 2014 1:55:15pm
    Author:  jens

  ==============================================================================
*/

#ifndef LAYERARRAY_HPP
#define LAYERARRAY_HPP
#include <stdint.h>
#include <Eigen/Dense>

using namespace Eigen;

template <class T>
class LayerArray;

template <class T>
class LayerArrayListener
{
public:
	LayerArrayListener() {}
	virtual~LayerArrayListener() {}
	
	virtual void onChanged(const LayerArray<T> &obj) = 0;
};

template <class T>
class LayerArray
{
	class Entry : public T
	{
	public:
		Entry(uint32_t numUnits, const VectorXd *pInit)
		: T(numUnits, pInit)
		, pPrev(nullptr)
		, pNext(nullptr)
		{
		}
		~Entry()
		{
		}
		Entry *pPrev;
		Entry *pNext;
	private:

	};

public:
	LayerArray(LayerArrayListener<T> *pListener = nullptr)
	: m_size(0)
	, m_pRoot(nullptr)
	, m_ppIndex(nullptr)
	, m_pListener(pListener)
	{
	}

	LayerArray(uint32_t numLayers, uint32_t numUnitsPerLayer)
	: m_size(0)
	, m_pRoot(nullptr)
	, m_ppIndex(nullptr)
	, m_pListener(nullptr)
	{
		uint32_t i;

		for (i=0; i < numLayers; i++)
		{
			add(nullptr, numUnitsPerLayer);
		}
	}

	virtual ~LayerArray()
	{
		m_pListener = nullptr;
		clear();
	}

	T* add(const VectorXd *pData, uint32_t size)
	{
		Entry *pNew;
		Entry *pL = m_pRoot;

		pNew = new Entry(size, pData);
		if (!m_pRoot)
		{
			m_pRoot = pNew;
		}
		else
		{
			pL = m_pRoot;
			while(pL->pNext)
			{
				pL = pL->pNext;
			}
			pL->pNext = pNew;
			pL->pNext->pPrev = pL;
		}
		rebuildIndex();
		if (m_pListener)
			m_pListener->onChanged(*this);

		return (T*)pNew;
	}

	void clear()
	{
		if (m_ppIndex)
		{
			for (int i=0; i < m_size; i++)
			{
				if (m_ppIndex[i])
					delete m_ppIndex[i];

				m_ppIndex[i] = nullptr;
			}
		}
		m_ppIndex = nullptr;
		m_pRoot = nullptr;
		m_size = 0;
		allocIndex(0);
		if (m_pListener)
			m_pListener->onChanged(*this);
	}

	T& getAt(uint32_t index)
	{
		return *m_ppIndex[index];
	}

	T& operator[](uint32_t index)
	{
		return getAt(index);
	}

	void removeAt(uint32_t index)
	{
		if (m_ppIndex[index]->pPrev)
		{
			m_ppIndex[index]->pPrev->pNext = m_ppIndex[index]->pNext;
		}
		else
		{
			m_pRoot = m_ppIndex[index]->pNext;
			m_ppIndex[index]->pNext->pPrev = nullptr;
		}
		delete m_ppIndex[index];
		m_ppIndex[index] = nullptr;
		rebuildIndex();
		if (m_pListener)
			m_pListener->onChanged(*this);
	}

	uint32_t getSize() const
	{
		return m_size;
	}

	void save(const char *pFilename)
	{
		FILE *pFile;

		pFile = fopen(pFilename,"w");

		if (!pFile)
			return;


		fprintf(pFile, "%d\n", m_size);

		uint32_t i, j;

		for (i=0; i < m_size; i++)
		{
			uint32_t numUnits = m_ppIndex[i]->getNumUnits();
			fprintf(pFile, "%d\n", numUnits);

			for (j=0; j < numUnits; j++)
			{
				fprintf(pFile, "%3.6f\n", m_ppIndex[i]->states()(j));
			}
		}

		fclose(pFile);
	}

	void load(const char *pFilename)
	{
		uint32_t size = 0;
		FILE *pFile;

		pFile = fopen(pFilename,"r");

		if (!pFile)
			return;

		fscanf(pFile, "%d\n", &size);

		uint32_t i, j;
		float v;
		double *pData;
		for (i=0; i < size; i++)
		{
			uint32_t numUnits;
			fscanf(pFile, "%d\n", &numUnits);
			pData = new double[numUnits];
			T* data = add(nullptr, numUnits);


			for (j=0; j < numUnits; j++)
			{
				fscanf(pFile, "%f", &v);
				data->states()(j) = v;
			}

			delete [] pData;
		}

		fclose(pFile);
	}

private:
	uint32_t m_size;
	Entry *m_pRoot;
	Entry **m_ppIndex;
	LayerArrayListener<T> *m_pListener;
	void allocIndex(size_t size)
	{
		if (m_ppIndex)
		{
			delete [] m_ppIndex;
			m_ppIndex = nullptr;
			allocIndex(size);
		}
		else
		{
			if (size)
			{
				m_ppIndex = new Entry*[size];
			}
		}
	}

	void rebuildIndex()
	{
		Entry *pL;
		uint32_t size;

		size = 0;
		pL = m_pRoot;
		while(pL)
		{
			size++;
			pL = pL->pNext;
		}
		allocIndex(size);

		size = 0;
		pL = m_pRoot;
		while(pL)
		{
			m_ppIndex[size++] = pL;
			pL = pL->pNext;
		}
		m_size = size;

	}
};

#endif  // LAYERARRAY_HPP
