//
//  (C) Copyright 1999-2008 by Autodesk, Inc.  All rights reserved.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted, 
// provided that the above copyright notice appears in all copies and 
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting 
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC. 
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to 
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

// 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

template <class T> class AmiArray
{
  public:

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

    // 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.
    //
    AmiArray(size_t n, T** ppObjs);

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

    // Array destructor.
    //
   ~AmiArray();

    // Array assignment operator.
    //
    AmiArray<T>& operator=(const AmiArray<T>& rArray);

    // Append operator adds the objects from rArray to the end of this
    // array.
    //
    AmiArray<T>& operator+=(const AmiArray<T>& rArray);

    // Append operator adds the single oject pObj to the end of this array.
    //
    AmiArray<T>& operator+=(T* pObj);

    // Indexing operators returns a pointer to the object at an index.
    //
    // NOTE: These operators DO NOT increment the object reference
    //       count so use them carefully. The array is still holding
    //       a pointer to the same object so care must be taken to
    //       increment the reference count after using one of these
    //       operators but ONLY IF you plan on doing a "release" yourself.
    //
    const T* operator[](size_t idx) const;
          T* operator[](size_t idx);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // 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.
    //
    AmiArray<T>& setLogicalLength(size_t n);

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

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

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

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

    // 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.
    //
    AmiArray<T>& attach(size_t n, T** ppObjs);

    // 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.
    //
    void detach(size_t& n, T**& ppObjs);

    // Return a constant pointer to the internal array.
    //
    T const* const* getArrayPtr() const;

  private:

    // Clear the contents of this array decrementing all object reference
    // counts. If the clearPhysical flag is true, the physical array is
    // also deleted and the physical length is set to zero.
    //
    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.
};

// Construct a new array with the specified initial physical length
// and grow length.
//
template <class T> inline
AmiArray<T>::AmiArray(size_t initPhysicalLength, size_t initGrowLength)
  : mPhysicalLen(initPhysicalLength),
    mGrowLen    (initGrowLength),
    mLogicalLen (0),
    mppObjs     (NULL)
{}

// 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.
//
template <class T> inline
AmiArray<T>::AmiArray(size_t n, T** ppObjs)
  : mPhysicalLen(0),
    mGrowLen    (10),
    mLogicalLen (0),
    mppObjs     (NULL)
{ assign(n, ppObjs); }

// Construct a new array using the data from the array rArray.
//
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); }

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

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

// Append operator adds the objects from rArray to the end of this
// array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::operator+=(const AmiArray<T>& rArray)
{ return append(rArray); }

// Append operator adds the single oject pObj to the end of this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::operator+=(T* pObj)
{ return append(pObj); }

// Indexing operator returns a constant pointer to the object at an index.
//
template <class T> inline
const T* AmiArray<T>::operator[](size_t idx) const
{ assert(idx < mLogicalLen); return (idx < mLogicalLen) ? mppObjs[idx] : NULL; }

// Indexing operator returns a pointer to the object at an index.
//
template <class T> inline
T* AmiArray<T>::operator[](size_t idx)
{ assert(idx < mLogicalLen); return (idx < mLogicalLen) ? mppObjs[idx] : NULL; }

// Return a pointer to the object at an index and also increment
// the object reference count.
//
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;
}

// Replace this array's contents with the objects from the array rArray.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::assign(const AmiArray<T>& rArray)
{ return assign(rArray.mLogicalLen, rArray.mppObjs); }

// Replace this array's contents with the single object pObj.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::assign(T* pObj)
{ return assign(1, &pObj); }

// Append the objects from the array rArray onto the end of this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::append(const AmiArray<T>& rArray)
{ return append(rArray.mLogicalLen, rArray.mppObjs); }

// Append the single object pObj onto the end of this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::append(T* pObj)
{ return append(1, &pObj); }

// Insert the objects from the array rArray into this array at index idx.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::insert(size_t idx, const AmiArray<T>& rArray)
{ return insert(idx, rArray.mLogicalLen, rArray.mppObjs); }

// Insert the object pObj into this array at index idx.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::insert(size_t idx, T* pObj)
{ return insert(idx, 1, &pObj); }

// Remove the first object from this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::removeFirst()
{ return remove(0, 1); }

// Remove the last object from this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::removeLast()
{ return remove(mLogicalLen-1, 1); }

// Return the logical length of this array.
//
template <class T> inline
size_t AmiArray<T>::length() const
{ return mLogicalLen; }

// Return true if this array is currently empty.
//
template <class T> inline
bool AmiArray<T>::isEmpty() const
{ return (mLogicalLen == 0) ? true : false; }

// Return the logical length of this array.
//
template <class T> inline
size_t AmiArray<T>::logicalLength() const
{ return mLogicalLen; }

// Return the physical length of this array.
//
template <class T> inline
size_t AmiArray<T>::physicalLength() const
{ return mPhysicalLen; }

// Return the current grow length of this array.
//
template <class T> inline
size_t AmiArray<T>::growLength() const
{ return mGrowLen; }

// Set the grow length of this array.
//
template <class T> inline
AmiArray<T>& AmiArray<T>::setGrowLength(size_t n)
{ assert(n > 0); mGrowLen = n; return *this; }

// Return a constant pointer to the internal array.
//
template <class T> inline
T const* const* AmiArray<T>::getArrayPtr() const
{ return mppObjs; }

// Replace this array's contents with n objects from the array ppObjs.
//
template <class T>
AmiArray<T>& AmiArray<T>::assign(size_t n, T** ppObjs)
{
  // Clear out the current array contents but leave the allocated
  // array alone for reuse.
  //
  clear(false);

  // Set the logical length. Note that this call will take care of
  // increasing the physical length if needed.
  //
  setLogicalLength(n);

  // Cycle through the input array of objects assigning each to the
  // array and incrementing the object reference count.
  //
  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[ii] = ppObjs[ii];
    if (mppObjs[ii])
      mppObjs[ii]->addRef();
  }

  return *this;
}

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

  // Make sure the index is within range.
  //
  if (idx < mLogicalLen)
  {
    // Decrement the reference count on the object being replaced.
    //
    if (mppObjs[idx])
      mppObjs[idx]->release();

    // Replace the object.
    //
    mppObjs[idx] = pObj;

    // Increment the reference count on the new object.
    //
    if (mppObjs[idx])
      mppObjs[idx]->addRef();
  }

  return *this;
}

// Append n objects from the array ppObjs onto the end of this array.
//
template <class T>
AmiArray<T>& AmiArray<T>::append(size_t n, T** ppObjs)
{
  // Set the new logical length to make room for the addition. Note
  // that this call will take care of increasing the physical length
  // if needed.
  //
  size_t oldLen = mLogicalLen;
  setLogicalLength(oldLen + n);

  // Cycle through the input array of objects appending each to the
  // array and incrementing the object reference count.
  //
  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[oldLen + ii] = ppObjs[ii];
    if (ppObjs[ii])
      ppObjs[ii]->addRef();
  }

  return *this;
}

// Insert n objects from the array ppObjs into this array at index idx.
//
template <class T>
AmiArray<T>& AmiArray<T>::insert(size_t idx, size_t n, T** ppObjs)
{
  assert(idx <= mLogicalLen);

  // Set the new logical length to make room for the addition. Note
  // that this call will take care of increasing the physical length
  // if needed.
  //
  size_t oldLen = mLogicalLen;
  setLogicalLength(oldLen + n);

  // If the index parameter is a valid index within the bounds of
  // the array size, then we are making room in the middle. If that's
  // the case, then shift the object pointers from that point to the
  // right by n slots.
  //
  if (idx < oldLen)
  {
    size_t sz = (oldLen - idx) * sizeof(T*);
    memmove(&(mppObjs[idx+n]), &(mppObjs[idx]), sz);
  }

  // Cycle through the input array of objects inserting each to the
  // array and incrementing the object reference count.
  //
  for (size_t ii = 0; ii < n; ii++)
  {
    mppObjs[idx + ii] = ppObjs[ii];
    if (ppObjs[ii])
      ppObjs[ii]->addRef();
  }

  return *this;
}

// Remove n objects from this array at index idx.
//
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;

    // Decrement the reference counts on the objects about to be
    // removed.
    //
    for (size_t ii = idx; ii < uldx; ii++)
      if (mppObjs[ii])
        mppObjs[ii]->release();

    // Slide the remaining objects over.
    //
    size_t sz = (mLogicalLen - uldx) * sizeof(T*);
    memmove(&(mppObjs[idx]), &(mppObjs[uldx]), sz);

    // Reset the logical length and clear the slots left on the end.
    //
    mLogicalLen -= n;
    memset(&(mppObjs[mLogicalLen]), 0, n * sizeof(T*));
  }

  return *this;
}

// 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.
//
template <class T>
AmiArray<T>& AmiArray<T>::setLogicalLength(size_t n)
{
  // If the new logical length is greater than the current physical
  // length, than the physical length needs to be increased.
  //
  if (n > mPhysicalLen)
  {
    size_t minSize = mPhysicalLen + mGrowLen;
    if (n > minSize)
      minSize = n;

    setPhysicalLength(minSize);
  }

  // If the new logical length is less than the current logical length,
  // then the reference counts on the remaining objects need decrementing
  // and the slots need clearing.
  //
  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;
}

// Set the physical length of this array. If the new physical length
// is smaller than the current logical length, the reference count
// of the objects that fall off the end is decremented.
//
template <class T>
AmiArray<T>& AmiArray<T>::setPhysicalLength(size_t n)
{
  if (n == 0)
    clear(true);

  else
  {
    // Allocate a new object pointer array and zero it out.
    //
    T **ppObjs = new T*[n];
    memset(ppObjs, 0, n * sizeof(T*));

    // If the new size is less than the old size, cycle through
    // the objects that are about to fall off and decrement their
    // reference counts.
    //
    if (n < mLogicalLen)
    {
      for (size_t ii = n; ii < mLogicalLen; ii++)
        if (mppObjs[ii])
          mppObjs[ii]->release();

      mLogicalLen = n;
    }

    // Copy the original set of objects from the old array into
    // the new array.
    //
    if (mLogicalLen)
      memcpy(ppObjs, mppObjs, mLogicalLen * sizeof(T*));

    // Ditch the old array and the new one in it's place.
    //
    delete[] mppObjs;
    mppObjs      = ppObjs;
    mPhysicalLen = n;
  }

  return *this;
}

// 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.
//
template <class T>
AmiArray<T>& AmiArray<T>::attach(size_t n, T** ppObjs)
{
  clear(true);

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

  return *this;
}

// 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.
//
template <class T>
void AmiArray<T>::detach(size_t& n, T**& ppObjs)
{
  ppObjs = mppObjs;
  n      = mLogicalLen;

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

// Clear the contents of this array decrementing all object reference
// counts. If the clearPhysical flag is true, the physical array is
// also deleted and the physical length is set to zero.
//
template <class T>
void AmiArray<T>::clear(bool fClearPhysical)
{
  // Cycle through all of the objects, decrement each object's reference
  // count, and clear the slot.
  //
  for (size_t ii = 0; ii < mLogicalLen; ii++)
  {
    if (mppObjs[ii])
    {
      mppObjs[ii]->release();
      mppObjs[ii] = NULL;
    }
  }

  // Zero out the logical length.
  //
  mLogicalLen = 0;

  // If the clear physical flag is true, delete the physical array and
  // set the physical length to zero.
  //
  if (fClearPhysical)
  {
    delete[] mppObjs;

    mPhysicalLen = 0;
    mppObjs      = NULL;
  }
}

#endif    // AMIARRAY_H
