#ifndef NETGEN_CORE_TABLE_HPP
#define NETGEN_CORE_TABLE_HPP

/**************************************************************************/
/* File:   table.hpp                                                      */
/* Author: Joachim Schoeberl                                              */
/* Date:   25. Mar. 2000                                                  */
/**************************************************************************/

#include <atomic>
#include <iostream>

#include "array.hpp"
#include "bitarray.hpp"
#include "taskmanager.hpp"
#include "ngcore_api.hpp"

namespace ngcore
{


template <class T>
class FlatTable
{
protected:
  /// number of rows
  size_t size;
  /// pointer to first in row
  size_t * index;
  /// array of data
  T * data;

public:
  FlatTable() = delete;

  NETGEN_INLINE FlatTable(size_t as, size_t * aindex, T * adata)
    : size(as), index(aindex), data(adata) { ; }

  /// Size of table
  NETGEN_INLINE size_t Size() const { return size; }

  /// Access entry
  NETGEN_INLINE const FlatArray<T> operator[] (size_t i) const
  {
    return FlatArray<T> (index[i+1]-index[i], data+index[i]);
  }

  NETGEN_INLINE T * Data() const { return data; }

  NETGEN_INLINE FlatArray<T> AsArray() const
  {
    return FlatArray<T> (index[size]-index[0], data+index[0]);
  }

  NETGEN_INLINE FlatArray<size_t> IndexArray() const
  {
    return FlatArray<size_t> (size+1, index);
  }

  /// takes range starting from position start of end-start elements
  NETGEN_INLINE FlatTable<T> Range (size_t start, size_t end) const
  {
    return FlatTable<T> (end-start, index+start, data);
  }

  /// takes range starting from position start of end-start elements
  NETGEN_INLINE FlatTable<T> Range (T_Range<size_t> range) const
  {
    return FlatTable<T> (range.Size(), index+range.First(), data);
  }


  class Iterator
  {
    const FlatTable & tab;
    size_t row;
  public:
    Iterator (const FlatTable & _tab, size_t _row) : tab(_tab), row(_row) { ; }
    Iterator & operator++ () { ++row; return *this; }
    FlatArray<T> operator* () const { return tab[row]; }
    bool operator!= (const Iterator & it2) { return row != it2.row; }
  };

  Iterator begin() const { return Iterator(*this, 0); }
  Iterator end() const { return Iterator(*this, size); }
};

  NGCORE_API extern size_t * TablePrefixSum32 (FlatArray<unsigned int> entrysize);
  NGCORE_API extern size_t * TablePrefixSum64 (FlatArray<size_t> entrysize);


  NETGEN_INLINE size_t * TablePrefixSum (FlatArray<unsigned int> entrysize)
  { return TablePrefixSum32 (entrysize); }
  NETGEN_INLINE size_t * TablePrefixSum (FlatArray<int> entrysize)
  { return TablePrefixSum32 (FlatArray<unsigned> (entrysize.Size(), (unsigned int*)(int*)(entrysize.Addr(0)))); }
  NETGEN_INLINE size_t * TablePrefixSum (FlatArray<std::atomic<int>> entrysize)
  { return TablePrefixSum32 (FlatArray<unsigned> (entrysize.Size(), (unsigned int*)(std::atomic<int>*)entrysize.Addr(0))); }
  NETGEN_INLINE size_t * TablePrefixSum (FlatArray<size_t> entrysize)
  { return TablePrefixSum64 (entrysize); }


/**
    A compact Table container.
    A table contains size entries of variable size.
    The entry sizes must be known at construction.
*/
template <class T>
class Table : public FlatTable<T>
{
protected:

  using FlatTable<T>::size;
  using FlatTable<T>::index;
  using FlatTable<T>::data;

public:
  ///
  NETGEN_INLINE Table () : FlatTable<T> (0,nullptr,nullptr) { ; }
  /// Construct table of uniform entrysize
  NETGEN_INLINE Table (size_t asize, size_t entrysize)
    : FlatTable<T>( asize, new size_t[asize+1], new T[asize*entrysize] )
  {
    for (size_t i : IntRange(size+1))
      index[i] = i*entrysize;
  }

  /// Construct table of variable entrysize
  template <typename TI>
  NETGEN_INLINE Table (FlatArray<TI> entrysize)
  : FlatTable<T> (0, nullptr, nullptr)
  {
    size  = entrysize.Size();
    index = TablePrefixSum (entrysize);
    size_t cnt = index[size];
    data = new T[cnt];
  }

  explicit NETGEN_INLINE Table (const Table<T> & tab2)
    : FlatTable<T>(0, nullptr, nullptr)
  {
    size = tab2.Size();

    index = new size_t[size+1];
    for (size_t i = 0; i <= size; i++)
      index[i] = tab2.index[i];

    size_t cnt = index[size];
    data = new T[cnt];
    for (size_t i = 0; i < cnt; i++)
      data[i] = tab2.data[i];
  }

  NETGEN_INLINE Table (Table<T> && tab2)
    : FlatTable<T>(0, nullptr, nullptr)
  {
    Swap (size, tab2.size);
    Swap (index, tab2.index);
    Swap (data, tab2.data);
  }

  NETGEN_INLINE Table & operator= (Table<T> && tab2)
  {
    Swap (size, tab2.size);
    Swap (index, tab2.index);
    Swap (data, tab2.data);
    return *this;
  }



  /// Delete data
  NETGEN_INLINE ~Table ()
  {
    delete [] data;
    delete [] index;
  }

  /// Size of table
  using FlatTable<T>::Size;

  /// number of elements in all rows
  NETGEN_INLINE size_t NElements() const { return index[size]; }

  using FlatTable<T>::operator[];
};


/// Print table
template <class T>
inline ostream & operator<< (ostream & s, const Table<T> & table)
{
  for (auto i : Range(table))
    {
      s << i << ":";
      for (auto el : table[i])
	s << " " << el;
      s << "\n";
    }
  s << std::flush;
  return s;
}




template <class T>
  class TableCreator
  {
  protected:
    int mode;    // 1 .. cnt, 2 .. cnt entries, 3 .. fill table
    std::atomic<size_t> nd;
    Array<std::atomic<int>> cnt;
    Table<T> table;
  public:
    TableCreator()
    { nd = 0; mode = 1; }
    TableCreator (size_t acnt)
    { nd = acnt; SetMode(2); }

    Table<T> MoveTable()
    {
      return std::move(table);
    }

    bool Done () { return mode > 3; }
    void operator++(int) { SetMode (mode+1); }

    int GetMode () const { return mode; }
    void SetMode (int amode)
    {
      mode = amode;
      if (mode == 2)
	{
	  // cnt.SetSize(nd);  // atomic has no copy
          cnt = Array<std::atomic<int>> (nd);
          for (auto & ci : cnt) ci.store (0, std::memory_order_relaxed);
	}
      if (mode == 3)
	{
          table = Table<T> (cnt);
          // for (auto & ci : cnt) ci = 0;
          for (auto & ci : cnt) ci.store (0, std::memory_order_relaxed);
          // cnt = 0;
	}
    }

    void SetSize (size_t _nd)
    {
      if (mode == 1)
        nd = _nd;
      else
        {
          if (nd != _nd)
            throw Exception ("cannot change size of table-creator");
        }
    }

    void Add (size_t blocknr, const T & data)
    {
      switch (mode)
	{
	case 1:
          {
            size_t oldval = nd;
            while (blocknr+1>nd) {
              nd.compare_exchange_weak (oldval, blocknr+1);
              oldval = nd;
            }
            break;
          }
	case 2:
	  cnt[blocknr]++;
	  break;
	case 3:
          int ci = cnt[blocknr]++;
          table[blocknr][ci] = data;
	  break;
	}
    }


    void Add (size_t blocknr, IntRange range)
    {
      switch (mode)
	{
	case 1:
          {
            size_t oldval = nd;
            while (blocknr+1>nd) {
              nd.compare_exchange_weak (oldval, blocknr+1);
              oldval = nd;
            }
            break;
          }
	case 2:
	  cnt[blocknr] += range.Size();
	  break;
	case 3:
          size_t ci = ( cnt[blocknr] += range.Size() ) - range.Size();
	  for (size_t j = 0; j < range.Size(); j++)
            table[blocknr][ci+j] = range.First()+j;
	  break;
	}
    }

    void Add (size_t blocknr, const FlatArray<int> & dofs)
    {
      switch (mode)
	{
	case 1:
          {
            size_t oldval = nd;
            while (blocknr+1>nd) {
              nd.compare_exchange_weak (oldval, blocknr+1);
              oldval = nd;
            }
            break;
          }
	case 2:
	  cnt[blocknr] += dofs.Size();
	  break;
	case 3:
          size_t ci = ( cnt[blocknr] += dofs.Size() ) - dofs.Size();
	  for (size_t j = 0; j < dofs.Size(); j++)
            table[blocknr][ci+j] = dofs[j];
	  break;
	}
    }
  };

  class NGCORE_API FilteredTableCreator : public TableCreator<int>
  {
  protected:
    const BitArray* takedofs;
  public:
    FilteredTableCreator(const BitArray* atakedofs)
      : TableCreator<int>(), takedofs(atakedofs) { };
    FilteredTableCreator(int acnt, const BitArray* atakedofs)
      : TableCreator<int>(acnt),takedofs(atakedofs) { };
    void Add (size_t blocknr, int data);
    void Add (size_t blocknr, IntRange range);
    void Add (size_t blocknr, FlatArray<int> dofs);
  };


  /// Base class to generic DynamicTable.
  class BaseDynamicTable
  {
  protected:

    ///
    struct linestruct
    {
      ///
      int size;
      ///
      int maxsize;
      ///
      void * col;
    };

    ///
    Array<linestruct> data;
    ///
    char * oneblock;

  public:
    ///
    NGCORE_API BaseDynamicTable (int size);
    ///
    NGCORE_API BaseDynamicTable (const Array<int> & entrysizes, int elemsize);
    ///
    NGCORE_API ~BaseDynamicTable ();

    /// Changes Size of table to size, deletes data
    NGCORE_API void SetSize (int size);
    ///
    NGCORE_API void IncSize (int i, int elsize);

    NGCORE_API void DecSize (int i);
  };



  /**
      A dynamic table class.

      A DynamicTable contains entries of variable size. Entry sizes can
      be increased dynamically.
  */
  template <class T>
  class DynamicTable : public BaseDynamicTable
  {
  public:
    /// Creates table of size size
    DynamicTable (int size = 0)
      : BaseDynamicTable (size) { ; }

    /// Creates table with a priori fixed entry sizes.
    DynamicTable (const Array<int> & entrysizes)
      : BaseDynamicTable (entrysizes, sizeof(T)) { ; }

    /// Inserts element acont into row i. Does not test if already used.
    void Add (int i, const T & acont)
    {
      if (data[i].size == data[i].maxsize)
        IncSize (i, sizeof (T));
      else
        data[i].size++;
      static_cast<T*> (data[i].col) [data[i].size-1] = acont;
    }

    /// Inserts element acont into row i, iff not yet exists.
    void AddUnique (int i, const T & cont)
    {
      int es = EntrySize (i);
      int * line = const_cast<int*> (GetLine (i));
      for (int j = 0; j < es; j++)
        if (line[j] == cont)
          return;
      Add (i, cont);
    }


    /// Inserts element acont into row i. Does not test if already used.
    void AddEmpty (int i)
    {
      IncSize (i, sizeof (T));
    }

    /** Set the nr-th element in the i-th row to acont.
        Does not check for overflow. */
    void Set (int i, int nr, const T & acont)
    { static_cast<T*> (data[i].col)[nr] = acont; }


    /** Returns the nr-th element in the i-th row.
      Does not check for overflow. */
    const T & Get (int i, int nr) const
    { return static_cast<T*> (data[i].col)[nr]; }


    /** Returns pointer to the first element in row i. */
    const T * GetLine (int i) const
    { return static_cast<T*> (data[i].col); }


    /// Returns size of the table.
    int Size () const
    { return data.Size(); }

    /// Returns size of the i-th row.
    int EntrySize (int i) const
    { return data[i].size; }

    ///
    void DecEntrySize (int i)
    { DecSize(i); }

    /// Access entry i
    FlatArray<T> operator[] (int i)
    { return FlatArray<T> (data[i].size, static_cast<T*> (data[i].col)); }

    /*
    typedef const FlatArray<T> ConstFlatArray;
    /// Access entry i
    ConstFlatArray operator[] (int i) const
    { return FlatArray<T> (data[i].size, static_cast<T*> (data[i].col)); }
    */
    FlatArray<T> operator[] (int i) const
    { return FlatArray<T> (data[i].size, static_cast<T*> (data[i].col)); }
  };




  /// Print table
  template <class T>
  inline ostream & operator<< (ostream & s, const DynamicTable<T> & table)
  {
    for (int i = 0; i < table.Size(); i++)
      {
        s << i << ":";
        for (int j = 0; j < table[i].Size(); j++)
          s << " " << table[i][j];
        s << "\n";
      }
    s << std::flush;
    return s;
  }

  typedef DynamicTable<int> IntTable;

} // namespace ngcore

#endif // NETGEN_CORE_TABLE_HPP