/*!
 * \file    OMS_ArrayObject.hpp
 * \author  IvanS
 * \brief   Array object.
 */

/*

    ========== licence begin  GPL
    Copyright (c) 2003-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end



*/

#ifndef __OMS_ARRAYOBJECT_HPP
#define __OMS_ARRAYOBJECT_HPP

#include "Oms/OMS_Object.hpp"

#include <memory.h>

static const size_t OMS_ARROBJ_OPTSIZECNT = 31;

/**
 * \brief Base for array objects.
 *
 * This class is the base for all array object classes.
 *
 * \see OmsArrayObject for more information.
 */
class AFX_EXT_CLASS OmsArrayObjectBase {
public:
  /*!
   * \return object size for given class (0..31)
   */
  static size_t omsGetClassSize(size_t sizeClass);

  /*!
   * \return class index (0..31) for given byte size
   */
  static size_t omsGetSizeClass(size_t byteSize);

  static void omsRegisterArray(OmsHandle &h, int ClsGuid, OmsSchemaHandle sh,
    OmsContainerNo contId, size_t realSize, size_t capacity,
    const char *typname, OmsAbstractObject *regobj);

  static void ThrowInvalidParam(const char *pFile, unsigned int line);
};

template <class T, int ClsGuid>
class OmsArrayObject;

/**
 * \brief Base for array object iterator.
 *
 * Each array object size iterator is derived from this class.
 * This class provides common methods for all iterators and
 * base (untyped) methods.
 *
 * \see OmsArrayObjectIterator for more information.
 */
class AFX_EXT_CLASS OmsArrayObjectIteratorBase {
protected:
  OmsArrayObjectIteratorBase(OmsHandle &_h, int _guid, OmsSchemaHandle _sh,
    OmsContainerNo _cno)
    : h(_h), guid(_guid), sh(_sh), cno(_cno), rcn(0)
  {
    omsNext();
  }
public:
  /// Return true, iff there are some more registered sizes.
  inline operator bool() const
  {
    return rcn != 0;
  }

  /// Return true, iff there are no more registered sizes.
  inline bool operator!() const
  {
    return rcn == 0;
  }

  /// Get current size class.
  size_t omsGetSizeClass()
  {
    return rcn >> 20;
  }

  /// Drop container with current size class.
  void omsDropContainer();

  /// Delete all objects with current size class.
  void omsDeleteAll();

  /// Move to the next size class.
  void omsNext();

  /// Move to the next size class.
  inline void operator++()
  {
    omsNext();
  }

protected:
  /// Get iterator over current size class.
  OmsObjByClsIterBase omsAllOids(int maxBufferSize = 20);

  OmsHandle       &h;         ///< OMS handle
  int             guid;       ///< Object GUID
  OmsSchemaHandle sh;         ///< Schema
  OmsContainerNo  cno;        ///< Logical container number.
  OmsContainerNo  rcn;        ///< Real container number for current size.
  size_t          szclass;    ///< Current size class.
};

/*!
 * \brief OmsArrayObject size iterator.
 *
 * This class enables iterating over all sizes in an array object container.
 * It is not possible to iterate over all objects in OmsArrayObject container
 * directly, since the objects are spread over more physical containers.
 * The same holds for some other functions, like omsDropContainer or
 * omsDeleteAll.
 *
 * To iterate over all objects in an OmsArrayObject container, use the
 * following construct:
 * \code
 *  OmsArrayObjectIterator<MyElement, MyGuid> iter =
 *    OmsArrayObject<MyElement, MyGuid>::omsGetSizesIterator(h, schema, cno);
 *  while (iter) {
 *    OmsObjByClsIter<OmsArrayObject<MyElement, MyGuid> > i = iter.omsAllOids();
 *    while (i) {
 *      // process the object
 *      ++i;
 *    }
 *    ++iter;
 *  }
 * \endcode
 *
 * Similarly, to drop all physical containers in given logical container
 * (effectively dropping the logical container) you can do the following:
 * \code
 *  OmsArrayObjectIterator<MyElement, MyGuid> iter =
 *    OmsArrayObject<MyElement, MyGuid>::omsGetSizesIterator(h, schema, cno);
 *  while (iter) {
 *    iter.omsDropContainer();
 *    ++iter;
 *  }
 * \endcode
 *
 * Deleting all objects with omsDeleteAll() is analogous.
 *
 * The iterator returns also the current size class capacity with
 * omsGetCapacity() and the current size class with omsGetSizeClass().
 * You can use OmsArrayObjectBase::omsGetClassSize to map the size
 * class to real byte size.
 *
 * \see OmsArrayObject
 * \since 7.4.3.30, 7.5.0.2
 */
template<class T, int ClsGuid>
class OmsArrayObjectIterator : public OmsArrayObjectIteratorBase {
public:
  /// Object type.
  typedef OmsArrayObject<T, ClsGuid>  ObjectType;

  /// Construct new iterator over sizes.
  OmsArrayObjectIterator(OmsHandle &h, OmsSchemaHandle sh, OmsContainerNo cn)
    : OmsArrayObjectIteratorBase(h, ClsGuid, sh, cn)
  {
  }

  /// Get iterator over current size class.
  inline OmsObjByClsIter<ObjectType> omsAllOids(int maxBufferSize = 20)
  {
    OmsObjByClsIterBase iter = OmsArrayObjectIteratorBase::omsAllOids(maxBufferSize);
    return *REINTERPRET_CAST(OmsObjByClsIter<ObjectType>*, &iter);
  }

  /// Get capacity of current size class.
  inline size_t omsGetCapacity()
  {
    return (OmsArrayObjectBase::omsGetClassSize(rcn >> 20) - sizeof(size_t) - sizeof(void*)) / sizeof(T);
  }
};

/*!
 * \brief Array object.
 *
 * This object type allows storing \c capa elements of equal type T, where
 * \c n can be specified at container registration time.
 *
 * \param T array element type (must provide default constructor and may
 *  not have a vtable, i.e., no virtual functions are allowed),
 * \param ClsGuid GUID of the class.
 *
 * The main restriction is that all elements of equal size \c n are stored
 * in the SAME container where the container number reflects the byte size
 * of the object array. Before creation of an object of size \c capa, the size
 * has to be registered to create a corresponding container \c (cn,capa) with
 *
 * \code
 * OmsArrayObject<T,ClsGuid>::omsRegisterSize(h, sh, cn, capa);
 * \endcode
 *
 * After restart, the container has to be re-registered exactly like any other
 * object container (e.g., in COM-object Register method). However, since you
 * don't want to register all sizes, you can use special registration method
 * omsRegisterExisting() to register only existing sizes.
 *
 * After creation of the container, the actual container number can be obtained
 * as follows:
 *
 * \code
 * OmsContainerNo realcn = OmsArrayObject<T,ClsGuid>::omsGetContainerNo(cn, capa);
 * \endcode
 *
 * Example of creating new array object in this container:
 *
 * \code
 * OmsArrayObject<T,ClsGuid>* p_array = new(h, sh, cn) OmsArrayObject<T,ClsGuid>(h, capa);
 * \endcode
 *
 * Optimal sizes for objects (including vtable pointer and size_t for element count) are:
 * 16, 24, 32, 40, 56, 72, 88, 104, 120, 144, 168, 192, 216, 248, 280, 328, 376, 424, 480,
 * 552, 648, 784, 872, 984, 1128, 1320, 1592, 2000, 2672, 4024, 8076.
 *
 * \note In order not to include conflicting header <new>, new instances of T are simply
 * zero-initialized.
 *
 * \see OmsArrayObjectIterator
 * \since 7.4.3.30, 7.5.0.2
 */
template <class T, int ClsGuid>
class OmsArrayObject : public OmsObject<OmsArrayObject<T, ClsGuid>, ClsGuid>
{
public:
  /// Array member type.
  typedef T MemberType;

  /// Object type.
  typedef OmsArrayObject<T, ClsGuid>  ObjectType;

  /// Object size class iterator type.
  typedef OmsArrayObjectIterator<T, ClsGuid>  Iterator;

  /*!
   * \brief Constructor
   *
   * Initialize all array elements using default constructor of T.
   *
   * \note In order not to include conflicting header <new>, new instances of T are simply
   * zero-initialized.
   */
  OmsArrayObject(OmsHandle& h, size_t n)
  {
    if (n > omsGetCapacity(h))
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    m_size = n;
    omsConstructT(m_data, m_size);
  }

  /*!
   * \return reference to i-th array element.
   */
  T& operator[](int idx)
  {
    if (size_t(idx) >= m_size)
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    return m_data[idx];
  }

  /*!
   * \return const reference to i-th array element
   */
  const T& operator[](int idx) const
  {
    if (size_t(idx) >= m_size)
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    return m_data[idx];
  }

  /*!
   * \brief Fill the array.
   *
   * Fill the first \c n array elements reading from \c p. If the array
   * is bigger than \c n, the remaining elements are untouched.
   *
   * \param p pointer to array with elements to fill in,
   * \param n count of elements to fill in.
   */
  void omsFill(const T* p, size_t n) 
  {
    if (n > m_size)
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    T* p_ = m_data;
    for (int i = 0; i < int(n); ++i)
    { *p_ = *p; ++p_; ++p; }
  }

  /*!
   * \brief Fill the array with memcpy.
   *
   * Fill the first \c n array elements reading from \c p. If the array
   * is bigger than \c n, the remaining elements are untouched.
   *
   * This version uses memcpy() instead of assignment operator.
   *
   * \param p pointer to array with elements to fill in,
   * \param n count of elements to fill in.
   */
  void omsFillMemcpy(const T* p, size_t n) 
  {
    if (n > m_size)
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    if (n > 0)
      memcpy(m_data, p, sizeof(T) * n);
  }

  /*!
   * \brief Get pointer to the first element
   *
   * \return const pointer to the first array element.
   */
  const T* omsBegin() const
  { 
    return m_data; 
  }

  /*!
   * \brief Get array size
   *
   * \return the size of the array (number of possible entries).
   */
  size_t omsGetSize() const
  { 
    return m_size;
  }

  /*!
   * \return real capacity of this object.
   */
  size_t omsGetCapacity(OmsHandle &h)
  {
    return (OmsArrayObjectBase::omsGetClassSize(h.omsGetContainerNo(this->omsGetOid()) >> 20)
      - sizeof(size_t) - sizeof(void*)) / sizeof(T);
  }

  /*!
   * \return optimal real capacity for given minimum capacity.
   */
  static size_t omsGetCapacity(size_t capacity)
  {
    return (OmsArrayObjectBase::omsGetClassSize(OmsArrayObjectBase::omsGetSizeClass(omsGetByteSize(capacity))) 
      - sizeof(size_t) - sizeof(void*)) / sizeof(T);
  }

  /*!
   * \brief Return maximal possible capacity for this array object type.
   */
  static size_t omsGetMaxCapacity()
  {
    return (OmsArrayObjectBase::omsGetClassSize(OMS_ARROBJ_OPTSIZECNT - 1) - sizeof(size_t) -
        sizeof(void*)) / sizeof(T);
  }

  /*!
   * \brief Resize the object array.
   *
   * This method allows resizing existing array in object in the range 0 to
   * omsGetCapacity(). Newly added objects are default initialized.
   *
   * \param h OMS handle,
   * \param newSize new size of the array.
   *
   * \note In order not to include conflicting header <new>, new instances of T are simply
   * zero-initialized.
   */
  void omsResize(OmsHandle &h, size_t newSize)
  {
    if (newSize > omsGetCapacity(h))
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    if (m_size < newSize) {
      omsConstructT(m_data + m_size, newSize - m_size);
    }
    m_size = newSize;
  }

  /*!
   * \return real container ID that takes OmsArrayObject with the given capacity.
   */
  static OmsContainerNo omsGetContainerNo(OmsContainerNo cno, size_t capacity) 
  {
    if (cno > 1<<20) {
      OmsArrayObjectBase::ThrowInvalidParam((const char*)__FILE__, __LINE__);
    }
    return OmsContainerNo((OmsArrayObjectBase::omsGetSizeClass(omsGetByteSize(capacity)) << 20) | cno);
  }

  /*!
   * \brief Register container for given capacity.
   *
   * Registers the container omsGetContainerNo(capacity) for an OmsArrayObject
   * of the given capacity.
   *
   * \param h OMS handle,
   * \param sh schema handle,
   * \param cn container number (base),
   * \param capacity required array capacity,
   * \param typname type name (for display in LC10).
   */
  static void omsRegisterSize(OmsHandle& h, OmsSchemaHandle sh, OmsContainerNo cn,
    size_t capacity, const char *typname)
  {
    // register only if container not yet registered
    if (!omsIsRegistered(h, sh, cn, capacity)) {
      OmsContainerNo contId = omsGetContainerNo(cn, capacity);
      size_t realSize = OmsArrayObjectBase::omsGetClassSize(contId >> 20);
      OmsArrayObjectBase::omsRegisterArray(h, ClsGuid, sh, contId, realSize,
        (realSize - sizeof(size_t) - sizeof(void*)) / sizeof(T), typname,
        new(h, OmsAbstractObject::USE_IN_REGISTRY_ONLY, realSize)
        OmsArrayObject<T,ClsGuid>);
    }
  }

  /*!
   * \brief Check if the container is registered.
   *
   * \param h OMS handle,
   * \param sh schema handle,
   * \param cn container number (base),
   * \param capacity required array capacity.
   * \return true iff omsRegisterSize(h, sh, cn, capacity) was already called
   *  in this invocation.
   */
  static bool omsIsRegistered(OmsHandle& h, OmsSchemaHandle sh, OmsContainerNo cn, size_t capacity) 
  {
    OmsContainerNo rcn = omsGetContainerNo(cn, capacity);
    return h.omsIsRegistered(ClsGuid, sh, rcn);
  }

  /*!
   * \brief Get container existence.
   *
   * \param h OMS handle,
   * \param sh schema handle,
   * \param cn container number (base),
   * \param capacity required array capacity.
   * \return true iff omsRegisterSize(h, sh, cn, capacity) was already called
   *  (also in past invocations).
   */
  static bool omsExistsContainer(OmsHandle &h, OmsSchemaHandle sh, OmsContainerNo cn,
    size_t capacity)
  {
    if (omsIsRegistered(h, sh, cn, capacity)) {
      return true;
    }
    // check in kernel
    ClassID rguid = ClsGuid;
    size_t csize = omsGetCapacity(capacity);
    omsMakeArrayGuid(rguid, csize);
    OmsContainerNo rcn = omsGetContainerNo(cn, capacity);
    return h.omsExistsContainer(rguid, sh, rcn);
  }

  /*!
   * \brief Register all existing sizes for this container.
   * This method calls omsRegisterSize() for all existing sizes in this container.
   *
   * \param h OMS handle,
   * \param sh schema handle,
   * \param cn container number (base).
   * \return count of registered size classes.
   */
  static int omsRegisterExisting(OmsHandle& h, OmsSchemaHandle sh, OmsContainerNo cn,
    const char *typname)
  {
    // go through all sizes
    int count = 0;
    for (int i = 0; i < OMS_ARROBJ_OPTSIZECNT; ++i) {
      OmsContainerNo rcn = OmsContainerNo((i << 20) | cn);
      if (h.omsIsRegistered(ClsGuid, sh, rcn)) {
        // already registered
        ++count;
        continue;
      }
      ClassID rguid = ClsGuid;
      size_t csize = OmsArrayObjectBase::omsGetClassSize(i);
      omsMakeArrayGuid(rguid, csize);
      if (h.omsExistsContainer(rguid, sh, rcn)) {
        // register this size
        omsRegisterSize(h, sh, cn, (csize - sizeof(size_t) - sizeof(void*)) / sizeof(T), typname);
        ++count;
      }
    }
    return count;
  }

  /*!
   * \brief Get iterator over all sizes in this container.
   *
   * \param h OMS handle,
   * \param sh schema handle,
   * \param cn container number (base).
   * \return iterator over all size classes registered in this container.
   */
  static Iterator omsGetSizesIterator(OmsHandle &h, OmsSchemaHandle sh, OmsContainerNo cn)
  {
    return Iterator(h, sh, cn);
  }

private:
  /// Disallow default constructor.
  OmsArrayObject() {}

  /*!
   * \return size for the object in bytes for given capacity (alignment 8B).
   */
  static size_t omsGetByteSize(size_t capacity) 
  {
    if (capacity == 0) {
      capacity = 1; // prevent capacity 0
    }
    return (sizeof(OmsArrayObject<T,ClsGuid>) + sizeof(T) * (capacity - 1) + 7) & ~7;
  }

  /// Default constructor for objects - zero initialize.
  void omsConstructT(T *obj, size_t count)
  {
    memset(obj, 0, sizeof(T) * count);
  }

protected:
  size_t    m_size;    ///< Size of the array (count of elements).
  T         m_data[1]; ///< First array element (placeholder for the whole array).
};

#endif  // __OMS_ARRAYOBJECT_HPP
