//
// This header file defines the container class openArrayT<> and
// its associated iterator. This container class represents an open
// array with packed and unpacked dimensions.
//

#ifndef OPEN_ARRAY_H
#define OPEN_ARRAY_H

#include "svdpi.h"

namespace DPI_OO {

	template <typename T>
	class openArrayT;

	template <typename T>
	class openArrayIterT {

	public:

		openArrayIterT(openArrayT<T>* arr, int idx)
			: array(arr),
			  index(idx)
		{}

		~openArrayIterT() {}

		openArrayIterT<T>&
		operator = (const openArrayIterT<T>& other)
		{
			index = other->get_index();
			array = other->get_array();
		}

		openArrayIterT<T>&
		operator + (int plus_val)
		{
			index += plus_val;
			return *this;
		}

		openArrayIterT<T>&
		operator ++ ()
		{
			index ++;
			return *this;
		}

		openArrayIterT<T>&
		operator ++ (int)
		{
			index ++;
			return *this;
		}

		T* operator * ()
		{
			return array->operator[] (index);
		}

		bool operator != (const openArrayIterT<T>& other)
		{
			if (this->get_array() != other.get_array() ||
				this->get_index() != other.get_index()) {
				return true;
			}
			return false;
		}

		openArrayT<T>* get_array() const { return array; }
		int get_index() const { return index; }

	private:
		openArrayT<T>* array;
		int index;
	};

	template <typename T>
	class openArrayIterT<openArrayT<openArrayT<T> > > {

	public:
		openArrayIterT(openArrayT<openArrayT<T> >* arr, int idx)
			: array(arr),
			  index(idx)
		{}

		~openArrayIterT() {}

		openArrayIterT<openArrayT<openArrayT<T> > >&
		operator = (const openArrayIterT<openArrayT<openArrayT<T> > >& other)
		{
			index = other->get_index();
			array = other->get_array();
		}

		openArrayIterT<openArrayT<openArrayT<T> > >&
		operator + (int plus_val)
		{
			index += plus_val;
			return *this;
		}

		openArrayIterT<openArrayT<openArrayT<T> > >&
		operator ++ ()
		{
			index ++;
			return *this;
		}

		openArrayIterT<openArrayT<openArrayT<T> > >&
		operator ++ (int)
		{
			index ++;
			return *this;
		}

		openArrayT<T>& operator * ()
		{
			return array->operator[] (index);
		}

		bool operator != (const openArrayIterT<openArrayT<openArrayT<T> > >& other)
		{
			if (this->get_array() != other.get_array() ||
				this->get_index() != other.get_index()) {
				return true;
			}
			return false;
		}

		openArrayT<openArrayT<T> >* get_array() const { return array; }
		int get_index() const { return index; }

	private:
		openArrayT<openArrayT<T> >* array;
		int index;
	};



	template <typename T>
	class openArrayT {

	public:
		typedef DPI_OO::openArrayIterT<T> iterator;

		typedef DPI_OO::openArrayT<T>* this_type;

		openArrayT(const svOpenArrayHandle hndl)
			: arrayhndl(hndl),
			  dimension(1),
			  elem(NULL),
			  indexes(NULL),
			  curr_index(0)
		{}

		openArrayT(const openArrayT<T>& from)
			: arrayhndl(from.get_hndl()),
			  dimension(from.dim()),
			  elem(from.get_elem()),
			  indexes(NULL),
			  curr_index(0)
		{
			if (dimension > 1) {
				int* from_ind = from.get_indexes();
				if (from_ind) {
					indexes = new int[dimension];
					for (int i = 0; i < dimension; i++) {
						indexes[i] = from_ind[i];
					}
				}
			}
		}

		~openArrayT()
		{
			if (indexes != NULL)
				delete[] indexes;
		}

		int size() const { return svSize(arrayhndl, dimension); }

		int arraysize() const { 
			if (dimension == 1) 
				return svSizeOfArray(arrayhndl); 
			return 0;
		}

		int low() const { return svLow(arrayhndl, dimension); }
		int high() const { return svHigh(arrayhndl, dimension); }
		int left() const { return svLeft(arrayhndl, dimension); }
		int right() const { return svRight(arrayhndl, dimension); }
		int dimensions() const { return svDimensions(arrayhndl); }

		T* operator [] (int idx)
		{
			// This is the leaf, need to get the value
			switch (dimension) {
				case 1:
					return (T*)svGetArrElemPtr1(arrayhndl, idx);
				case 2:
					std::cout << "Indexes " << indexes[0] << ", " << idx << endl;
					return (T*)svGetArrElemPtr2(arrayhndl, indexes[0], idx);
				case 3:
					return (T*)svGetArrElemPtr3(arrayhndl, indexes[0], indexes[1], idx);
				case 4:
					return (T*)svGetArrElemPtr(arrayhndl, indexes[0], indexes[1], indexes[2], idx);
				default:
					break;
			}
		}

		openArrayT<T>& operator= (const openArrayT<T>& other)
		{
		}

		T* operator* ()
		{
			if (dimension == 1) return (T*)svGetArrayPtr(arrayhndl); 
			return NULL;
		}

		iterator begin() const { return iterator((this_type)this, left()); }

		iterator end() const { return iterator((this_type)this, right()); }


		operator svOpenArrayHandle () {
			return arrayhndl;
		}

		/* Internal functions */

		int dim() const { return dimension; }

		void incr_dim() { dimension++; }

		void push_index(int idx, int d) { 
			if (indexes == NULL) 
				indexes = new int[dimension];
			indexes[d-1] = idx; 
		} 

		T* get_elem() const { return elem; }
		int* get_indexes() const { return indexes; }
		svOpenArrayHandle get_hndl() const { return arrayhndl; }
		int get_current_index() const { return curr_index; }


	private:
			svOpenArrayHandle arrayhndl;
			int dimension;
			T* elem;
			int* indexes;
			int curr_index;
	};

	template <typename T>
	class openArrayT<openArrayT<T> > {


	public:

		typedef DPI_OO::openArrayIterT<openArrayT<openArrayT<T> > > iterator;

		typedef DPI_OO::openArrayT<openArrayT<T> >* this_type;

		openArrayT(const svOpenArrayHandle hndl, openArrayT<T>* elem)
			: arrayhndl(hndl),
			  dimension(1),
			  elem(elem),
			  indexes(NULL),
			  curr_index(0)
		{ elem->incr_dim(); }

		openArrayT(const openArrayT<openArrayT<T> >& from)
			: arrayhndl(from.get_hndl()),
			  dimension(from.dim()),
			  elem(from.get_elem()),
			  indexes(NULL),
			  curr_index(0)
		{
			if (dimension > 1) {
				int* from_ind = from.get_indexes();
				if (from_ind) {
					indexes = new int[dimension];
					for (int i = 0; i < dimension; i++) {
						indexes[i] = from_ind[i];
					}
				}
			}
		}
			

		~openArrayT()
		{
			if (indexes != NULL)
				delete[] indexes;
		}

		int size() const { return svSize(arrayhndl, dimension); }
		int arraysize() const { 
			if (dimension == 1) 
				return svSizeOfArray(arrayhndl); 
			return 0;
		}
		int low() const { return svLow(arrayhndl, dimension); }
		int high() const { return svHigh(arrayhndl, dimension); }
		int left() const { return svLeft(arrayhndl, dimension); }
		int right() const { return svRight(arrayhndl, dimension); }
		int dimensions() const { return svDimensions(arrayhndl); }


		openArrayT<T>& operator [] (int idx)
		{
			if (dimension > 1) {
				for (int i = 1; i < dimension; i++)
					elem->push_index(indexes[i-1], i);
			}
			elem->push_index(idx, dimension);
			return *elem;
		}

		openArrayT<openArrayT<T> >& operator= (const openArrayT<openArrayT<T> >& other)
		{
			arrayhndl = other.get_hndl();
			dimension = other.dim();
			elem = other.get_elem();
			if (indexes != NULL)
				delete[] indexes;

			indexes = NULL;

			if (dimension > 1) {
				int* from_ind = other.get_indexes();
				if (from_ind) {
					indexes = new int[dimension];
					for (int i = 0; i < dimension; i++) {
						indexes[i] = from_ind[i];
					}
				}
			}
		}

		void* operator* ()
		{
			if (dimension == 1) return svGetArrayPtr(arrayhndl); 
			return NULL;
		}

		iterator begin() const { return iterator((this_type)this, left()); }

		iterator end() const { return iterator((this_type)this, right()); }

		operator svOpenArrayHandle () {
			return arrayhndl;
		}

		// Internal functions

		int dim() const { return dimension; }
		void incr_dim() { dimension++; elem->incr_dim(); }

		void push_index(int idx, int dim) { 
			if (indexes == NULL) indexes = new int[dimension];
			indexes[dim-1] = idx;
		}

		T* get_elem() const { return elem; }

		int* get_indexes() const { return indexes; }

		svOpenArrayHandle get_hndl() const { return arrayhndl; }

		int get_current_index() const { return curr_index; }


		private:
			svOpenArrayHandle arrayhndl;
			int dimension;
			openArrayT<T>* elem;
			int* indexes;
			int curr_index;
	};

	template <>
	class openArrayT<svLogic> {

	public:
		typedef DPI_OO::openArrayIterT<svLogic> iterator;

		typedef DPI_OO::openArrayT<svLogic>* this_type;

		openArrayT(const svOpenArrayHandle hndl)
			: arrayhndl(hndl),
			  dimension(1),
			  elem(NULL),
			  indexes(NULL),
			  curr_index(0)
		{}

		openArrayT(const openArrayT<svLogic>& from)
			: arrayhndl(from.get_hndl()),
			  dimension(from.dim()),
			  elem(from.get_elem()),
			  indexes(NULL),
			  curr_index(0)
		{
			if (dimension > 1) {
				int* from_ind = from.get_indexes();
				if (from_ind) {
					indexes = new int[dimension];
					for (int i = 0; i < dimension; i++) {
						indexes[i] = from_ind[i];
					}
				}
			}
		}

		~openArrayT()
		{
			if (indexes != NULL)
				delete[] indexes;
		}

		int size() const { return svSize(arrayhndl, dimension); }

		int arraysize() const { 
			if (dimension == 1) 
				return svSizeOfArray(arrayhndl); 
			return 0;
		}

		int low() const { return svLow(arrayhndl, dimension); }
		int high() const { return svHigh(arrayhndl, dimension); }
		int left() const { return svLeft(arrayhndl, dimension); }
		int right() const { return svRight(arrayhndl, dimension); }
		int dimensions() const { return svDimensions(arrayhndl); }

		svLogic* operator [] (int idx)
		{
			// This is the leaf, need to get the value
			switch (dimension) {
				case 1:
					return (svLogic*)svGetArrElemPtr1(arrayhndl, idx);
				case 2:
					std::cout << "Indexes " << indexes[0] << ", " << idx << endl;
					return (svLogic*)svGetArrElemPtr2(arrayhndl, indexes[0], idx);
				case 3:
					return (svLogic*)svGetArrElemPtr3(arrayhndl, indexes[0], indexes[1], idx);
				case 4:
					return (svLogic*)svGetArrElemPtr(arrayhndl, indexes[0], indexes[1], indexes[2], idx);
				default:
					break;
			}
		}

		openArrayT<svLogic>& operator= (const openArrayT<svLogic>& other)
		{
		}

		svLogicVecVal* operator* ()
		{
			if (dimension == 1) return (svLogicVecVal*)svGetArrayPtr(arrayhndl); 
			return NULL;
		}

		iterator begin() const { return iterator((this_type)this, left()); }

		iterator end() const { return iterator((this_type)this, right()); }


		operator svOpenArrayHandle () {
			return arrayhndl;
		}

		void set_elem_val(int leaf_idx, const svLogic* val);
		void get_elem_val(int leaf_idx, svLogic* val);

		// Internal functions

		int dim() const { return dimension; }
		void incr_dim() { dimension++; }

		void push_index(int idx, int d) { 
			if (indexes == NULL) 
				indexes = new int[dimension];
			indexes[d-1] = idx; 
		} 

		svLogic* get_elem() const { return elem; }
		int* get_indexes() const { return indexes; }
		svOpenArrayHandle get_hndl() const { return arrayhndl; }
		int get_current_index() const { return curr_index; }


	private:
			svOpenArrayHandle arrayhndl;
			int dimension;
			svLogic* elem;
			int* indexes;
			int curr_index;
	};
};

#endif
