//////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2025 Autodesk, Inc.  All rights reserved.
//
//  Use of this software is subject to the terms of the Autodesk license 
//  agreement provided at the time of installation or download, or which 
//  otherwise accompanies this software in either electronic or hard copy form.   
//
//////////////////////////////////////////////////////////////////////////////

// DESCRIPTION: This file contains the definition for the AmiArray
//              template for holding arrays of reference counted objects.
//
// All reference counted objects in the MCAD API derive from the base
// class "AmiObjectBase". The array class, however, was designed as a
// template in order to provide the convenience of type checking in
// cases where, for example, you may want an array of object keys only.
// In that case, you would instance the array as in the following code:
//
//     AmiArray<AmiObjectKey> keyArray;
//
// NOTE that all objects of any type deriving from AmiObjectBase must
// always be allocated dynamically. Therefore the array class simply
// keeps an array of pointers to these objects. The objects themselves
// are NEVER copied when added to the array. Instead, only the pointers
// are copied and the object's reference count is implemented to reflect
// another holder (the array).
//
// The AmiArray class is a dynamic array meaning that the array can grow
// without bounds. In order to use the AmiArray, you need to understand
// a couple of key concepts:
//
//     1) The logical length of the array.
//            - How many objects have been placed into the array.
//     2) The physical length of the array.
//            - How many objects the array will hold before it
//              automatically "grows" larger.
//     3) The grow length of the array.
//            - How much the array will grow when required.
//
// The physical length of the array is the actual length of the physically
// allocated, but perhaps not fully used, array. As a point of clarification,
// the size in bytes of the array buffer for an array called "myArray" would
// be:
//
//    sizeOf(T*) * myArray.physicalLength()
//
// The logical length of the array (or just the "length()") reflects how
// many objects have been placed into the array with, for example, assign(),
// append(), or insert(). Many methods are only valid for indices that are
// greater than or equal to zero AND less than length(). For example,
// indexing into the array with the operator[] is only valid for indices in
// this range.
//
// You can explicitly set the logical length to any value and if the
// physical length is not large enough, the array will grow to that length.
// Note that if the logical length is explicitly reset to a larger value,
// then all the entries from the old length up to the new length will contain
// NULL pointers. This would be considered an invalid state and those slots
// must be initialized explicitly. Also, if the logical length is explicitly
// explicitly reset to a smaller value, the reference count on all objects
// that fall off the end is decremented.
//
// The logical length is always less than or equal to the physical length.
// NOTE that the array ALWAYS starts out empty, i.e. the logical length
// always starts at zero regardless of the initial physical length.
//
// If you add object(s) to the array causing the logical length to become
// greater than the physical length of the array, then the "grow length"
// determines how much additional space to allocate, and the physical
// length will increase by the grow length.
//
#ifndef AMIARRAY_H
#define AMIARRAY_H

///<summary>
///<para>All reference counted objects in the MCAD API derive from the base class "AmiObjectBase".
///The array class, however, was designed as a template in order to provide the convenience of type 
///checking in cases where, for example, you may want an array of object keys only.In that case, you
///would instance the array as in the following code:</para>
///<para></para>
///<para>NOTE that all objects of any type deriving from AmiObjectBase must always be allocated dynamically.
///Therefore the array class simply keeps an array of pointers to these objects. The objects themselves are
///NEVER copied when added to the array. Instead, only the pointers are copied and the object's reference count
///is implemented to reflect another holder (the array).</para>
///<para>The AmiArray class is a dynamic array meaning that the array can grow without bounds. In order to use 
///the AmiArray, you need to understand a couple of key concepts:</para>
///<para>1) The logical length of the array.</para>
///<para>- How many objects have been placed into the array.</para>
///<para>2) The physical length of the array.</para>
///<para>- How many objects the array will hold before it automatically "grows" larger.</para>
///<para>3) The grow length of the array.</para>
///<para>- How much the array will grow when required.</para>
///<para>The physical length of the array is the actual length of the physically allocated, but perhaps not fully 
///used, array. As a point of clarification, the size in bytes of the array buffer for an array called "myArray" 
///would be:</para>
///<para>sizeOf(T*) * myArray.physicalLength()</para>
///<para>The logical length of the array (or just the "length()") reflects how many objects have been placed into
///the array with, for example, assign(), append(), or insert(). Many methods are only valid for indices that are
///greater than or equal to zero AND less than length(). For example, indexing into the array with the operator[] 
///is only valid for indices in this range.</para>
///<para>You can explicitly set the logical length to any value and if the physical length is not large enough, the
///array will grow to that length. Note that if the logical length is explicitly reset to a larger value, then all 
///the entries from the old length up to the new length will contain NULL pointers. This would be considered an
///invalid state and those slots must be initialized explicitly. Also, if the logical length is explicitly explicitly
///reset to a smaller value, the reference count on all objects that fall off the end is decremented.</para>
///<para>The logical length is always less than or equal to the physical length. NOTE that the array ALWAYS starts
///out empty, i.e. the logical length always starts at zero regardless of the initial physical length.</para>
///<para>If you add object(s) to the array causing the logical length to become greater than the physical length of
///the array, then the "grow length" determines how much additional space to allocate, and the physical length will 
///increase by the grow length.</para>
///</summary>
template <class T> class AmiArray
{
  public:

    ///<summary>Construct a new array with the specified initial physical length and grow length.</summary>
    AmiArray(size_t initPhysicalLength = 0, size_t initGrowLength = 10);

    ///<summary>Construct a new array using n objects from the array ppObjs. The initial physical length will equal
    ///the number of objects and the grow length will be 10.</summary>
    AmiArray(size_t n, T** ppObjs);

    ///<summary>Construct a new array using the data from the array rArray.</summary>
    AmiArray(const AmiArray<T>& rArray);

    ///<summary>Array destructor.</summary>
   ~AmiArray();

    AmiArray<T>& operator=(const AmiArray<T>& rArray);

    AmiArray<T>& operator+=(const AmiArray<T>& rArray);

    AmiArray<T>& operator+=(T* pObj);

    const T* operator[](size_t idx) const;
          T* operator[](size_t idx);

    ///<summary>Return a pointer to the object at an index and also increment the object reference count.</summary>
    T* getRef(size_t idx) const;

    ///<summary>Replace this array's contents with n objects from the array ppObjs.</summary>
    AmiArray<T>& assign(size_t n, T** ppObjs);

    ///<summary>Replace this array's contents with the objects from the array rArray.</summary>
    AmiArray<T>& assign(const AmiArray<T>& rArray);

    ///<summary>Replace this array's contents with the single object pObj.</summary>
    AmiArray<T>& assign(T* pObj);

    ///<summary>Replace the object at index idx with the single object pObj. Note that the replace object's reference 
    ///count is decremented.</summary>
    AmiArray<T>& assign(size_t idx, T* pObj);

    ///<summary>Append n objects from the array ppObjs onto the end of this array.</summary>
    AmiArray<T>& append(size_t n, T** ppObjs);

    ///<summary>Append the objects from the array rArray onto the end of this array.</summary>
    AmiArray<T>& append(const AmiArray<T>& rArray);

    ///<summary>Append the single object pObj onto the end of this array.</summary>
    AmiArray<T>& append(T* pObj);

    ///<summary>Insert n objects from the array ppObjs into this array at index idx.</summary>
    AmiArray<T>& insert(size_t idx, size_t n, T** ppObjs);

    ///<summary>Insert the objects from the array rArray into this array at index idx.</summary>
    AmiArray<T>& insert(size_t idx, const AmiArray<T>& rArray);

    ///<summary>Insert the object pObj into this array at index idx.</summary>
    AmiArray<T>& insert(size_t idx, T* pObj);

    ///<summary>Remove n objects from this array at index idx.</summary>
    AmiArray<T>& remove(size_t idx, size_t n = 1);

    ///<summary>Remove the first object from this array.</summary>
    AmiArray<T>& removeFirst();

    ///<summary>Remove the last object from this array.</summary>
    AmiArray<T>& removeLast();

    ///<summary>Return the logical length of this array.</summary>
    size_t length() const;

    ///<summary>Return true if this array is currently empty.</summary>
    bool isEmpty() const;

    ///<summary>Return the logical length of this array.</summary>
    size_t logicalLength() const;

    ///<summary>Set the logical length of this array. If the new logical length is smaller than the current logical 
    ///length, the reference count of the objects that fall off the end is decremented.</summary>
    AmiArray<T>& setLogicalLength(size_t n);

    ///<summary>Return the physical length of this array.</summary>
    size_t physicalLength() const;

    ///<summary>Set the physical length of this array. If the new physical length is smaller than the current physical
    ///length, the reference count of the objects that fall off the end is decremented.</summary>
    AmiArray<T>& setPhysicalLength(size_t n);

    ///<summary>Return the current grow length of this array.</summary>
    size_t growLength() const;

    ///<summary>Set the grow length of this array.</summary>
    AmiArray<T>& setGrowLength(size_t n);

    ///<summary>Replace this array's contents with the array ppObjs. This method uses the specified array "as is" 
    ///without allocating new storage. The logical and physical length are both set equal to the number of objects.</summary>
    AmiArray<T>& attach(size_t n, T** ppObjs);

    ///<summary>Detach the array of objects returning the pointer to the internal array. The current array is then 
    ///cleared leaving the caller with the responsibility of cleanup.</summary>
    void detach(size_t& n, T**& ppObjs);

    T const* const* getArrayPtr() const;

  private:


    void clear(bool fClearPhysical);

    size_t mPhysicalLen;                // Actual array size.
    size_t mGrowLen;                    // Array grow length.
    size_t mLogicalLen;                 // Number of items in the array.
    T    **mppObjs;                     // Array of object pointers.
};

template <class T> inline
AmiArray<T>::AmiArray(size_t initPhysicalLength, size_t initGrowLength)
  : mPhysicalLen(initPhysicalLength),
    mGrowLen    (initGrowLength),
    mLogicalLen (0),
    mppObjs     (NULL)
{}

template <class T> inline
AmiArray<T>::AmiArray(size_t n, T** ppObjs)
  : mPhysicalLen(0),
    mGrowLen    (10),
    mLogicalLen (0),
    mppObjs     (NULL)
{ assign(n, ppObjs); }

template <class T> inline
AmiArray<T>::AmiArray(const AmiArray<T>& rArray)
  : mPhysicalLen(0),
    mGrowLen    (rArray.mGrowLen),
    mLogicalLen (0),
    mppObjs     (NULL)
{ assign(rArray.mLogicalLen, rArray.mppObjs); }

template <class T> inline
AmiArray<T>::~AmiArray()
{ clear(true); }

template <class T> inline
AmiArray<T>& AmiArray<T>::operator=(const AmiArray<T>& rArray)
{ return assign(rArray); }

template <class T> inline
AmiArray<T>& AmiArray<T>::operator+=(const AmiArray<T>& rArray)
{ return append(rArray); }

template <class T> inline
AmiArray<T>& AmiArray<T>::operator+=(T* pObj)
{ return append(pObj); }

template <class T> inline
const T* AmiArray<T>::operator[](size_t idx) const
{ assert(idx < mLogicalLen); return (idx < mLogicalLen) ? mppObjs[idx] : NULL; }

template <class T> inline
T* AmiArray<T>::operator[](size_t idx)
{ assert(idx < mLogicalLen); return (idx < mLogicalLen) ? mppObjs[idx] : NULL; }

template <class T> inline
T* AmiArray<T>::getRef(size_t idx) const
{
  assert(idx < mLogicalLen);

  T* pObj = (idx < mLogicalLen) ? mppObjs[idx] : NULL;
  if (pObj)
    pObj->addRef();

  return pObj;
}

template <class T> inline
AmiArray<T>& AmiArray<T>::assign(const AmiArray<T>& rArray)
{ return assign(rArray.mLogicalLen, rArray.mppObjs); }

template <class T> inline
AmiArray<T>& AmiArray<T>::assign(T* pObj)
{ return assign(1, &pObj); }

template <class T> inline
AmiArray<T>& AmiArray<T>::append(const AmiArray<T>& rArray)
{ return append(rArray.mLogicalLen, rArray.mppObjs); }

template <class T> inline
AmiArray<T>& AmiArray<T>::append(T* pObj)
{ return append(1, &pObj); }

template <class T> inline
AmiArray<T>& AmiArray<T>::insert(size_t idx, const AmiArray<T>& rArray)
{ return insert(idx, rArray.mLogicalLen, rArray.mppObjs); }

template <class T> inline
AmiArray<T>& AmiArray<T>::insert(size_t idx, T* pObj)
{ return insert(idx, 1, &pObj); }

template <class T> inline
AmiArray<T>& AmiArray<T>::removeFirst()
{ return remove(0, 1); }

template <class T> inline
AmiArray<T>& AmiArray<T>::removeLast()
{ return remove(mLogicalLen-1, 1); }

template <class T> inline
size_t AmiArray<T>::length() const
{ return mLogicalLen; }

template <class T> inline
bool AmiArray<T>::isEmpty() const
{ return (mLogicalLen == 0) ? true : false; }

template <class T> inline
size_t AmiArray<T>::logicalLength() const
{ return mLogicalLen; }

template <class T> inline
size_t AmiArray<T>::physicalLength() const
{ return mPhysicalLen; }

template <class T> inline
size_t AmiArray<T>::growLength() const
{ return mGrowLen; }

template <class T> inline
AmiArray<T>& AmiArray<T>::setGrowLength(size_t n)
{ assert(n > 0); mGrowLen = n; return *this; }

template <class T> inline
T const* const* AmiArray<T>::getArrayPtr() const
{ return mppObjs; }

template <class T>
AmiArray<T>& AmiArray<T>::assign(size_t n, T** ppObjs)
{
  clear(false);

  setLogicalLength(n);

  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[ii] = ppObjs[ii];
    if (mppObjs[ii])
      mppObjs[ii]->addRef();
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::assign(size_t idx, T* pObj)
{
  assert(idx < mLogicalLen);

  if (idx < mLogicalLen)
  {
    if (mppObjs[idx])
      mppObjs[idx]->release();

    mppObjs[idx] = pObj;

    if (mppObjs[idx])
      mppObjs[idx]->addRef();
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::append(size_t n, T** ppObjs)
{
  size_t oldLen = mLogicalLen;
  setLogicalLength(oldLen + n);

  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[oldLen + ii] = ppObjs[ii];
    if (ppObjs[ii])
      ppObjs[ii]->addRef();
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::insert(size_t idx, size_t n, T** ppObjs)
{
  assert(idx <= mLogicalLen);

  size_t oldLen = mLogicalLen;
  setLogicalLength(oldLen + n);

  if (idx < oldLen)
  {
    size_t sz = (oldLen - idx) * sizeof(T*);
    memmove(&(mppObjs[idx+n]), &(mppObjs[idx]), sz);
  }

  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[idx + ii] = ppObjs[ii];
    if (ppObjs[ii])
      ppObjs[ii]->addRef();
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::remove(size_t idx, size_t n)
{
  assert(n > 0);
  assert(idx <= mLogicalLen);
  assert(n <= (mLogicalLen - idx));

  if (n && idx <= mLogicalLen && n <= (mLogicalLen - idx))
  {
    size_t uldx = idx + n;

    for (size_t ii = idx; ii < uldx; ii++)
      if (mppObjs[ii])
        mppObjs[ii]->release();

    size_t sz = (mLogicalLen - uldx) * sizeof(T*);
    memmove(&(mppObjs[idx]), &(mppObjs[uldx]), sz);

    mLogicalLen -= n;
    memset(&(mppObjs[mLogicalLen]), 0, n * sizeof(T*));
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::setLogicalLength(size_t n)
{
  if (n > mPhysicalLen)
  {
    size_t minSize = mPhysicalLen + mGrowLen;
    if (n > minSize)
      minSize = n;

    setPhysicalLength(minSize);
  }

  else if (n < mLogicalLen)
  {
    for (size_t ii = n; ii < mLogicalLen; ii++)
    {
      if (mppObjs[ii])
        mppObjs[ii]->release();
      mppObjs[ii] = NULL;
    }
  }

  mLogicalLen = n;
  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::setPhysicalLength(size_t n)
{
  if (n == 0)
    clear(true);

  else
  {
    T **ppObjs = new T*[n];
    memset(ppObjs, 0, n * sizeof(T*));

    if (n < mLogicalLen)
    {
      for (size_t ii = n; ii < mLogicalLen; ii++)
        if (mppObjs[ii])
          mppObjs[ii]->release();

      mLogicalLen = n;
    }

    if (mLogicalLen)
      memcpy_s(ppObjs, n*sizeof(T*), mppObjs, mLogicalLen * sizeof(T*));

    delete[] mppObjs;
    mppObjs      = ppObjs;
    mPhysicalLen = n;
  }

  return *this;
}

template <class T>
AmiArray<T>& AmiArray<T>::attach(size_t n, T** ppObjs)
{
  clear(true);

  mPhysicalLen = n;
  mLogicalLen  = n;
  mppObjs      = ppObjs;

  return *this;
}

template <class T>
void AmiArray<T>::detach(size_t& n, T**& ppObjs)
{
  ppObjs = mppObjs;
  n      = mLogicalLen;

  mPhysicalLen = 0;
  mLogicalLen  = 0;
  mppObjs      = NULL;
}

template <class T>
void AmiArray<T>::clear(bool fClearPhysical)
{
  for (size_t ii = 0; ii < mLogicalLen; ii++)
  {
    if (mppObjs[ii])
    {
      mppObjs[ii]->release();
      mppObjs[ii] = NULL;
    }
  }

  mLogicalLen = 0;

  if (fClearPhysical)
  {
    delete[] mppObjs;

    mPhysicalLen = 0;
    mppObjs      = NULL;
  }
}

#endif    // AMIARRAY_H
