// Created on: 2002-03-29
// Created by: Alexander GRIGORIEV
// 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.

// Purpose:   Implementation of the BaseSequence class

#include <NCollection_BaseSequence.hxx>
#include <Standard_NoSuchObject.hxx>
#include <Standard_OutOfRange.hxx>
#include <Standard_DomainError.hxx>

//=======================================================================
// function : ClearSeq
// purpose  : removes all items from the current sequence
//=======================================================================

void NCollection_BaseSequence::ClearSeq(NCollection_DelSeqNode fDel)
{
  NCollection_SeqNode* p = myFirstItem;
  while (p)
  {
    NCollection_SeqNode* q = p;
    p                      = p->Next();
    fDel(q, myAllocator);
  }
  Nullify();
}

//=================================================================================================

void NCollection_BaseSequence::PAppend(NCollection_SeqNode* theItem)
{
  if (mySize == 0)
  {
    myFirstItem = myLastItem = myCurrentItem = theItem;
    myCurrentIndex = mySize = 1;
  }
  else
  {
    myLastItem->SetNext(theItem);
    theItem->SetPrevious(myLastItem);
    theItem->SetNext(nullptr);
    myLastItem = theItem;
    ++mySize;
  }
}

//=======================================================================
// function : PAppend
// purpose  : push a sequence at the end of the sequence
//=======================================================================

void NCollection_BaseSequence::PAppend(NCollection_BaseSequence& Other)
{
  if (Other.mySize == 0)
  {
    return;
  }
  if (mySize == 0)
  {
    mySize         = Other.mySize;
    myFirstItem    = Other.myFirstItem;
    myLastItem     = Other.myLastItem;
    myCurrentItem  = myFirstItem;
    myCurrentIndex = 1;
  }
  else
  {
    mySize += Other.mySize;
    myLastItem->SetNext(Other.myFirstItem);
    if (Other.myFirstItem)
    {
      Other.myFirstItem->SetPrevious(myLastItem);
      myLastItem = Other.myLastItem;
    }
  }
  Other.Nullify();
}

//=================================================================================================

void NCollection_BaseSequence::PPrepend(NCollection_SeqNode* theItem)
{
  if (mySize == 0)
  {
    myFirstItem = myLastItem = myCurrentItem = theItem;
    myCurrentIndex = mySize = 1;
  }
  else
  {
    myFirstItem->SetPrevious(theItem);
    theItem->SetNext(myFirstItem);
    theItem->SetPrevious(nullptr);
    theItem->SetNext(myFirstItem);
    myFirstItem = theItem;
    ++mySize;
    ++myCurrentIndex;
  }
}

//=======================================================================
// function : PPrepend
// purpose  : push a sequence in the beginning of the sequence
//=======================================================================

void NCollection_BaseSequence::PPrepend(NCollection_BaseSequence& Other)
{
  if (Other.mySize == 0)
  {
    return;
  }
  if (mySize == 0)
  {
    mySize         = Other.mySize;
    myFirstItem    = Other.myFirstItem;
    myLastItem     = Other.myLastItem;
    myCurrentIndex = 1;
    myCurrentItem  = myFirstItem;
  }
  else
  {
    mySize += Other.mySize;
    if (Other.myLastItem)
    {
      Other.myLastItem->SetNext(myFirstItem);
    }
    myFirstItem->SetPrevious(Other.myLastItem);
    myFirstItem = Other.myFirstItem;
    myCurrentIndex += Other.mySize;
  }
  Other.Nullify();
}

//=======================================================================
// function : PReverse
// purpose  : reverse the order of a given sequence
//=======================================================================

void NCollection_BaseSequence::PReverse() noexcept
{
  NCollection_SeqNode* p = myFirstItem;
  while (p)
  {
    NCollection_SeqNode* tmp = p->Next();
    p->SetNext(p->Previous());
    p->SetPrevious(tmp);
    p = tmp;
  }
  NCollection_SeqNode* tmp = myFirstItem;
  myFirstItem              = myLastItem;
  myLastItem               = tmp;
  if (mySize != 0)
  {
    myCurrentIndex = mySize + 1 - myCurrentIndex;
  }
}

//=================================================================================================

void NCollection_BaseSequence::PInsertAfter(NCollection_BaseSequence::Iterator& thePosition,
                                            NCollection_SeqNode*                theItem)
{
  NCollection_SeqNode* aPos = thePosition.myCurrent;
  if (aPos == nullptr)
  {
    PPrepend(theItem);
  }
  else
  {
    theItem->SetNext(aPos->Next());
    theItem->SetPrevious(aPos);
    if (aPos->Next() == nullptr)
    {
      myLastItem = theItem;
    }
    else
    {
      aPos->Next()->SetPrevious(theItem);
    }
    aPos->SetNext(theItem);
    ++mySize;
    myCurrentItem  = myFirstItem;
    myCurrentIndex = 1;
  }
}

//=================================================================================================

void NCollection_BaseSequence::PInsertAfter(const size_t theIndex, NCollection_SeqNode* theItem)
{
  if (theIndex == 0)
  {
    PPrepend(theItem);
  }
  else
  {
    NCollection_SeqNode* p = Find(theIndex);
    theItem->SetNext(p->Next());
    theItem->SetPrevious(p);
    if (theIndex == mySize)
    {
      myLastItem = theItem;
    }
    else
    {
      p->Next()->SetPrevious(theItem);
    }
    p->SetNext(theItem);
    ++mySize;
    if (theIndex < myCurrentIndex)
    {
      ++myCurrentIndex;
    }
  }
}

//=======================================================================
// function : PInsertAfter
// purpose  : insert a sequence after a given index in the sequence
//=======================================================================

void NCollection_BaseSequence::PInsertAfter(const size_t theIndex, NCollection_BaseSequence& Other)
{
  if (theIndex > mySize)
  {
    throw Standard_OutOfRange();
  }
  if (Other.mySize != 0)
  {
    if (theIndex == 0)
    {
      PPrepend(Other);
    }
    else
    {
      NCollection_SeqNode* p = Find(theIndex);
      Other.myFirstItem->SetPrevious(p);
      Other.myLastItem->SetNext(p->Next());
      if (theIndex == mySize)
      {
        myLastItem = Other.myLastItem;
      }
      else
      {
        p->Next()->SetPrevious(Other.myLastItem);
      }
      p->SetNext(Other.myFirstItem);
      mySize += Other.mySize;
      if (theIndex < myCurrentIndex)
      {
        myCurrentIndex += Other.mySize;
      }
      Other.Nullify();
    }
  }
}

//=======================================================================
// function : PExchange
// purpose  : exchange two elements in the sequence
//=======================================================================

void NCollection_BaseSequence::PExchange(const size_t I, const size_t J)
{
  Standard_OutOfRange_Raise_if(I == 0 || J == 0 || I > mySize || J > mySize, "");

  // Assume I < J
  if (J < I)
  {
    PExchange(J, I);
  }
  else if (I < J)
  {
    NCollection_SeqNode* pi = Find(I);
    NCollection_SeqNode* pj = Find(J);

    // update the node before I
    if (pi->Previous())
    {
      pi->Previous()->SetNext(pj);
    }
    else
    {
      myFirstItem = pj;
    }

    // update the node after J
    if (pj->Next())
    {
      pj->Next()->SetPrevious(pi);
    }
    else
    {
      myLastItem = pi;
    }

    if (pi->Next() == pj)
    { // I and J are consecutives, update them
      pj->SetPrevious(pi->Previous());
      pi->SetPrevious(pj);
      pi->SetNext(pj->Next());
      pj->SetNext(pi);
    }
    else
    { // I and J are not consecutive
      // update the node after I
      pi->Next()->SetPrevious(pj);
      // update the node before J
      pj->Previous()->SetNext(pi);
      // update nodes I and J
      NCollection_SeqNode* tmp = pi->Next();
      pi->SetNext(pj->Next());
      pj->SetNext(tmp);
      tmp = pi->Previous();
      pi->SetPrevious(pj->Previous());
      pj->SetPrevious(tmp);
    }

    if (myCurrentIndex == I)
    {
      myCurrentItem = pj;
    }
    else if (myCurrentIndex == J)
    {
      myCurrentItem = pi;
    }
  }
}

//=================================================================================================

void NCollection_BaseSequence::PSplit(const size_t theIndex, NCollection_BaseSequence& Sub)
{
  Standard_OutOfRange_Raise_if(theIndex == 0 || theIndex > mySize, "");
  Standard_DomainError_Raise_if(this == &Sub, "No Split on myself!!");

  NCollection_SeqNode* p = Find(theIndex);

  Sub.myLastItem = myLastItem;
  Sub.mySize     = mySize - theIndex + 1;

  myLastItem = p->Previous();
  if (myLastItem)
  {
    myLastItem->SetNext(nullptr);
    mySize = theIndex - 1;
    if (myCurrentIndex >= theIndex)
    {
      myCurrentIndex = 1;
      myCurrentItem  = myFirstItem;
    }
  }
  else
  {
    myFirstItem = myCurrentItem = nullptr;
    mySize = myCurrentIndex = 0;
  }

  Sub.myFirstItem = Sub.myCurrentItem = p;
  p->SetPrevious(nullptr);
  Sub.myCurrentIndex = 1;
}

//=================================================================================================

void NCollection_BaseSequence::RemoveSeq(NCollection_BaseSequence::Iterator& thePosition,
                                         NCollection_DelSeqNode              fDel)
{
  NCollection_SeqNode* aPos = thePosition.myCurrent;
  if (aPos == nullptr)
  {
    return;
  }
  thePosition.myCurrent = aPos->Next();

  if (aPos->Previous())
  {
    aPos->Previous()->SetNext(aPos->Next());
  }
  else
  {
    myFirstItem = aPos->Next();
  }

  if (aPos->Next())
  {
    aPos->Next()->SetPrevious(aPos->Previous());
  }
  else
  {
    myLastItem = aPos->Previous();
  }

  --mySize;
  myCurrentItem  = myLastItem;
  myCurrentIndex = mySize;

  fDel(aPos, myAllocator);
}

//=================================================================================================

void NCollection_BaseSequence::RemoveSeq(const size_t theIndex, NCollection_DelSeqNode fDel)
{
  Standard_OutOfRange_Raise_if(theIndex == 0 || theIndex > mySize,
                               "NCollection_BaseSequence::RemoveSeq() - index is out of range");

  NCollection_SeqNode* p = Find(theIndex);
  if (p->Previous())
  {
    p->Previous()->SetNext(p->Next());
  }
  else
  {
    myFirstItem = p->Next();
  }
  if (p->Next())
  {
    p->Next()->SetPrevious(p->Previous());
  }
  else
  {
    myLastItem = p->Previous();
  }

  --mySize;
  if (myCurrentIndex > theIndex)
  {
    --myCurrentIndex;
  }
  else if (myCurrentIndex == theIndex)
  {
    if (p->Next())
    {
      myCurrentItem = p->Next();
    }
    else
    {
      myCurrentItem  = myLastItem;
      myCurrentIndex = mySize;
    }
  }
  fDel(p, myAllocator);
}

//=================================================================================================

void NCollection_BaseSequence::RemoveSeq(const size_t           From,
                                         const size_t           To,
                                         NCollection_DelSeqNode fDel)
{
  Standard_OutOfRange_Raise_if(From == 0 || To > mySize || From > To,
                               "NCollection_BaseSequence::RemoveSeq() - invalid input range");

  NCollection_SeqNode* pfrom = Find(From);
  NCollection_SeqNode* pto   = Find(To);

  if (pfrom->Previous())
  {
    pfrom->Previous()->SetNext(pto->Next());
  }
  else
  {
    myFirstItem = pto->Next();
  }
  if (pto->Next())
  {
    pto->Next()->SetPrevious(pfrom->Previous());
  }
  else
  {
    myLastItem = pfrom->Previous();
  }

  mySize -= To - From + 1;
  if (myCurrentIndex > To)
  {
    myCurrentIndex -= To - From + 1;
  }
  else if (myCurrentIndex >= From)
  {
    if (pto->Next())
    {
      myCurrentItem  = pto->Next();
      myCurrentIndex = From; // AGV fix 24.05.01
    }
    else
    {
      myCurrentItem  = myLastItem;
      myCurrentIndex = mySize;
    }
  }

  for (size_t i = From; i <= To; ++i)
  {
    NCollection_SeqNode* tmp = pfrom;
    pfrom                    = pfrom->Next();
    fDel(tmp, myAllocator);
  }
}

//=================================================================================================

NCollection_SeqNode* NCollection_BaseSequence::Find(const size_t theIndex) const noexcept
{
  size_t               i;
  NCollection_SeqNode* p;
  if (theIndex <= myCurrentIndex)
  {
    if (theIndex < myCurrentIndex / 2)
    {
      p = myFirstItem;
      for (i = 1; i < theIndex; ++i)
      {
        p = p->Next();
      }
    }
    else
    {
      p = myCurrentItem;
      for (i = myCurrentIndex; i > theIndex; --i)
      {
        p = p->Previous();
      }
    }
  }
  else
  {
    if (theIndex < (myCurrentIndex + mySize) / 2)
    {
      p = myCurrentItem;
      for (i = myCurrentIndex; i < theIndex; ++i)
      {
        p = p->Next();
      }
    }
    else
    {
      p = myLastItem;
      for (i = mySize; i > theIndex; --i)
      {
        p = p->Previous();
      }
    }
  }
  return p;
}
