// Created on: 2002-04-15
// Created by: Alexander Kartomin (akm)
// Copyright (c) 2002-2014 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_Array2_HeaderFile
#define NCollection_Array2_HeaderFile

#include <Standard_DimensionMismatch.hxx>
#include <Standard_OutOfMemory.hxx>
#include <NCollection_Allocator.hxx>
#include <Standard_OutOfRange.hxx>
#include <NCollection_Array1.hxx>

#include <NCollection_DefineAlloc.hxx>

// *********************************************** Template for Array2 class
/**
 * Purpose:   The class Array2 represents bi-dimensional arrays
 *            of fixed size known at run time.
 *            The ranges of indices are user defined.
 *
 *            Class allocates one 1D array storing full data (all Rows and Columns)
 *            and extra 1D array storing pointers to each Row.
 *
 * Warning:   Programs clients of such class must be independent
 *            of the range of the first element. Then, a C++ for
 *            loop must be written like this
 *
 *            for (i = A.LowerRow(); i <= A.UpperRow(); i++)
 *              for (j = A.LowerCol(); j <= A.UpperCol(); j++)
 *
 * Zero-based (size_t) construction mode:
 *   NCollection_Array2(size_t theNbRows, size_t theNbCols) creates a zero-based array
 *   (LowerRow()==0, LowerCol()==0). In this mode At()/ChangeAt() and STL iterators are
 *   the preferred access path -- they address elements directly without any offset subtraction.
 *   Buffer-reuse variant NCollection_Array2(pointer, size_t, size_t) wraps an existing
 *   flat row-major buffer and does NOT own the memory.
 */
template <class TheItemType>
class NCollection_Array2 : public NCollection_Array1<TheItemType>
{
public:
  //! Memory allocation
  DEFINE_STANDARD_ALLOC;
  DEFINE_NCOLLECTION_ALLOC;

public:
  typedef NCollection_Allocator<TheItemType> allocator_type;

public:
  // Define various type aliases for convenience
  using value_type      = typename NCollection_Array1<TheItemType>::value_type;
  using size_type       = typename NCollection_Array1<TheItemType>::size_type;
  using difference_type = typename NCollection_Array1<TheItemType>::difference_type;
  using pointer         = typename NCollection_Array1<TheItemType>::pointer;
  using const_pointer   = typename NCollection_Array1<TheItemType>::const_pointer;
  using reference       = typename NCollection_Array1<TheItemType>::reference;
  using const_reference = typename NCollection_Array1<TheItemType>::const_reference;

  using iterator       = typename NCollection_Array1<TheItemType>::iterator;
  using const_iterator = typename NCollection_Array1<TheItemType>::const_iterator;

  static int BeginPosition(int theRowLower,
                           int /*theRowUpper*/,
                           int theColLower,
                           int theColUpper) noexcept
  {
    // Calculate the offset for the beginning position
    return theColLower + (theRowLower * (theColUpper - theColLower + 1));
  }

  static int LastPosition(int theRowLower,
                          int theRowUpper,
                          int theColLower,
                          int theColUpper) noexcept
  {
    return ((theRowUpper - theRowLower + 1) * (theColUpper - theColLower + 1)) + theColLower
           + (theRowLower * (theColUpper - theColLower + 1)) - 1;
  }

public:
  // ---------- PUBLIC METHODS ------------

  //! Empty constructor; should be used with caution.
  //! @sa methods Resize() and Move().
  NCollection_Array2() noexcept
      : NCollection_Array1<TheItemType>(),
        myLowerRow(1),
        mySizeRow(0),
        myLowerCol(1),
        mySizeCol(0)
  {
  }

  //! Constructor
  NCollection_Array2(const int theRowLower,
                     const int theRowUpper,
                     const int theColLower,
                     const int theColUpper)
      : NCollection_Array1<TheItemType>(
          BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper),
          LastPosition(theRowLower, theRowUpper, theColLower, theColUpper)),
        myLowerRow(theRowLower),
        mySizeRow(theRowUpper - theRowLower + 1),
        myLowerCol(theColLower),
        mySizeCol(theColUpper - theColLower + 1)
  {
  }

  //! Copy constructor
  NCollection_Array2(const NCollection_Array2& theOther)
      : NCollection_Array1<TheItemType>(theOther),
        myLowerRow(theOther.LowerRow()),
        mySizeRow(theOther.NbRows()),
        myLowerCol(theOther.LowerCol()),
        mySizeCol(theOther.NbColumns())
  {
  }

  //! Move constructor
  NCollection_Array2(NCollection_Array2&& theOther) noexcept
      : NCollection_Array1<TheItemType>(std::forward<NCollection_Array2>(theOther)),
        myLowerRow(theOther.LowerRow()),
        mySizeRow(theOther.NbRows()),
        myLowerCol(theOther.LowerCol()),
        mySizeCol(theOther.NbColumns())
  {
    theOther.myLowerRow = 1;
    theOther.mySizeRow  = 0;
    theOther.myLowerCol = 1;
    theOther.mySizeCol  = 0;
  }

  //! C array-based constructor
  explicit NCollection_Array2(const TheItemType& theBegin,
                              const int          theRowLower,
                              const int          theRowUpper,
                              const int          theColLower,
                              const int          theColUpper)
      : NCollection_Array1<TheItemType>(
          theBegin,
          BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper),
          LastPosition(theRowLower, theRowUpper, theColLower, theColUpper)),
        myLowerRow(theRowLower),
        mySizeRow(theRowUpper - theRowLower + 1),
        myLowerCol(theColLower),
        mySizeCol(theColUpper - theColLower + 1)
  {
  }

  //! Zero-based constructor: allocates theNbRows x theNbCols elements with lower bounds 0.
  //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction).
  explicit NCollection_Array2(const size_t theNbRows, const size_t theNbCols)
      : NCollection_Array1<TheItemType>(theNbRows * theNbCols),
        myLowerRow(0),
        mySizeRow(theNbRows),
        myLowerCol(0),
        mySizeCol(theNbCols)
  {
  }

  //! Zero-based buffer-reuse constructor: wraps an existing flat row-major C array.
  //! The array does NOT own the buffer and will NOT free it on destruction.
  //! Use At()/ChangeAt() or STL iterators for optimal access (no offset subtraction).
  explicit NCollection_Array2(pointer theBegin, const size_t theNbRows, const size_t theNbCols)
      : NCollection_Array1<TheItemType>(theBegin, theNbRows * theNbCols),
        myLowerRow(0),
        mySizeRow(theNbRows),
        myLowerCol(0),
        mySizeCol(theNbCols)
  {
  }

  //! Size (number of items).
  size_t Size() const noexcept { return mySizeRow * mySizeCol; }

  //! Length (legacy int-returning API).
  int Length() const noexcept { return NbRows() * NbColumns(); }

  //! Returns number of rows
  int NbRows() const noexcept { return static_cast<int>(mySizeRow); }

  //! Returns number of columns
  int NbColumns() const noexcept { return static_cast<int>(mySizeCol); }

  //! Returns length of the row, i.e. number of columns
  int RowLength() const noexcept { return NbColumns(); }

  //! Returns length of the column, i.e. number of rows
  int ColLength() const noexcept { return NbRows(); }

  //! LowerRow
  int LowerRow() const noexcept { return myLowerRow; }

  //! UpperRow
  int UpperRow() const noexcept { return myLowerRow + static_cast<int>(mySizeRow) - 1; }

  //! LowerCol
  int LowerCol() const noexcept { return myLowerCol; }

  //! UpperCol
  int UpperCol() const noexcept { return myLowerCol + static_cast<int>(mySizeCol) - 1; }

  //! Updates lower row
  void UpdateLowerRow(const int theLowerRow) noexcept { myLowerRow = theLowerRow; }

  //! Updates lower column
  void UpdateLowerCol(const int theLowerCol) noexcept { myLowerCol = theLowerCol; }

  //! Updates upper row
  void UpdateUpperRow(const int theUpperRow) noexcept
  {
    myLowerRow = myLowerRow - UpperRow() + theUpperRow;
  }

  //! Updates upper column
  void UpdateUpperCol(const int theUpperCol) noexcept
  {
    myLowerCol = myLowerCol - UpperCol() + theUpperCol;
  }

  //! Replaces this array by a copy of theOther array.
  //! Row and column bounds are copied from theOther.
  NCollection_Array2& Assign(const NCollection_Array2& theOther)
  {
    if (&theOther == this)
    {
      return *this;
    }
    NCollection_Array1<TheItemType>::Assign(theOther);
    myLowerRow = theOther.myLowerRow;
    mySizeRow  = theOther.mySizeRow;
    myLowerCol = theOther.myLowerCol;
    mySizeCol  = theOther.mySizeCol;
    return *this;
  }

  //! Copies values from theOther array without changing this array bounds.
  //! This array should be pre-allocated and have the same dimensions as theOther;
  //! otherwise exception Standard_DimensionMismatch is thrown.
  NCollection_Array2& CopyValues(const NCollection_Array2& theOther)
  {
    if (&theOther == this)
    {
      return *this;
    }
    Standard_DimensionMismatch_Raise_if(mySizeRow != theOther.mySizeRow
                                          || mySizeCol != theOther.mySizeCol,
                                        "NCollection_Array2::CopyValues");
    NCollection_Array1<TheItemType>::CopyValues(theOther);
    return *this;
  }

  //! Move assignment.
  //! This array will borrow all the data from theOther.
  //! The moved object will be left uninitialized and should not be used anymore.
  NCollection_Array2& Move(NCollection_Array2&& theOther) noexcept
  {
    if (&theOther == this)
    {
      return *this;
    }
    NCollection_Array1<TheItemType>::Move(theOther);
    myLowerRow          = theOther.myLowerRow;
    mySizeRow           = theOther.mySizeRow;
    myLowerCol          = theOther.myLowerCol;
    mySizeCol           = theOther.mySizeCol;
    theOther.myLowerRow = 1;
    theOther.mySizeRow  = 0;
    theOther.myLowerCol = 1;
    theOther.mySizeCol  = 0;
    return *this;
  }

  //! Move assignment.
  //! This array will borrow all the data from theOther.
  //! The moved object will be left uninitialized and should not be used anymore.
  NCollection_Array2& Move(NCollection_Array2& theOther) noexcept
  {
    return Move(std::move(theOther));
  }

  //! Assignment operator
  NCollection_Array2& operator=(const NCollection_Array2& theOther) { return Assign(theOther); }

  //! Move assignment operator; @sa Move()
  NCollection_Array2& operator=(NCollection_Array2&& theOther) noexcept
  {
    return Move(std::forward<NCollection_Array2>(theOther));
  }

  //! Constant value access
  const_reference Value(const int theRow, const int theCol) const
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    return NCollection_Array1<TheItemType>::at(aPos);
  }

  //! operator() - alias to ChangeValue
  const_reference operator()(const int theRow, const int theCol) const
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    return NCollection_Array1<TheItemType>::at(aPos);
  }

  //! Variable value access
  reference ChangeValue(const int theRow, const int theCol)
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    return NCollection_Array1<TheItemType>::at(aPos);
  }

  //! operator() - alias to ChangeValue
  reference operator()(const int theRow, const int theCol) { return ChangeValue(theRow, theCol); }

  //! SetValue
  void SetValue(const int theRow, const int theCol, const TheItemType& theItem)
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    NCollection_Array1<TheItemType>::at(aPos) = theItem;
  }

  //! SetValue
  void SetValue(const int theRow, const int theCol, TheItemType&& theItem)
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    NCollection_Array1<TheItemType>::at(aPos) = std::forward<TheItemType>(theItem);
  }

  //! 0-based checked access independent of LowerRow()/LowerCol().
  //! @param[in] theRow 0-based row index in [0, NbRows()-1]
  //! @param[in] theCol 0-based column index in [0, NbColumns()-1]
  const_reference At(const size_t theRow, const size_t theCol) const
  {
    Standard_OutOfRange_Raise_if(theRow >= mySizeRow || theCol >= mySizeCol,
                                 "NCollection_Array2::At");
    return NCollection_Array1<TheItemType>::at(theRow * mySizeCol + theCol);
  }

  //! 0-based checked mutable access independent of LowerRow()/LowerCol().
  //! @param[in] theRow 0-based row index in [0, NbRows()-1]
  //! @param[in] theCol 0-based column index in [0, NbColumns()-1]
  reference ChangeAt(const size_t theRow, const size_t theCol)
  {
    Standard_OutOfRange_Raise_if(theRow >= mySizeRow || theCol >= mySizeCol,
                                 "NCollection_Array2::ChangeAt");
    return NCollection_Array1<TheItemType>::at(theRow * mySizeCol + theCol);
  }

  //! Emplace value at the specified row and column, constructing it in-place
  //! @param theRow row index at which to emplace the value
  //! @param theCol column index at which to emplace the value
  //! @param theArgs arguments forwarded to TheItemType constructor
  //! @return reference to the newly constructed item
  template <typename... Args>
  reference EmplaceValue(const int theRow, const int theCol, Args&&... theArgs)
  {
    const size_t aPos = (theRow - myLowerRow) * mySizeCol + (theCol - myLowerCol);
    Standard_OutOfRange_Raise_if(aPos >= this->mySize, "NCollection_Array2::EmplaceValue");
    this->myPointer[aPos] = value_type(std::forward<Args>(theArgs)...);
    return this->myPointer[aPos];
  }

  //! Resizes the array to specified bounds.
  //! When theToCopyData is false, the array is re-allocated without preserving data.
  //! When theToCopyData is true, copies elements in linear (row-major) order.
  //! No re-allocation is done if dimensions are unchanged.
  //! @param theRowLower new lower Row of array
  //! @param theRowUpper new upper Row of array
  //! @param theColLower new lower Column of array
  //! @param theColUpper new upper Column of array
  //! @param theToCopyData flag to copy existing data into new array
  void Resize(int  theRowLower,
              int  theRowUpper,
              int  theColLower,
              int  theColUpper,
              bool theToCopyData)
  {
    if (!theToCopyData)
    {
      resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper);
      return;
    }
    resizeImpl<false>(theRowLower, theRowUpper, theColLower, theColUpper);
  }

  //! Resizes the array preserving 2D element layout.
  //! When theToCopyData is false, the array is re-allocated without preserving data.
  //! When theToCopyData is true, copies min(oldNbRows,newNbRows) x min(oldNbCols,newNbCols)
  //! elements from the top-left corner of the old array to the top-left corner of the new,
  //! preserving relative (row, col) offsets from lower bounds. Trimming or growing as needed.
  //! No re-allocation is done if dimensions are unchanged.
  //! @param theRowLower new lower Row of array
  //! @param theRowUpper new upper Row of array
  //! @param theColLower new lower Column of array
  //! @param theColUpper new upper Column of array
  //! @param theToCopyData flag to copy existing data into new array
  void ResizeWithTrim(int  theRowLower,
                      int  theRowUpper,
                      int  theColLower,
                      int  theColUpper,
                      bool theToCopyData)
  {
    if (!theToCopyData)
    {
      resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper);
      return;
    }
    resizeImpl<true>(theRowLower, theRowUpper, theColLower, theColUpper);
  }

  //! Zero-based Resize: resizes to theNbRows x theNbCols, keeping lower bounds unchanged.
  //! No re-allocation is done if dimensions are unchanged.
  //! @param theNbRows new number of rows
  //! @param theNbCols new number of columns
  //! @param theToCopyData flag to copy existing data into new array
  void Resize(const size_t theNbRows, const size_t theNbCols, const bool theToCopyData)
  {
    Resize(myLowerRow,
           myLowerRow + static_cast<int>(theNbRows) - 1,
           myLowerCol,
           myLowerCol + static_cast<int>(theNbCols) - 1,
           theToCopyData);
  }

  //! Zero-based ResizeWithTrim: resizes preserving 2D layout, keeping lower bounds unchanged.
  //! No re-allocation is done if dimensions are unchanged.
  //! @param theNbRows new number of rows
  //! @param theNbCols new number of columns
  //! @param theToCopyData flag to copy existing data into new array
  void ResizeWithTrim(const size_t theNbRows, const size_t theNbCols, const bool theToCopyData)
  {
    ResizeWithTrim(myLowerRow,
                   myLowerRow + static_cast<int>(theNbRows) - 1,
                   myLowerCol,
                   myLowerCol + static_cast<int>(theNbCols) - 1,
                   theToCopyData);
  }

protected:
  //! Resize without copying data.
  void resizeNoData(int theRowLower, int theRowUpper, int theColLower, int theColUpper)
  {
    Standard_RangeError_Raise_if(theRowUpper < theRowLower || theColUpper < theColLower,
                                 "NCollection_Array2::Resize");
    NCollection_Array1<TheItemType>::Resize(
      BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper),
      LastPosition(theRowLower, theRowUpper, theColLower, theColUpper),
      false);
    mySizeRow  = theRowUpper - theRowLower + 1;
    mySizeCol  = theColUpper - theColLower + 1;
    myLowerRow = theRowLower;
    myLowerCol = theColLower;
  }

  //! Internal resize with data copy.
  //! @tparam thePreserve2D if true, copies the common sub-matrix preserving
  //!   2D element positions (row, col); if false, copies elements in linear order.
  template <bool thePreserve2D>
  void resizeImpl(int theRowLower, int theRowUpper, int theColLower, int theColUpper)
  {
    Standard_RangeError_Raise_if(theRowUpper < theRowLower || theColUpper < theColLower,
                                 "NCollection_Array2::Resize");
    const size_t aNewNbRows = theRowUpper - theRowLower + 1;
    const size_t aNewNbCols = theColUpper - theColLower + 1;
    if (mySizeRow == aNewNbRows && mySizeCol == aNewNbCols)
    {
      myLowerRow = theRowLower;
      myLowerCol = theColLower;
      NCollection_Array1<TheItemType>::UpdateLowerBound(
        BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper));
      return;
    }
    if (mySizeRow == 0 || mySizeCol == 0)
    {
      resizeNoData(theRowLower, theRowUpper, theColLower, theColUpper);
      return;
    }
    const size_t aNbRowsToCopy = (std::min)(mySizeRow, aNewNbRows);
    const size_t aNbColsToCopy = (std::min)(mySizeCol, aNewNbCols);
    const size_t aOldStride    = thePreserve2D ? mySizeCol : aNbColsToCopy;

    NCollection_Array2<TheItemType> aTmpMovedCopy(std::move(*this));
    TheItemType*                    anOldPointer = &aTmpMovedCopy.ChangeFirst();
    NCollection_Array1<TheItemType>::Resize(
      BeginPosition(theRowLower, theRowUpper, theColLower, theColUpper),
      LastPosition(theRowLower, theRowUpper, theColLower, theColUpper),
      false);
    mySizeRow  = aNewNbRows;
    mySizeCol  = aNewNbCols;
    myLowerRow = theRowLower;
    myLowerCol = theColLower;
    for (size_t aRowIter = 0; aRowIter < aNbRowsToCopy; ++aRowIter)
    {
      for (size_t aColIter = 0; aColIter < aNbColsToCopy; ++aColIter)
      {
        NCollection_Array1<TheItemType>::at(aRowIter * aNewNbCols + aColIter) =
          std::move(anOldPointer[aRowIter * aOldStride + aColIter]);
      }
    }
  }

protected:
  // ---------- PROTECTED FIELDS -----------
  int    myLowerRow;
  size_t mySizeRow;
  int    myLowerCol;
  size_t mySizeCol;

  // ----------- FRIEND CLASSES ------------
  friend iterator;
  friend const_iterator;
};

#endif
