// C++ code Copyright (C) David R. Evans G4AMJ/NQ0I

#ifndef T_AR_WINH
#define T_AR_WINH

#include <defines.h>
#include <DREstring.h>

#include <stdarg.h>

// add some defines so that we can work around the Mac's lack of
// template capability.
#define BIG_ARRAY(x) big_array<x>
#define BIG_2_ARRAY(x) big_2_array<x>
#define BIG_4_ARRAY(x) big_4_array<x>

// a class for large arrays.
// pro: can be used with segmented architecture
//      has bounds checking built in
//      parsimonious with memory
// con: slower than a simple array declaration

template <class T> class big_array
{ boolean _auto_resize;
  const uint16 _n_elements_per_segment;
  uint32 _size, _n_segments;
  T** _tpp;
  
public:

// constructor
  big_array(const uint32 size = 0) :
	 _n_elements_per_segment(64000 / sizeof(T)), _auto_resize(false)
  { _size = size;
	 _n_segments = ((size ? size : (size + 1)) - 1) / _n_elements_per_segment + 1;

	 heap_check(_tpp = new T* [_n_segments]);
	 for (int n = 0; n < _n_segments; n++)
		heap_check(_tpp[n] = new T [_n_elements_per_segment]);
  }

// copy constructor
  big_array(const big_array<T>& p) :
	 _n_elements_per_segment(p._n_elements_per_segment)
  { _auto_resize = p._auto_resize;
	 _size = p._size;
	 _n_segments = p._n_segments;
	 heap_check(_tpp = new T* [_n_segments]);
	 for (int n = 0; n < _n_segments; n++)
		heap_check(_tpp[n] = new T [_n_elements_per_segment]);

	 for (n = 0; n < _n_segments; n++)
		for (int n1 = 0; n1 < _n_elements_per_segment; n1++)
	_tpp[n][n1] = p._tpp[n][n1];
  }

// destructor
  ~big_array(void)
  { for (int n = 0; n < _n_segments; n++)
		destroy_array(_tpp[n]);

	 destroy_array(_tpp);
  }

// switch on auto resizing
  inline void auto_resize(void)
	 { _auto_resize = true; }

// switch off auto resizing
  inline void no_auto_resize(void)
	 { _auto_resize = false; }

// set all elements of the array -- we can take advantage of the internal
// structure to do this more quickly than otherwise possible
  void set_value(const T value)
  { for (int n = 0; n < _n_segments; n++)
		for (int n1 = 0; n1 < _n_elements_per_segment; n1++)
	_tpp[n][n1] = value;
  }

// resize the array. The new array contains as much information as possible
// from the old one.
  void resize(const uint32 new_size)
  { // allocate the new space
	 const int _new_n_segments =((new_size ? new_size :
									  (new_size + 1)) - 1) / _n_elements_per_segment + 1;

	 T** _new_tpp;
	 heap_check(_new_tpp = new T* [_new_n_segments]);
	 for (uint32 n = 0; n < _new_n_segments; n++)
		heap_check(_new_tpp[n] = new T [_n_elements_per_segment]);

	 const uint32 _elements_to_transfer = MIN(_size, (uint32)new_size);
	 for (n = 0; n < _elements_to_transfer; n++)
		_new_tpp[n / _n_elements_per_segment][n % _n_elements_per_segment] =
		  _tpp[n / _n_elements_per_segment][n % _n_elements_per_segment];

// now delete everything from the heap
	 for (n = 0; n < _n_segments; n++)
		destroy_array(_tpp[n]);

	 destroy_array(_tpp);

// change internal values
	 _size = new_size;
	 _n_segments = _new_n_segments;
	 _tpp = _new_tpp;
}

// retrieve an element
  T& operator[](const uint32 n)
  { if (n >= _size) then
	 { if (!_auto_resize) then
		  fatal_error("big_array index out of bounds");
		else
		  resize(MAX(2 * _size, (uint32)n));
	 }
// perform explicit sanity check
    const uint16 index1 = n / _n_elements_per_segment,
                 index2 = n % _n_elements_per_segment;
    if ((index1 >= _n_segments) || (index2 >= _n_elements_per_segment)) then
      fatal_error("failed sanity check in t_ar_win.h");

	 return _tpp[index1][index2];
  }

// return the current size
  inline uint32 size(void) const
	 { return _size; }
};

// a class for large multidimensional arrays

template <class T> class big_multi_array
{
protected:
  int _n_dimensions;
  int* _dimensions;
  big_array<T> _array;

public:
// constructor
  big_multi_array(const uint n_dimensions, ...)
  { va_list ap;
    va_start(ap, n_dimensions);
    _n_dimensions = n_dimensions;
    heap_check(_dimensions = new int [n_dimensions]);
    uint32 product = 1;
    for (int n = 0; n < n_dimensions; n++)
    { _dimensions[n] = va_arg(ap, const uint);
      product *= _dimensions[n];
    }
    va_end(ap);
    _array.resize(product);
  }

// copy constructor
  big_multi_array(const big_multi_array<T>& p)
  { _n_dimensions = p._n_dimensions;
    heap_check(_dimensions = new int [_n_dimensions]);
    uint32 product = 1;
    for (int n = 0; n < _n_dimensions; n++)
    { _dimensions[n] = p._dimensions[n];
      product *= _dimensions[n];
    }
    _array.resize(product);

// now copy the individual elements
    for (uint32 el = 0; el < product; el++)
		_array[el] = p._array[el];
  }

// destructor
  virtual ~big_multi_array(void)
  { destroy_array(_dimensions); }

// big_multi_array = big_multi_array
  void operator=(big_multi_array<T> param)
  { uint32 product = 1;
    for (int n = 0; n < _n_dimensions; n++)
    { _dimensions[n] = param._dimensions[n];
      product *= _dimensions[n];
    }
    _array.resize(product);

// now copy the individual elements
    for (uint32 el = 0; el < product; el++)
      _array[el] = param._array[el];
  }

// set every element to zero
  void set_value(const T value)
  { _array.set_value(value); }

// this is a dummy function; it is necessary because raw DOS uses it
  void freeze(void)
  { }

// return a reference to an element. Unfortunately, for no reason of which I
// am aware, one cannot declare an operator[](...). This is very slow (which
// is a good reason not to use this template class unless one really needs
// it). On the other hand, it is nice to be able to handle _all_ kinds of
// multidimensional array with a single routine.
  T& element(const uint element, ...)
  { int* _elements;
    heap_check(_elements = new int [_n_dimensions]);
    va_list ap;
    va_start(ap, element);
    _elements[0] = element;
    for (int n = 1; n < _n_dimensions; n++)
      _elements[n] = va_arg(ap, const uint);
    va_end(ap);
    for (n = 0; n < _n_dimensions; n++)
      if ((_elements[n] < 0) || (_elements[n] >= _dimensions[n])) then
	fatal_error((DREstring)"Element out of range in big_multi_array; "
	+ (DREstring)" dimension number " + DREstring10(n) + (DREstring)", value "
	+ DREstring10(_elements[n]) + (DREstring)", bound "
	+ DREstring10(_dimensions[n]));
// we store in order of rightmost index contiguous
    uint32 index = 0;
    for (n = 0; n < _n_dimensions - 1; n++)
      index = (index + _elements[n]) * _dimensions[n + 1];
    index += _elements[_n_dimensions - 1];
    destroy_array(_elements);
	 return _array[index];
  }
};

// faster versions for specific sizes
template <class T> class big_2_array : public big_multi_array<T>
{
public:
  big_2_array(const uint d1 = 0, const uint d2 = 0) :
    big_multi_array<T>(2, d1, d2)
  { }

// copy constructor
  big_2_array(const big_2_array<T>& p) : big_multi_array<T>(p)
    { }

  T& element(const uint element0, const uint element1)
  { if ((element0 > _dimensions[0]) || (element1 > _dimensions[1])) then
      fatal_error("Index out of bounds");

// we store in order of rightmost index contiguous
    return _array[((uint32)(element0) * _dimensions[1]) + element1];
  }
};

template <class T> class big_4_array : public big_multi_array<T>
{
public:
  big_4_array(const uint d1 = 0, const uint d2 = 0, const uint d3 = 0, const uint d4 = 0) :
    big_multi_array<T>(4, d1, d2, d3, d4)
  { }

// copy constructor
  big_4_array(const big_4_array<T>& p) : big_multi_array<T>(p)
    { }

  T& element(const uint element0, const uint element1, const uint element2, const uint element3) const
  { if ((element0 > _dimensions[0]) || (element1 > _dimensions[1]) ||
	(element2 > _dimensions[2]) || (element3 > _dimensions[3])) then
      fatal_error("Index out of bounds");

// we store in order of rightmost index contiguous
    uint32 index = (((uint32)element0 * _dimensions[1] + element1) 
                        * _dimensions[2] + element2)
                          * _dimensions[3] + element3;
    return _array[index];
  }
};

#endif
