// Created on: 2001-08-31
// Created by: Julia DOROVSKIKH
// 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.

#include <Message_Messenger.hxx>
#include <Standard_Type.hxx>
#include <TDF_Tool.hxx>
#include <TDocStd_FormatVersion.hxx>
#include <TNaming_Iterator.hxx>
#include <TNaming_Naming.hxx>
#include <XmlMNaming_NamingDriver.hxx>
#include <XmlObjMgt.hxx>
#include <XmlObjMgt_Persistent.hxx>

IMPLEMENT_STANDARD_RTTIEXT(XmlMNaming_NamingDriver, XmlMDF_ADriver)

//=======================================================================
static TopAbs_ShapeEnum ShapeEnumFromString(const XmlObjMgt_DOMString&);
static TNaming_NameType NameTypeFromString(const XmlObjMgt_DOMString&);

static const XmlObjMgt_DOMString& ShapeEnumToString(const TopAbs_ShapeEnum);
static const XmlObjMgt_DOMString& NameTypeToString(const TNaming_NameType);

IMPLEMENT_DOMSTRING(IndexString, "index")
IMPLEMENT_DOMSTRING(StopNamedShapeString, "stopNS")
IMPLEMENT_DOMSTRING(TypeString, "nametype")
IMPLEMENT_DOMSTRING(ShapeTypeString, "shapetype")
IMPLEMENT_DOMSTRING(ArgumentsString, "arguments")
IMPLEMENT_DOMSTRING(ContextLabelString, "contextlabel")
IMPLEMENT_DOMSTRING(OrientString, "orientation")

IMPLEMENT_DOMSTRING(NTUnknownString, "unknown")
IMPLEMENT_DOMSTRING(NTIdentityString, "identity")
IMPLEMENT_DOMSTRING(NTModifUntilString, "modifuntil")
IMPLEMENT_DOMSTRING(NTGenerationString, "generation")
IMPLEMENT_DOMSTRING(NTIntersectionString, "intersection")
IMPLEMENT_DOMSTRING(NTUnionString, "union")
IMPLEMENT_DOMSTRING(NTSubtractionString, "subtraction")
IMPLEMENT_DOMSTRING(NTConstShapeString, "constshape")
IMPLEMENT_DOMSTRING(NTFilterByNeighString, "filterbyneigh")
IMPLEMENT_DOMSTRING(NTOrientationString, "orientation")
IMPLEMENT_DOMSTRING(NTWireInString, "wirein")
IMPLEMENT_DOMSTRING(NTShellInString, "shellin")

IMPLEMENT_DOMSTRING(ShCompoundString, "compound")
IMPLEMENT_DOMSTRING(ShCompsolidString, "compsolid")
IMPLEMENT_DOMSTRING(ShSolidString, "solid")
IMPLEMENT_DOMSTRING(ShShellString, "shell")
IMPLEMENT_DOMSTRING(ShFaceString, "face")
IMPLEMENT_DOMSTRING(ShWireString, "wire")
IMPLEMENT_DOMSTRING(ShEdgeString, "edge")
IMPLEMENT_DOMSTRING(ShVertexString, "vertex")
IMPLEMENT_DOMSTRING(ShShapeString, "shape")

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

XmlMNaming_NamingDriver::XmlMNaming_NamingDriver(const occ::handle<Message_Messenger>& theMsgDriver)
    : XmlMDF_ADriver(theMsgDriver, nullptr)
{
}

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

occ::handle<TDF_Attribute> XmlMNaming_NamingDriver::NewEmpty() const
{
  return (new TNaming_Naming());
}

//=======================================================================
// function : Paste
// purpose  : persistent -> transient (retrieve)
//=======================================================================
bool XmlMNaming_NamingDriver::Paste(const XmlObjMgt_Persistent&       theSource,
                                    const occ::handle<TDF_Attribute>& theTarget,
                                    XmlObjMgt_RRelocationTable&       theRelocTable) const
{
  const XmlObjMgt_Element&    anElem = theSource;
  occ::handle<TNaming_Naming> aNg    = occ::down_cast<TNaming_Naming>(theTarget);

  TNaming_Name& aNgName = aNg->ChangeName();

  // type and shape type
  aNgName.Type(NameTypeFromString(anElem.getAttribute(::TypeString())));
  aNgName.ShapeType(ShapeEnumFromString(anElem.getAttribute(::ShapeTypeString())));

  int                             aNb;
  occ::handle<TNaming_NamedShape> NS;
  TCollection_ExtendedString      aMsgString;

  XmlObjMgt_DOMString aDOMStr = anElem.getAttribute(::ArgumentsString());
  if (aDOMStr != nullptr)
  {
    const char* aGs = static_cast<const char*>(aDOMStr.GetString());

    // first argument
    if (!XmlObjMgt::GetInteger(aGs, aNb))
    {
      aMsgString = TCollection_ExtendedString("XmlMNaming_NamingDriver: Cannot retrieve reference "
                                              "on first Argument from \"")
                   + aDOMStr + "\"";
      myMessageDriver->Send(aMsgString, Message_Fail);
      return false;
    }
    while (aNb > 0)
    {
      if (theRelocTable.IsBound(aNb))
        NS = occ::down_cast<TNaming_NamedShape>(theRelocTable.Find(aNb));
      else
      {
        NS = new TNaming_NamedShape;
        theRelocTable.Bind(aNb, NS);
      }
      aNgName.Append(NS);

      // next argument
      if (!XmlObjMgt::GetInteger(aGs, aNb))
        aNb = 0;
    }
  }

  // stop named shape
  aDOMStr = anElem.getAttribute(::StopNamedShapeString());
  if (aDOMStr != nullptr)
  {
    if (!aDOMStr.GetInteger(aNb))
    {
      aMsgString = TCollection_ExtendedString("XmlMNaming_NamingDriver: Cannot retrieve reference "
                                              "on StopNamedShape from \"")
                   + aDOMStr + "\"";
      myMessageDriver->Send(aMsgString, Message_Fail);
      return false;
    }
    if (aNb > 0)
    {
      if (theRelocTable.IsBound(aNb))
        NS = occ::down_cast<TNaming_NamedShape>(theRelocTable.Find(aNb));
      else
      {
        NS = new TNaming_NamedShape;
        theRelocTable.Bind(aNb, NS);
      }
      aNgName.StopNamedShape(NS);
    }
  }

  // index
  aDOMStr = anElem.getAttribute(::IndexString());
  if (!aDOMStr.GetInteger(aNb))
  {
    aMsgString = TCollection_ExtendedString("XmlMNaming_NamingDriver: Cannot retrieve "
                                            "integer value of Index from \"")
                 + aDOMStr + "\"";
    myMessageDriver->Send(aMsgString, Message_Fail);
    return false;
  }
  aNgName.Index(aNb);
  //
  if (theRelocTable.GetHeaderData()->StorageVersion().IntegerValue()
      >= TDocStd_FormatVersion_VERSION_4)
  {
    XmlObjMgt_DOMString aDomEntry = anElem.getAttribute(::ContextLabelString());
    if (aDomEntry != nullptr)
    {
      TCollection_AsciiString anEntry;
      if (XmlObjMgt::GetTagEntryString(aDomEntry, anEntry) == false)
      {
        TCollection_ExtendedString aMessage =
          TCollection_ExtendedString("Cannot retrieve Entry from \"") + aDomEntry + '\"';
        myMessageDriver->Send(aMessage, Message_Fail);
        return false;
      }

      // find label by entry
      TDF_Label tLab; // Null label.
      if (anEntry.Length() > 0)
      {
        TDF_Tool::Label(aNg->Label().Data(), anEntry, tLab, true);
        aNgName.ContextLabel(tLab);
#ifdef OCCT_DEBUG
        std::cout << "Retrieving Context Label = " << anEntry.ToCString() << std::endl;
#endif
      }
    }
#ifdef OCCT_DEBUG
    else
      std::cout << "Retrieving Context Label is NULL" << std::endl;
#endif

    if (theRelocTable.GetHeaderData()->StorageVersion().IntegerValue()
          >= TDocStd_FormatVersion_VERSION_5
        && theRelocTable.GetHeaderData()->StorageVersion().IntegerValue()
             < TDocStd_FormatVersion_VERSION_7)
    {
      // Orientation processing - converting from old format
      occ::handle<TNaming_NamedShape> aNS;
      if (aNg->Label().FindAttribute(TNaming_NamedShape::GetID(), aNS))
      {
        // const TDF_Label& aLab = aNS->Label();
        TNaming_Iterator itL(aNS);
        for (; itL.More(); itL.Next())
        {
          const TopoDS_Shape& S = itL.NewShape();
          if (S.IsNull())
            continue;
          if (aNS->Evolution() == TNaming_SELECTED)
          {
            if (itL.More() && itL.NewShape().ShapeType() != TopAbs_VERTEX
                && !itL.OldShape().IsNull() && itL.OldShape().ShapeType() == TopAbs_VERTEX)
            { // OR-N
              TopAbs_Orientation OrientationToApply = itL.OldShape().Orientation();
              aNgName.Orientation(OrientationToApply);
            }
          }
        }
      }
    }
    if (theRelocTable.GetHeaderData()->StorageVersion().IntegerValue()
        >= TDocStd_FormatVersion_VERSION_7)
    {
      aDOMStr = anElem.getAttribute(::OrientString());
      if (!aDOMStr.GetInteger(aNb))
      {
        aMsgString = TCollection_ExtendedString("XmlMNaming_NamingDriver: Cannot retrieve "
                                                "integer value of orientation from \"")
                     + aDOMStr + "\"";
        myMessageDriver->Send(aMsgString, Message_Fail);
        return false;
      }
      aNgName.Orientation((TopAbs_Orientation)aNb);
    }
    // or. end
  }
#ifdef OCCT_DEBUG
  else
    std::cout << "Current Document Format Version = "
              << theRelocTable.GetHeaderData()->StorageVersion().IntegerValue() << std::endl;
#endif
  return true;
}

//=======================================================================
// function : Paste
// purpose  : transient -> persistent (store)
//=======================================================================
void XmlMNaming_NamingDriver::Paste(const occ::handle<TDF_Attribute>& theSource,
                                    XmlObjMgt_Persistent&             theTarget,
                                    XmlObjMgt_SRelocationTable&       theRelocTable) const
{
  occ::handle<TNaming_Naming> aNg     = occ::down_cast<TNaming_Naming>(theSource);
  XmlObjMgt_Element&          anElem  = theTarget;
  const TNaming_Name&         aNgName = aNg->GetName();

  // type and shape type
  anElem.setAttribute(::TypeString(), NameTypeToString(aNgName.Type()));
  anElem.setAttribute(::ShapeTypeString(), ShapeEnumToString(aNgName.ShapeType()));

  int aNb;

  // arguments
  int NbArgs = aNgName.Arguments().Extent();
  if (NbArgs > 0)
  {
    TCollection_AsciiString anArgsStr;
    for (NCollection_List<occ::handle<TNaming_NamedShape>>::Iterator it(aNgName.Arguments());
         it.More();
         it.Next())
    {
      occ::handle<TNaming_NamedShape> anArg = it.Value();
      aNb                                   = 0;
      if (!anArg.IsNull())
      {
        aNb = theRelocTable.FindIndex(anArg);
        if (aNb == 0)
        {
          aNb = theRelocTable.Add(anArg);
        }
        anArgsStr += TCollection_AsciiString(aNb) + " ";
      }
      else
        anArgsStr += "0 ";
    }
    anElem.setAttribute(::ArgumentsString(), anArgsStr.ToCString());
  }

  // stop named shape
  occ::handle<TNaming_NamedShape> aSNS = aNgName.StopNamedShape();
  if (!aSNS.IsNull())
  {
    aNb = theRelocTable.FindIndex(aSNS);
    if (aNb == 0)
    {
      aNb = theRelocTable.Add(aSNS);
    }
    anElem.setAttribute(::StopNamedShapeString(), aNb);
  }

  // index
  anElem.setAttribute(::IndexString(), aNgName.Index());

  // context label
  TCollection_AsciiString anEntry;
  if (!aNgName.ContextLabel().IsNull())
    TDF_Tool::Entry(aNgName.ContextLabel(), anEntry);
  XmlObjMgt_DOMString aDOMString;
  XmlObjMgt::SetTagEntryString(aDOMString, anEntry);
  anElem.setAttribute(::ContextLabelString(), aDOMString);
#ifdef OCCT_DEBUG
  std::cout << "XmlMNaming_NamingDriver::Store: ContextLabel Entry = " << anEntry << std::endl;
  if (aDOMString != NULL)
  {
    const char* aStr = static_cast<const char*>(aDOMString.GetString());
    std::cout << "XmlMNaming_NamingDriver::Store: ContextLabel DOMString = " << aStr << std::endl;
  }
  else
    std::cout << "XmlMNaming_NamingDriver::Store: aDOMString is NULL" << std::endl;
#endif

  // orientation
  anElem.setAttribute(::OrientString(), (int)aNgName.Orientation());
}

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

static const XmlObjMgt_DOMString& ShapeEnumToString(const TopAbs_ShapeEnum theE)
{
  switch (theE)
  {
    case TopAbs_COMPOUND:
      return ::ShCompoundString();
    case TopAbs_COMPSOLID:
      return ::ShCompsolidString();
    case TopAbs_SOLID:
      return ::ShSolidString();
    case TopAbs_SHELL:
      return ::ShShellString();
    case TopAbs_FACE:
      return ::ShFaceString();
    case TopAbs_WIRE:
      return ::ShWireString();
    case TopAbs_EDGE:
      return ::ShEdgeString();
    case TopAbs_VERTEX:
      return ::ShVertexString();
    case TopAbs_SHAPE:
      return ::ShShapeString();
  }
  static XmlObjMgt_DOMString aNullString;
  return aNullString; // To avoid compilation error message.
}

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

static const XmlObjMgt_DOMString& NameTypeToString(const TNaming_NameType theE)
{
  switch (theE)
  {
    case TNaming_UNKNOWN:
      return ::NTUnknownString();
    case TNaming_IDENTITY:
      return ::NTIdentityString();
    case TNaming_MODIFUNTIL:
      return ::NTModifUntilString();
    case TNaming_GENERATION:
      return ::NTGenerationString();
    case TNaming_INTERSECTION:
      return ::NTIntersectionString();
    case TNaming_UNION:
      return ::NTUnionString();
    case TNaming_SUBSTRACTION:
      return ::NTSubtractionString();
    case TNaming_CONSTSHAPE:
      return ::NTConstShapeString();
    case TNaming_FILTERBYNEIGHBOURGS:
      return ::NTFilterByNeighString();
    case TNaming_ORIENTATION:
      return ::NTOrientationString();
    case TNaming_WIREIN:
      return ::NTWireInString();
    case TNaming_SHELLIN:
      return ::NTShellInString();
    default:
      throw Standard_DomainError("TNaming_NameType; enum term unknown ");
  }
}

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

static TopAbs_ShapeEnum ShapeEnumFromString(const XmlObjMgt_DOMString& theString)
{
  TopAbs_ShapeEnum aResult = TopAbs_SHAPE;
  if (!theString.equals(::ShShapeString()))
  {
    if (theString.equals(::ShCompoundString()))
      aResult = TopAbs_COMPOUND;
    else if (theString.equals(::ShCompsolidString()))
      aResult = TopAbs_COMPSOLID;
    else if (theString.equals(::ShSolidString()))
      aResult = TopAbs_SOLID;
    else if (theString.equals(::ShShellString()))
      aResult = TopAbs_SHELL;
    else if (theString.equals(::ShFaceString()))
      aResult = TopAbs_FACE;
    else if (theString.equals(::ShWireString()))
      aResult = TopAbs_WIRE;
    else if (theString.equals(::ShEdgeString()))
      aResult = TopAbs_EDGE;
    else if (theString.equals(::ShVertexString()))
      aResult = TopAbs_VERTEX;
    else
      throw Standard_DomainError("TopAbs_ShapeEnum; string value without enum term equivalence");
  }
  return aResult;
}

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

static TNaming_NameType NameTypeFromString(const XmlObjMgt_DOMString& theString)
{
  TNaming_NameType aResult = TNaming_UNKNOWN;
  if (!theString.equals(::NTUnknownString()))
  {
    if (theString.equals(::NTIdentityString()))
      aResult = TNaming_IDENTITY;
    else if (theString.equals(::NTModifUntilString()))
      aResult = TNaming_MODIFUNTIL;
    else if (theString.equals(::NTGenerationString()))
      aResult = TNaming_GENERATION;
    else if (theString.equals(::NTIntersectionString()))
      aResult = TNaming_INTERSECTION;
    else if (theString.equals(::NTUnionString()))
      aResult = TNaming_UNION;
    else if (theString.equals(::NTSubtractionString()))
      aResult = TNaming_SUBSTRACTION;
    else if (theString.equals(::NTConstShapeString()))
      aResult = TNaming_CONSTSHAPE;
    else if (theString.equals(::NTFilterByNeighString()))
      aResult = TNaming_FILTERBYNEIGHBOURGS;
    else if (theString.equals(::NTOrientationString()))
      aResult = TNaming_ORIENTATION;
    else if (theString.equals(::NTWireInString()))
      aResult = TNaming_WIREIN;
    else if (theString.equals(::NTShellInString()))
      aResult = TNaming_SHELLIN;
    else
      throw Standard_DomainError("TNaming_NameType; string value without enum term equivalence");
  }
  return aResult;
}
