// Created on: 2001-06-26
// Created by: Alexander GRIGORIEV
// Copyright (c) 2001-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.

// AGV 140202: Replace(const char *) for (LDOMBasicString)=>myTagName

#include <LDOM_BasicElement.hxx>
#include <LDOM_BasicAttribute.hxx>
#include <LDOM_BasicText.hxx>
#include <LDOM_MemManager.hxx>
#include <LDOM_NodeList.hxx>

//=======================================================================
// function : Create
// purpose  : construction in the Document's data pool
//=======================================================================

LDOM_BasicElement& LDOM_BasicElement::Create(const char*                         aName,
                                             const int                           aLen,
                                             const occ::handle<LDOM_MemManager>& aDoc)
{
  if (aName == nullptr)
  {
    static LDOM_BasicElement aVoidElement;
    aVoidElement = LDOM_BasicElement();
    return aVoidElement;
  }
  void*              aMem     = aDoc->Allocate(sizeof(LDOM_BasicElement));
  LDOM_BasicElement* aNewElem = new (aMem) LDOM_BasicElement;

  int aHash;
  //  aDoc -> HashedAllocate (aString, strlen(aString), aNewElem -> myTagName);
  aNewElem->myTagName = aDoc->HashedAllocate(aName, aLen, aHash);

  aNewElem->myNodeType = LDOM_Node::ELEMENT_NODE;
  return *aNewElem;
}

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

void LDOM_BasicElement::RemoveNodes()
{
  const LDOM_BasicNode* aNode = (const LDOM_BasicNode*)myFirstChild;
  while (aNode)
  {
    const LDOM_BasicNode* aNext = aNode->GetSibling();
    switch (aNode->getNodeType())
    {
      case LDOM_Node::ELEMENT_NODE: {
        LDOM_BasicElement& anElement = *(LDOM_BasicElement*)aNode;
        anElement                    = nullptr;
        break;
      }
      case LDOM_Node::ATTRIBUTE_NODE: {
        LDOM_BasicAttribute& anAttr = *(LDOM_BasicAttribute*)aNode;
        anAttr                      = nullptr;
        break;
      }
      case LDOM_Node::TEXT_NODE:
      case LDOM_Node::COMMENT_NODE:
      case LDOM_Node::CDATA_SECTION_NODE: {
        LDOM_BasicText& aTxt = *(LDOM_BasicText*)aNode;
        aTxt                 = nullptr;
        break;
      }
      default:
        break;
    }
    aNode = aNext;
  }
  myFirstChild = nullptr;
}

//=======================================================================
// function : operator =
// purpose  : Nullify
//=======================================================================

LDOM_BasicElement& LDOM_BasicElement::operator=(const LDOM_NullPtr* aNull)
{
  myTagName = nullptr;
  RemoveNodes();
  LDOM_BasicNode::operator=(aNull);
  return *this;
}

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

/*
LDOM_BasicElement::LDOM_BasicElement (const LDOM_Element& anElement)
     : LDOM_BasicNode   (LDOM_Node::ELEMENT_NODE),
       myAttributeMask  (0),
       myFirstChild     (NULL)
{
//  LDOMString aNewTagName (anElement.getTagName(), anElement.myDocument);
//  myTagName = aNewTagName;
  const LDOM_BasicElement& anOther =
    (const LDOM_BasicElement&) anElement.Origin();
  myTagName = anOther.GetTagName();
}
*/
//=================================================================================================

LDOM_BasicElement::~LDOM_BasicElement()
{
  myTagName = nullptr;
  RemoveNodes();
}

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

const LDOM_BasicNode* LDOM_BasicElement::GetLastChild() const
{
  const LDOM_BasicNode* aNode = myFirstChild;
  if (aNode)
  {
    if (aNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
      aNode = nullptr;
    else
      while (aNode->mySibling)
      {
        if (aNode->mySibling->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
          break;
        aNode = aNode->mySibling;
      }
  }
  return aNode;
}

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

const LDOM_BasicAttribute& LDOM_BasicElement::GetAttribute(const LDOMBasicString& aName,
                                                           const LDOM_BasicNode*  aLastCh) const
{
  const LDOM_BasicNode* aNode;
  if (aLastCh)
    aNode = aLastCh->GetSibling();
  else
    aNode = myFirstChild;
  const char* aNameStr = aName.GetString();
  while (aNode)
  {
    if (aNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
    {
      const LDOM_BasicAttribute* anAttr = (const LDOM_BasicAttribute*)aNode;
      if (!strcmp(aNameStr, anAttr->GetName()))
        return *anAttr;
    }
    aNode = aNode->mySibling;
  }
  static const LDOM_BasicAttribute aNullAttribute;
  return aNullAttribute;
}

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

const LDOM_BasicAttribute* LDOM_BasicElement::GetFirstAttribute(
  const LDOM_BasicNode*&  theLastCh,
  const LDOM_BasicNode**& thePrevNode) const
{
  //  Find the First Attribute as well as the Last Child among siblings
  const LDOM_BasicNode*  aFirstAttr;
  const LDOM_BasicNode** aPrevNode;
  if (theLastCh)
  {
    aFirstAttr = theLastCh->mySibling;
    aPrevNode  = (const LDOM_BasicNode**)&(theLastCh->mySibling);
    while (aFirstAttr)
    {
      if (aFirstAttr->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
        break;
      aPrevNode  = (const LDOM_BasicNode**)&(aFirstAttr->mySibling);
      aFirstAttr = aFirstAttr->mySibling;
    }
  }
  else
  {
    aFirstAttr = myFirstChild;
    aPrevNode  = (const LDOM_BasicNode**)&myFirstChild;
    while (aFirstAttr)
    {
      if (aFirstAttr->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
        break;
      if (aFirstAttr->isNull() == false)
        theLastCh = aFirstAttr;
      aPrevNode  = (const LDOM_BasicNode**)&(aFirstAttr->mySibling);
      aFirstAttr = aFirstAttr->mySibling;
    }
  }
  thePrevNode = aPrevNode;
  return (LDOM_BasicAttribute*)aFirstAttr;
}

//=======================================================================
// function : AddAttribute
// purpose  : Add or replace an attribute
//=======================================================================

const LDOM_BasicNode* LDOM_BasicElement::AddAttribute(const LDOMBasicString& anAttrName,
                                                      const LDOMBasicString& anAttrValue,
                                                      const occ::handle<LDOM_MemManager>& aDocument,
                                                      const LDOM_BasicNode*               aLastCh)
{
  //  Create attribute
  int                  aHash;
  LDOM_BasicAttribute& anAttr = LDOM_BasicAttribute::Create(anAttrName, aDocument, aHash);
  anAttr.myValue              = anAttrValue;

  //  Initialize the loop of attribute name search
  const LDOM_BasicNode**     aPrNode;
  const LDOM_BasicAttribute* aFirstAttr = GetFirstAttribute(aLastCh, aPrNode);
  const char*                aNameStr   = anAttrName.GetString();

  //  Check attribute hash value against the current mask
  const unsigned int  anAttrMaskValue = aHash & (8 * sizeof(myAttributeMask) - 1);
  const unsigned long anAttributeMask = (1 << anAttrMaskValue);
#ifdef OCCT_DEBUG_MASK
  anAttributeMask = 0xffffffff;
#endif
  if ((myAttributeMask & anAttributeMask) == 0)
  {
    // this is new attribute, OK
    myAttributeMask |= anAttributeMask;
    *aPrNode = &anAttr;
    anAttr.SetSibling(aFirstAttr);
  }
  else
  {
    // this attribute may have already been installed
    LDOM_BasicAttribute* aCurrentAttr = (LDOM_BasicAttribute*)aFirstAttr;
    while (aCurrentAttr)
    {
      if (aCurrentAttr->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
        if (LDOM_MemManager::CompareStrings(aNameStr, aHash, aCurrentAttr->GetName()))
        {
          aCurrentAttr->SetValue(anAttrValue, aDocument);
          break;
        }
      aCurrentAttr = (LDOM_BasicAttribute*)aCurrentAttr->mySibling;
    }
    if (aCurrentAttr == nullptr)
    {
      // this is new attribute, OK
      *aPrNode = &anAttr;
      anAttr.SetSibling(aFirstAttr);
    }
  }
  return aLastCh;
}

//=======================================================================
// function : RemoveAttribute
// purpose  : Find and delete an attribute from list
//=======================================================================

const LDOM_BasicNode* LDOM_BasicElement::RemoveAttribute(const LDOMBasicString& aName,
                                                         const LDOM_BasicNode*  aLastCh) const
{
  //  Check attribute hash value against the current mask
  const char* const   aNameStr        = aName.GetString();
  const int           aHash           = LDOM_MemManager::Hash(aNameStr, (int)strlen(aNameStr));
  const unsigned int  anAttrMaskValue = aHash & (8 * sizeof(myAttributeMask) - 1);
  const unsigned long anAttributeMask = (1 << anAttrMaskValue);
#ifdef OCCT_DEBUG_MASK
  anAttributeMask = 0xffffffff;
#endif
  if ((myAttributeMask & anAttributeMask) == 0)
  {
    ; // maybe cause for exception
  }
  else
  {
    const LDOM_BasicNode**     aPrevNode; // dummy
    const LDOM_BasicAttribute* anAttr = GetFirstAttribute(aLastCh, aPrevNode);
    while (anAttr)
    {
      if (anAttr->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
        if (LDOM_MemManager::CompareStrings(aNameStr, aHash, anAttr->GetName()))
        {
          anAttr = nullptr;
          break;
        }
      anAttr = (const LDOM_BasicAttribute*)anAttr->mySibling;
    }
  }
  return aLastCh;
}

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

void LDOM_BasicElement::RemoveChild(const LDOM_BasicNode* aChild) const
{
  const LDOM_BasicNode*  aNode     = myFirstChild;
  const LDOM_BasicNode** aPrevNode = (const LDOM_BasicNode**)&myFirstChild;
  while (aNode)
  {
    if (aNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
      break;
    if (aNode == aChild)
    {
      *aPrevNode               = aNode->GetSibling();
      *(LDOM_BasicNode*)aChild = nullptr;
      break;
    }
    aPrevNode = (const LDOM_BasicNode**)&(aNode->mySibling);
    aNode     = aNode->GetSibling();
  }
  // here may be the cause to throw an exception
}

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

void LDOM_BasicElement::AppendChild(const LDOM_BasicNode*  aChild,
                                    const LDOM_BasicNode*& aLastChild) const
{
  if (aLastChild)
  {
    (const LDOM_BasicNode*&)aChild->mySibling     = aLastChild->mySibling;
    (const LDOM_BasicNode*&)aLastChild->mySibling = aChild;
  }
  else
  {
    const LDOM_BasicNode*  aNode     = myFirstChild;
    const LDOM_BasicNode** aPrevNode = (const LDOM_BasicNode**)&myFirstChild;
    while (aNode)
    {
      if (aNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
      {
        (const LDOM_BasicNode*&)aChild->mySibling = aNode;
        break;
      }
      aPrevNode = (const LDOM_BasicNode**)&(aNode->mySibling);
      aNode     = aNode->mySibling;
    }
    *aPrevNode = aChild;
  }
  aLastChild = aChild;
}

//=======================================================================
// function : AddElementsByTagName
// purpose  : Add to the List all sub-elements with the given name (recursive)
//=======================================================================

void LDOM_BasicElement::AddElementsByTagName(LDOM_NodeList&         aList,
                                             const LDOMBasicString& aTagName) const
{
  const LDOM_BasicNode* aNode      = myFirstChild;
  const char*           aTagString = aTagName.GetString();
  while (aNode)
  {
    if (aNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
      break;
    if (aNode->getNodeType() == LDOM_Node::ELEMENT_NODE)
    {
      LDOM_BasicElement& anElement = *(LDOM_BasicElement*)aNode;
      //      if (anElement.GetTagName().equals(aTagName))
      if (strcmp(anElement.GetTagName(), aTagString) == 0)
        aList.Append(anElement);
      anElement.AddElementsByTagName(aList, aTagName);
    }
    aNode = aNode->GetSibling();
  }
}

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

void LDOM_BasicElement::AddAttributes(LDOM_NodeList& aList, const LDOM_BasicNode* aLastChild) const
{
  const LDOM_BasicNode* aBNode;
  if (aLastChild)
    aBNode = aLastChild->GetSibling();
  else
    aBNode = GetFirstChild();
  while (aBNode)
  {
    if (aBNode->getNodeType() == LDOM_Node::ATTRIBUTE_NODE)
      aList.Append(*aBNode);
    aBNode = aBNode->GetSibling();
  }
}

//=======================================================================
// function : ReplaceElement
// purpose  : Copy data and children into this node from another one
//           The only preserved data is mySibling
//=======================================================================

void LDOM_BasicElement::ReplaceElement(const LDOM_BasicElement&            anOtherElem,
                                       const occ::handle<LDOM_MemManager>& aDocument)
{
  myTagName                        = anOtherElem.GetTagName();
  myAttributeMask                  = anOtherElem.myAttributeMask;
  myFirstChild                     = nullptr;
  const LDOM_BasicNode* aBNode     = anOtherElem.GetFirstChild();
  const LDOM_BasicNode* aLastChild = nullptr;

  // Loop on children (non-attributes)
  for (; aBNode != nullptr; aBNode = aBNode->GetSibling())
  {
    if (aBNode->isNull())
      continue;
    LDOM_BasicNode*           aNewBNode;
    const LDOM_Node::NodeType aNewNodeType = aBNode->getNodeType();
    switch (aNewNodeType)
    {
      case LDOM_Node::ELEMENT_NODE: {
        const LDOM_BasicElement& aBNodeElem = *(const LDOM_BasicElement*)aBNode;
        const char*              aTagString = aBNodeElem.GetTagName();
        LDOM_BasicElement&       aNewBNodeElem =
          LDOM_BasicElement::Create(aTagString, (int)strlen(aTagString), aDocument);
        aNewBNodeElem.ReplaceElement(aBNodeElem, aDocument); // reccur
        aNewBNode = &aNewBNodeElem;
        break;
      }
      case LDOM_Node::ATTRIBUTE_NODE:
        goto loop_attr;
      case LDOM_Node::TEXT_NODE:
      case LDOM_Node::COMMENT_NODE:
      case LDOM_Node::CDATA_SECTION_NODE: {
        const LDOM_BasicText& aBNodeText = *(const LDOM_BasicText*)aBNode;
        aNewBNode                        = &LDOM_BasicText::Create(aNewNodeType,
                                            LDOMString(aBNodeText.GetData(), aDocument),
                                            aDocument);
        break;
      }
      default:
        continue;
    }
    if (GetFirstChild())
      (const LDOM_BasicNode*&)aLastChild->mySibling = aNewBNode;
    else
      (const LDOM_BasicNode*&)myFirstChild = aNewBNode;
    (const LDOM_BasicNode*&)aLastChild = aNewBNode;
  }

  // Loop on attributes (in the end of the list of children)
loop_attr:
  LDOM_BasicNode* aLastAttr = (LDOM_BasicNode*)aLastChild;
  for (; aBNode != nullptr; aBNode = aBNode->GetSibling())
  {
    int aHash;
    if (aBNode->isNull())
      continue;
    const LDOM_BasicAttribute* aBNodeAtt = (const LDOM_BasicAttribute*)aBNode;
    LDOM_BasicAttribute*       aNewAtt =
      &LDOM_BasicAttribute::Create(aBNodeAtt->GetName(), aDocument, aHash);
    aNewAtt->SetValue(aBNodeAtt->myValue, aDocument);
    if (aLastAttr)
      aLastAttr->SetSibling(aNewAtt);
    else
      myFirstChild = aNewAtt;
    aLastAttr = aNewAtt;
  }
}
