// C++ code Copyright (C) David R. Evans G4AMJ/NQ0I

#ifndef T_AR_DOSH
#define T_AR_DOSH

#include <defines.h>
#include <DREstring.h>

#include <alloc.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; these are rather hard in DOS, where we
// don't have any memory to play with.

template <class T> class big_array
{ boolean _auto_resize, _frozen;
  uint16 _elements_per_segment, _n_segments, _bytes_per_segment;
  int16 _current_segment_nr;
  T* _cache;
  uint32 _size;
  FILE* _fp;
  
  void _flush(void)
  { if (_frozen) then
      return;
    if (_current_segment_nr != -1) then
    { long offset = (long)_current_segment_nr * _bytes_per_segment;
      fseek(_fp, offset, 0);
      if (fwrite((char*)_cache, _bytes_per_segment, 1, _fp) != 1)
        fatal_error("Error flushing segment of big_array to disk");   
    }
  }


public:

// constructor
  big_array(const uint32 size = 0) : _current_segment_nr(-1), 
                                     _frozen(false)
  { int max_segment_size = MIN(farcoreleft() / 10, (unsigned long)10000),
        min_segment_size = sizeof(T);

    if (min_segment_size > max_segment_size) then    // highly unlikely
      fatal_error("Insufficient heap memory to build big_array");

    _elements_per_segment = max_segment_size / sizeof(T);
    _bytes_per_segment = _elements_per_segment * sizeof(T);
    heap_check(_cache = new T [_elements_per_segment]);
    _n_segments = size / _elements_per_segment + 1;

// allocate space on disk
    _fp = tmpfile();
    _size = size;
    START(_fp);
    for (int n = 0; n < _n_segments; n++)
      if (fwrite((byte*)_cache, _bytes_per_segment, 1, _fp) != 1) then
          fatal_error("Insufficient disk space to build type big_array");
  }

// destructor
  ~big_array(void)
  { _flush();
    destroy_array(_cache);
    fclose(_fp);     // closes and deletes file
  }

// switch on auto resizing
  void auto_resize(void)
    { _auto_resize = true; }

// switch off auto resizing
  void no_auto_resize(void)
    { _auto_resize = false; }

// return the size of the array
  uint32 size(void)
    { return _size; }

// freeze the array
  void freeze(void)
  { _frozen = true; }

// 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 < _elements_per_segment; n++)
      _cache[n] = value;
    START(_fp);
    for (n = 0; n < _n_segments; n++)
      fwrite((byte*)_cache, _bytes_per_segment, 1, _fp);
  }

// resize the array
  void resize(const uint32 new_size)
  { _flush();
    fclose(_fp);
    destroy_array(_cache);
    _current_segment_nr = -1;
    _fp = tmpfile();
    heap_check(_cache = new T [_elements_per_segment]);
    _n_segments = new_size / _elements_per_segment + 1;
    _size = new_size;
    START(_fp);
    for (int n = 0; n < _n_segments; n++)
      if (fwrite((byte*)_cache, _bytes_per_segment, 1, _fp) != 1) then
        fatal_error("Insufficient disk space to resize type big_array");
  }
  
// retrieve an element
  T& operator[](const uint32 n)
  { if (n >= _size) then
    { if (_auto_resize) then
      { const uint32 new_size = MAX(n, (const uint32)(2 * _size));
        resize(new_size);
      }
      else
        fatal_error("big_array index out of bounds");
    }

    uint16 segment_to_load = n / _elements_per_segment;

    if (_current_segment_nr != segment_to_load) then
    { _flush();
      long offset = (long)segment_to_load * _bytes_per_segment;
      fseek(_fp, offset, 0);
      if (fread((char*)_cache, _bytes_per_segment, 1, _fp) != 1)
        fatal_error("Error reading element of big_array from disk");   
      _current_segment_nr = segment_to_load;
// cout << "\r\t loaded segment # " << _current_segment_nr;
    }
// cout << "\r" << n;
    return _cache[n % _elements_per_segment];
  }
};

// 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> param)
  { _n_dimensions = param._n_dimensions;
    heap_check(_dimensions = new int [n_dimensions]);
    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 (int el = 0; el < product; el++)
      _array[el] = param._array[el];
  }
*/

// destructor
  ~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 (int el = 0; el < product; el++)
      _array[el] = param._array[el];
  }

  void freeze(void)
  { _array.freeze(); }

// set every element to zero
  void set_value(const T value)
  { _array.set_value(value); }

// 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
    uint 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)
  { }

  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
    uint index = (element0 * _dimensions[1] + element1);
    return _array[index];
  }
};

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)
  { }

  T& element(const uint element0, const uint element1, const uint element2, const uint element3)
  { 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
    uint index = ((element0 * _dimensions[1] + element1) * _dimensions[2] + element2)
                   * _dimensions[3] + element3;
    return _array[index];
  }
};

#endif

