/** \file grower.h Template class that implements dynamic growing arrays.
 *  Yacas implements its own growing array class to ensure correct behaviour
 *  on all platforms (stl was still in development and not supported on all
 *  platforms when development started).
 *
 * grower started 1998 ayal Pinkus
 *
 */
 
#ifndef _GROWER_H_
#define _GROWER_H_

#include "yacasbase.h"
#include "lispassert.h"



// Default placement versions of operator new and delete, placed here because I do not want to include <new>
inline void* operator new(size_t, void* __p) throw() { return __p; }
inline void* operator new[](size_t, void* __p) throw() { return __p; }
inline void  operator delete  (void*, void*) throw() { }
inline void  operator delete[](void*, void*) throw() { }


template <class T>
inline void constructAt(void * to_obj, const T * from_obj)
{
  new (to_obj) T(*from_obj);
}

class ArrOps
{
public:
  virtual bool isPOD() const { return false; }
  virtual void construct(void * buffer) const = 0;
  virtual void construct(void * to_obj, const void * from_obj) const = 0;
  virtual void destruct(void * obj) const = 0;
  virtual int granularity() const { return 8; }
  static void resize(char** pArray, const ArrOps& opers, int& iSize, int& iCapacity, int aSize, int aItemSize, int ext);
    static void remove(char** pArray, const ArrOps& opers, int& iSize, int aIndex, int aCount, int aItemSize);
  static void reserve(char** pArray, int& iCapacity, int aSize, int aItemSize);
  //static void* insert(char** pArray, const ArrOps& opers, int& iSize, void* aData, int aIndex, int aCount, int aSize, int aExtSize);
};

template <class T>
class ArrOpsCustomObj : public ArrOps
{
public:
  ArrOpsCustomObj() {}
  inline void construct(void * buffer) const
  { new (buffer) T; }
  inline void construct(void * to_obj, const void * from_obj) const
  { constructAt(to_obj, static_cast<const T*>(from_obj)); }
  inline void destruct(void * obj) const
  { /*obj;*/ static_cast<T*>(obj)->~T(); }
  //inline int size() const { return sizeof(T); }
};

template <class T>
class ArrOpsPOD : public ArrOps
{
public:
  ArrOpsPOD() {}
  inline bool isPOD() const { return true; }
  inline void construct(void * buffer) const {}
  inline void construct(void * to_obj, const void * from_obj) const
  { *static_cast<T*>(to_obj) = *static_cast<const T*>(from_obj); }
  inline void destruct(void * obj) const {}
  //inline int size() const { return sizeof(T); }
};

template <class T>
class ArrOpsCustomPtr : public ArrOps
{
  typedef T* TY;
public:
  ArrOpsCustomPtr() {}
  inline void construct(void * buffer) const
  { *static_cast<TY*>(buffer) = 0; }
  inline void construct(void * to_obj, const void * from_obj) const
  { *static_cast<TY*>(to_obj) = *static_cast<const TY*>(from_obj); }
  inline void destruct(void * obj) const {}
  //inline int size() const { return sizeof(TY); }
  inline int granularity() const { return 8; }
};

template <class T>
class ArrOpsDeletingObj {};

template <class T>
class ArrOpsDeletingPtr : public ArrOpsCustomPtr<T>
{
  typedef T* TY;
public:
  inline void destruct(void * obj) const
  { delete *static_cast<const TY*>(obj); }
};

/** template class useful for implementing a dynamic growing array
 *  for any arbitrary type of object. If using the array to maintain
 *  objects, please use pointers to the objects, and use the
 * CDeletingArrayGrower to automatically delete the objects at destruction.
 */
template <class T, class TOps >
class CArrayGrower : public YacasBase
{
public:
  /** ElementType can be used outside this class as the type of the
   * object in the array. This is useful in templated functions that
   * work on a CArrayGrower without being part of CArrayGrower
   */
  typedef LispInt SizeType;
  typedef T ElementType;

  CArrayGrower()
    : iArray(0)
    , iSize(0)
    , iCapacity(0)
    , iArrayOwnedExternally(LispFalse)
    {
    }
  virtual ~CArrayGrower()
  {
    Clear();
  }
  inline void Clear()  // oddly, only *one* use outside destructor!?
  {
    if (iSize)
    {
      TOps opers;
      if(!opers.isPOD())
      {
        while (iSize)
        opers.destruct(iArray + --iSize);
      }
    }
    if (!iArrayOwnedExternally)
    {
      PlatFree(iArray);
    }
    iArray = 0;
    iCapacity = iSize = 0;
    iArrayOwnedExternally = LispFalse;
  }
  inline SizeType Size() const { return iSize; }

  inline CArrayGrower(const CArrayGrower<T,TOps>& aOther)
    : iArray(0)
    , iSize(0)
    , iCapacity(0)
    , iArrayOwnedExternally(LispFalse)
  {
    // Make sure we're not accidentally copying a huge array. We want this system to stay efficient...
    LISPASSERT(aOther.iSize == 0);
    LISPASSERT(aOther.iArrayOwnedExternally == LispFalse);
  }

  inline const CArrayGrower<T,TOps>& operator=(const CArrayGrower<T,TOps>& aOther)
  {
    // Make sure we're not accidentally copying a huge array. We want this system to stay efficient...
    LISPASSERT(iArray == 0);
    LISPASSERT(iSize == 0);
    LISPASSERT(iCapacity == 0);
    LISPASSERT(iArrayOwnedExternally == LispFalse);

    LISPASSERT(aOther.iSize == 0);
    LISPASSERT(aOther.iArrayOwnedExternally == LispFalse);
    return *this;
  }

private:

  void moreCapacity(SizeType aSize, int g)  // almost independent of T
  {
    LISPASSERT(!iArrayOwnedExternally);
    LISPASSERT(iCapacity >= 0);
    // Compute a new iCapacity >= aSize, with iCapacity % g == 0.
    // We assume g is a power of 2.  (fwiw, in two's-complement, ~(g-1) == -g.
    iCapacity = (aSize + g) & ~(g-1);
    if (!iArray)
    {
      iArray = (ElementType*)PlatAlloc(iCapacity*sizeof(ElementType));
    }
    else
    {
      // we assume 'memcpy' suffices for moving the existing items.
      iArray = (ElementType*)PlatReAlloc(iArray,iCapacity*sizeof(ElementType));
    }
  }
public:
  inline void ResizeTo(SizeType aSize)
  {
    LISPASSERT(!iArrayOwnedExternally);
    TOps opers;
    if (aSize > iCapacity)
    {
      moreCapacity(aSize, opers.granularity());
    }
    if (!opers.isPOD())
    {
      if (iSize < aSize)
      {
        for (int ii = iSize; ii < aSize; ii++)
          opers.construct(iArray + ii);
      }
      else
      {
        for (int ii = aSize; ii < iSize; ii++)
          opers.destruct(iArray + ii);
      }
    }
    iSize = aSize;
  }
  void Delete(SizeType aIndex, SizeType aCount=1)
  {
    LISPASSERT(aIndex>=0 && aIndex<iSize);
    ArrOps::remove((char**)&iArray, TOps(), iSize, aIndex, aCount, sizeof(ElementType));
  }
  inline LispBoolean ArrayOwnedExternally()
  {
    return iArrayOwnedExternally;
  }
public:
  /// Access to an element in the array
  inline ElementType& operator[](const SizeType aIndex) const
  {
    return iArray[aIndex];
  }

    /// Append an element to an array
  template <class Type>
    inline void Append(const Type& aVal)
    {
      if (iSize >= iCapacity) moreCapacity(iSize+1, TOps().granularity());
      new ((void *)(iArray+iSize)) ElementType(aVal);
      ++iSize;
    }

  /// Insert object aObj at aIndex, aCount times.
  inline void Insert(SizeType aIndex, const ElementType& aObj, SizeType aCount=1)
  {
    const SizeType oldItems = iSize;
    LISPASSERT(aIndex <= oldItems && aCount >= 0);
    ResizeTo(iSize+aCount);
    ElementType * pOld = iArray+oldItems;
    ElementType * pEnd = iArray+iSize;
    int i = iSize - aIndex;  // = (oldItems - aIndex) + aCount
    for ( ; i > aCount; i--)
      *--pEnd = *--pOld;
    while (i-- > 0)
      *--pEnd = aObj;
  }

  /** Set the array to an external array. This means the array will
    * not be freed at destruction time
    */
  inline void SetExternalArray(ElementType* aArray, SizeType aSize)
  {
    LISPASSERT(!iArray || iArrayOwnedExternally == LispTrue);
    iArray = aArray;
    iSize = aSize;
    iArrayOwnedExternally = LispTrue;
    iCapacity = -10000;  // Setting iCapacity should not strictly be necessary, setting it to hugely negative number will hopefully force a fail.
  }

  /// Copy the array to another array
  inline void CopyToExternalArray(ElementType * aArray)
  {
    PlatMemCopy(aArray,iArray,iSize*sizeof(ElementType));
  }

protected:
  inline ElementType * elements() const { return iArray; }
private:
  ElementType * iArray;
  SizeType iSize;
  SizeType iCapacity;
  LispBoolean iArrayOwnedExternally;
};

/** \class CDeletingArrayGrower calls delete on each element in the
 * array at destruction time. This is useful if the array is a list
 * of pointers to objects.
 */

template <class T, class TOps >
class CDeletingArrayGrower : public CArrayGrower<T, TOps >
{
public:
  CDeletingArrayGrower() {}
};

#endif


