// Copyright (c) 2021 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#ifndef _NCollection_AliasedArray_HeaderFile
#define _NCollection_AliasedArray_HeaderFile

#include <Standard_DimensionMismatch.hxx>
#include <Standard_OutOfMemory.hxx>
#include <Standard_OutOfRange.hxx>
#include <Standard_TypeMismatch.hxx>
#include <Standard_Macro.hxx>

#include <cstring>

//! Defines an array of values of configurable size.
//! For instance, this class allows defining an array of 32-bit or 64-bit integer values with
//! bitness determined in runtime. The element size in bytes (stride) should be specified at
//! construction time. Indexation starts from 0 index. As actual type of element varies at runtime,
//! element accessors are defined as templates. Memory for array is allocated with the given
//! alignment (template parameter).
template <int MyAlignSize = 16>
class NCollection_AliasedArray
{
public:
  DEFINE_STANDARD_ALLOC
public:
  //! Empty constructor.
  NCollection_AliasedArray(int theStride)
      : myData(nullptr),
        myStride(theStride),
        mySize(0),
        myDeletable(false)
  {
    if (theStride <= 0)
    {
      throw Standard_RangeError("NCollection_AliasedArray, stride should be positive");
    }
  }

  //! Constructor
  NCollection_AliasedArray(int theStride, int theLength)
      : myData(nullptr),
        myStride(theStride),
        mySize(theLength),
        myDeletable(true)
  {
    if (theLength <= 0 || myStride <= 0)
    {
      throw Standard_RangeError("NCollection_AliasedArray, stride and length should be positive");
    }
    myData = (uint8_t*)Standard::AllocateAligned(SizeBytes(), MyAlignSize);
    if (myData == nullptr)
    {
      throw Standard_OutOfMemory("NCollection_AliasedArray, allocation failed");
    }
  }

  //! Copy constructor
  NCollection_AliasedArray(const NCollection_AliasedArray& theOther)
      : myData(nullptr),
        myStride(theOther.myStride),
        mySize(theOther.mySize),
        myDeletable(false)
  {
    if (mySize != 0)
    {
      myDeletable = true;
      myData      = (uint8_t*)Standard::AllocateAligned(SizeBytes(), MyAlignSize);
      if (myData == nullptr)
      {
        throw Standard_OutOfMemory("NCollection_AliasedArray, allocation failed");
      }
      Assign(theOther);
    }
  }

  //! Move constructor
  NCollection_AliasedArray(NCollection_AliasedArray&& theOther) noexcept
      : myData(theOther.myData),
        myStride(theOther.myStride),
        mySize(theOther.mySize),
        myDeletable(theOther.myDeletable)
  {
    theOther.myDeletable = false;
  }

  //! Constructor wrapping pre-allocated C-array of values without copying them.
  template <typename Type_t>
  NCollection_AliasedArray(const Type_t& theBegin, int theLength)
      : myData((uint8_t*)&theBegin),
        myStride((int)sizeof(Type_t)),
        mySize(theLength),
        myDeletable(false)
  {
    if (theLength <= 0)
    {
      throw Standard_RangeError("NCollection_AliasedArray, length should be positive");
    }
  }

  //! Returns an element size in bytes.
  int Stride() const { return myStride; }

  //! Size query
  int Size() const { return mySize; }

  //! Length query (the same as Size())
  int Length() const { return mySize; }

  //! Return TRUE if array has zero length.
  bool IsEmpty() const { return mySize == 0; }

  //! Lower bound
  int Lower() const { return 0; }

  //! Upper bound
  int Upper() const { return mySize - 1; }

  //! myDeletable flag
  bool IsDeletable() const { return myDeletable; }

  //! IsAllocated flag - for naming compatibility
  bool IsAllocated() const { return myDeletable; }

  //! Return buffer size in bytes.
  size_t SizeBytes() const { return size_t(myStride) * size_t(mySize); }

  //! Copies data of theOther array to this.
  //! This array should be pre-allocated and have the same length as theOther;
  //! otherwise exception Standard_DimensionMismatch is thrown.
  NCollection_AliasedArray& Assign(const NCollection_AliasedArray& theOther)
  {
    if (&theOther != this)
    {
      if (myStride != theOther.myStride || mySize != theOther.mySize)
      {
        throw Standard_DimensionMismatch(
          "NCollection_AliasedArray::Assign(), arrays have different size");
      }
      if (myData != nullptr)
      {
        memcpy(myData, theOther.myData, SizeBytes());
      }
    }
    return *this;
  }

  //! Move assignment.
  //! This array will borrow all the data from theOther.
  //! The moved object will keep pointer to the memory buffer and
  //! range, but it will not free the buffer on destruction.
  NCollection_AliasedArray& Move(NCollection_AliasedArray& theOther)
  {
    if (&theOther != this)
    {
      if (myDeletable)
      {
        Standard::FreeAligned(myData);
      }
      myStride             = theOther.myStride;
      mySize               = theOther.mySize;
      myDeletable          = theOther.myDeletable;
      myData               = theOther.myData;
      theOther.myDeletable = false;
    }
    return *this;
  }

  //! Assignment operator; @sa Assign()
  NCollection_AliasedArray& operator=(const NCollection_AliasedArray& theOther)
  {
    return Assign(theOther);
  }

  //! Move assignment operator; @sa Move()
  NCollection_AliasedArray& operator=(NCollection_AliasedArray&& theOther)
  {
    return Move(theOther);
  }

  //! Resizes the array to specified bounds.
  //! No re-allocation will be done if length of array does not change,
  //! but existing values will not be discarded if theToCopyData set to FALSE.
  //! @param theLength new length of array
  //! @param theToCopyData flag to copy existing data into new array
  void Resize(int theLength, bool theToCopyData)
  {
    if (theLength <= 0)
    {
      throw Standard_RangeError("NCollection_AliasedArray::Resize, length should be positive");
    }
    if (mySize == theLength)
    {
      return;
    }

    const int      anOldLen  = mySize;
    const uint8_t* anOldData = myData;
    mySize                   = theLength;
    if (!theToCopyData && myDeletable)
    {
      Standard::FreeAligned(myData);
    }
    myData = (uint8_t*)Standard::AllocateAligned(SizeBytes(), MyAlignSize);
    if (myData == nullptr)
    {
      throw Standard_OutOfMemory("NCollection_AliasedArray, allocation failed");
    }
    if (!theToCopyData)
    {
      myDeletable = true;
      return;
    }

    const size_t aLenCopy = size_t((std::min)(anOldLen, theLength)) * size_t(myStride);
    memcpy(myData, anOldData, aLenCopy);
    if (myDeletable)
    {
      Standard::FreeAligned(anOldData);
    }
    myDeletable = true;
  }

  //! Destructor - releases the memory
  ~NCollection_AliasedArray()
  {
    if (myDeletable)
    {
      Standard::FreeAligned(myData);
    }
  }

public:
  //! Access raw bytes of specified element.
  const uint8_t* value(int theIndex) const
  {
    Standard_OutOfRange_Raise_if(theIndex < 0 || theIndex >= mySize,
                                 "NCollection_AliasedArray::value(), out of range index");
    return myData + size_t(myStride) * size_t(theIndex);
  }

  //! Access raw bytes of specified element.
  uint8_t* changeValue(int theIndex)
  {
    Standard_OutOfRange_Raise_if(theIndex < 0 || theIndex >= mySize,
                                 "NCollection_AliasedArray::changeValue(), out of range index");
    return myData + size_t(myStride) * size_t(theIndex);
  }

  //! Initialize the items with theValue
  template <typename Type_t>
  void Init(const Type_t& theValue)
  {
    for (int anIter = 0; anIter < mySize; ++anIter)
    {
      ChangeValue<Type_t>(anIter) = theValue;
    }
  }

  //! Access element with specified position and type.
  //! This method requires size of a type matching stride value.
  template <typename Type_t>
  const Type_t& Value(int theIndex) const
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) != sizeof(Type_t),
                                   "NCollection_AliasedArray::Value(), wrong type");
    return *reinterpret_cast<const Type_t*>(value(theIndex));
  }

  //! Access element with specified position and type.
  //! This method requires size of a type matching stride value.
  template <typename Type_t>
  void Value(int theIndex, Type_t& theValue) const
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) != sizeof(Type_t),
                                   "NCollection_AliasedArray::Value(), wrong type");
    theValue = *reinterpret_cast<const Type_t*>(value(theIndex));
  }

  //! Access element with specified position and type.
  //! This method requires size of a type matching stride value.
  template <typename Type_t>
  Type_t& ChangeValue(int theIndex)
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) != sizeof(Type_t),
                                   "NCollection_AliasedArray::ChangeValue(), wrong type");
    return *reinterpret_cast<Type_t*>(changeValue(theIndex));
  }

  //! Access element with specified position and type.
  //! This method allows wrapping element into smaller type (e.g. to alias 2-components within
  //! 3-component vector).
  template <typename Type_t>
  const Type_t& Value2(int theIndex) const
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) < sizeof(Type_t),
                                   "NCollection_AliasedArray::Value2(), wrong type");
    return *reinterpret_cast<const Type_t*>(value(theIndex));
  }

  //! Access element with specified position and type.
  //! This method allows wrapping element into smaller type (e.g. to alias 2-components within
  //! 3-component vector).
  template <typename Type_t>
  void Value2(int theIndex, Type_t& theValue) const
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) < sizeof(Type_t),
                                   "NCollection_AliasedArray::Value2(), wrong type");
    theValue = *reinterpret_cast<const Type_t*>(value(theIndex));
  }

  //! Access element with specified position and type.
  //! This method allows wrapping element into smaller type (e.g. to alias 2-components within
  //! 3-component vector).
  template <typename Type_t>
  Type_t& ChangeValue2(int theIndex)
  {
    Standard_TypeMismatch_Raise_if(size_t(myStride) < sizeof(Type_t),
                                   "NCollection_AliasedArray::ChangeValue2(), wrong type");
    return *reinterpret_cast<Type_t*>(changeValue(theIndex));
  }

  //! Return first element
  template <typename Type_t>
  const Type_t& First() const
  {
    return Value<Type_t>(0);
  }

  //! Return first element
  template <typename Type_t>
  Type_t& ChangeFirst()
  {
    return ChangeValue<Type_t>(0);
  }

  //! Return last element
  template <typename Type_t>
  const Type_t& Last() const
  {
    return Value<Type_t>(mySize - 1);
  }

  //! Return last element
  template <typename Type_t>
  Type_t& ChangeLast()
  {
    return Value<Type_t>(mySize - 1);
  }

protected:
  uint8_t* myData;      //!< data pointer
  int      myStride;    //!< element size
  int      mySize;      //!< number of elements
  bool     myDeletable; //!< flag showing who allocated the array
};

#endif // _NCollection_AliasedArray_HeaderFile
