// Copyright (c) 2023 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 <XSDRAWDE.hxx>

#include <DBRep.hxx>
#include <DDocStd.hxx>
#include <DDocStd_DrawDocument.hxx>
#include <DE_ConfigurationContext.hxx>
#include <DE_Provider.hxx>
#include <DE_Wrapper.hxx>
#include <DEBREP_ConfigurationNode.hxx>
#include <DEXCAF_ConfigurationNode.hxx>
#include <DE_PluginHolder.hxx>
#include <Draw.hxx>
#include <Draw_Interpretor.hxx>
#include <Draw_PluginMacro.hxx>
#include <Draw_ProgressIndicator.hxx>
#include <Message.hxx>
#include <TDataStd_Name.hxx>
#include <TDocStd_Application.hxx>
#include <TopoDS_Shape.hxx>
#include <XSControl_WorkSession.hxx>
#include <XSDRAW.hxx>

namespace
{
// Singleton to ensure DEBREP and DEXCAF plugins are registered only once
void DECascadeSingleton()
{
  static DE_MultiPluginHolder<DEBREP_ConfigurationNode, DEXCAF_ConfigurationNode> aHolder;
  (void)aHolder;
}
} // namespace

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

static int DumpConfiguration(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  occ::handle<DE_Wrapper>                   aConf = DE_Wrapper::GlobalWrapper();
  TCollection_AsciiString                   aPath;
  bool                                      aIsRecursive    = true;
  bool                                      isHandleFormat  = false;
  bool                                      isHandleVendors = false;
  NCollection_List<TCollection_AsciiString> aFormats;
  NCollection_List<TCollection_AsciiString> aVendors;
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if ((anArg == "-path") && (anArgIter + 1 < theNbArgs))
    {
      ++anArgIter;
      aPath = theArgVec[anArgIter];
    }
    else if ((anArg == "-recursive") && (anArgIter + 1 < theNbArgs)
             && Draw::ParseOnOff(theArgVec[anArgIter + 1], aIsRecursive))
    {
      ++anArgIter;
    }
    else if (anArg == "-format")
    {
      isHandleFormat  = true;
      isHandleVendors = false;
    }
    else if (anArg == "-vendor")
    {
      isHandleFormat  = false;
      isHandleVendors = true;
    }
    else if (isHandleFormat)
    {
      aFormats.Append(theArgVec[anArgIter]);
    }
    else if (isHandleVendors)
    {
      aVendors.Append(theArgVec[anArgIter]);
    }
    else if (!isHandleFormat && !isHandleVendors)
    {
      theDI << "Syntax error at argument '" << theArgVec[anArgIter] << "'\n";
      return 1;
    }
  }
  bool aStat = true;
  if (!aPath.IsEmpty())
  {
    aStat = aConf->Save(aPath, aIsRecursive, aFormats, aVendors);
  }
  else
  {
    theDI << aConf->Save(aIsRecursive, aFormats, aVendors) << "\n";
  }
  if (!aStat)
  {
    return 1;
  }
  return 0;
}

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

static int CompareConfiguration(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs > 5)
  {
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }
  occ::handle<DE_ConfigurationContext> aResourceFirst = new DE_ConfigurationContext();
  if (!aResourceFirst->Load(theArgVec[1]))
  {
    theDI << "Error: Can't load first configuration\n";
    return 1;
  }
  occ::handle<DE_ConfigurationContext> aResourceSecond = new DE_ConfigurationContext();
  if (!aResourceSecond->Load(theArgVec[2]))
  {
    theDI << "Error: Can't load second configuration\n";
    return 1;
  }
  const NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString>& aResourceMapFirst =
    aResourceFirst->GetInternalMap();
  const NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString>& aResourceMapSecond =
    aResourceSecond->GetInternalMap();
  int anDiffers = 0;
  for (NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString>::Iterator anOrigIt(
         aResourceMapFirst);
       anOrigIt.More();
       anOrigIt.Next())
  {
    const TCollection_AsciiString& anOrigValue = anOrigIt.Value();
    const TCollection_AsciiString& anOrigKey   = anOrigIt.Key();
    TCollection_AsciiString        aCompValue;
    if (!aResourceMapSecond.Find(anOrigKey, aCompValue))
    {
      Message::SendWarning() << "Second configuration don't have the next scope : " << anOrigKey;
      anDiffers++;
    }
    if (!aCompValue.IsEqual(anOrigValue))
    {
      Message::SendWarning() << "Configurations have differs value with the next scope :"
                             << anOrigKey << " First value : " << anOrigValue
                             << " Second value : " << aCompValue;
      anDiffers++;
    }
  }
  TCollection_AsciiString aMessage;
  if (aResourceMapFirst.Extent() != aResourceMapSecond.Extent() || anDiffers > 0)
  {
    theDI << "Error: Configurations are not same : " << " Differs count : " << anDiffers
          << " Count of first's scopes : " << aResourceMapFirst.Extent()
          << " Count of second's scopes : " << aResourceMapSecond.Extent() << "\n";
    return 1;
  }
  return 0;
}

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

static int LoadConfiguration(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs > 4)
  {
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }
  occ::handle<DE_Wrapper> aConf        = DE_Wrapper::GlobalWrapper();
  TCollection_AsciiString aString      = theArgVec[1];
  bool                    aIsRecursive = true;
  if (theNbArgs == 4)
  {
    TCollection_AsciiString anArg = theArgVec[2];
    anArg.LowerCase();
    if (!(anArg == "-recursive") || !Draw::ParseOnOff(theArgVec[3], aIsRecursive))
    {
      theDI << "Syntax error at argument '" << theArgVec[3] << "'\n";
      return 1;
    }
  }
  if (!aConf->Load(aString, aIsRecursive))
  {
    theDI << "Error: configuration is incorrect\n";
    return 1;
  }
  return 0;
}

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

static int ReadFile(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs > 6)
  {
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }
  TCollection_AsciiString          aDocShapeName;
  TCollection_AsciiString          aFilePath;
  occ::handle<TDocStd_Document>    aDoc;
  occ::handle<TDocStd_Application> anApp = DDocStd::GetApplication();
  TCollection_AsciiString          aConfString;
  bool                             isNoDoc = (TCollection_AsciiString(theArgVec[0]) == "readfile");
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if ((anArg == "-conf") && (anArgIter + 1 < theNbArgs))
    {
      ++anArgIter;
      aConfString = theArgVec[anArgIter];
    }
    else if (aDocShapeName.IsEmpty())
    {
      aDocShapeName        = theArgVec[anArgIter];
      const char* aNameVar = aDocShapeName.ToCString();
      if (!isNoDoc)
      {
        DDocStd::GetDocument(aNameVar, aDoc, false);
      }
    }
    else if (aFilePath.IsEmpty())
    {
      aFilePath = theArgVec[anArgIter];
    }
    else
    {
      theDI << "Syntax error at argument '" << theArgVec[anArgIter] << "'\n";
      return 1;
    }
  }
  if (aDocShapeName.IsEmpty() || aFilePath.IsEmpty())
  {
    theDI << "Syntax error: wrong number of arguments\n";
    return 1;
  }
  if (aDoc.IsNull() && !isNoDoc)
  {
    anApp->NewDocument(TCollection_ExtendedString("BinXCAF"), aDoc);
    occ::handle<DDocStd_DrawDocument> aDrawDoc = new DDocStd_DrawDocument(aDoc);
    TDataStd_Name::Set(aDoc->GetData()->Root(), theArgVec[1]);
    Draw::Set(theArgVec[1], aDrawDoc);
  }

  occ::handle<DE_Wrapper>            aConf = DE_Wrapper::GlobalWrapper()->Copy();
  occ::handle<XSControl_WorkSession> aWS   = XSDRAW::Session();
  bool                               aStat = true;
  if (!aConfString.IsEmpty())
  {
    aStat = aConf->Load(aConfString);
  }
  if (aStat)
  {
    TopoDS_Shape aShape;
    aStat = isNoDoc ? aConf->Read(aFilePath, aShape, aWS) : aConf->Read(aFilePath, aDoc, aWS);
    if (isNoDoc && aStat)
    {
      DBRep::Set(aDocShapeName.ToCString(), aShape);
    }
  }
  if (!aStat)
  {
    return 1;
  }
  XSDRAW::CollectActiveWorkSessions(aFilePath);
  return 0;
}

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

static int WriteFile(Draw_Interpretor& theDI, int theNbArgs, const char** theArgVec)
{
  if (theNbArgs > 6)
  {
    theDI.PrintHelp(theArgVec[0]);
    return 1;
  }
  TCollection_AsciiString       aDocShapeName;
  TCollection_AsciiString       aFilePath;
  occ::handle<TDocStd_Document> aDoc;
  TCollection_AsciiString       aConfString;
  bool                          isNoDoc = (TCollection_AsciiString(theArgVec[0]) == "writefile");
  for (int anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
  {
    TCollection_AsciiString anArg(theArgVec[anArgIter]);
    anArg.LowerCase();
    if ((anArg == "-conf") && (anArgIter + 1 < theNbArgs))
    {
      ++anArgIter;
      aConfString = theArgVec[anArgIter];
    }
    else if (aDocShapeName.IsEmpty())
    {
      aDocShapeName        = theArgVec[anArgIter];
      const char* aNameVar = aDocShapeName.ToCString();
      if (!isNoDoc)
      {
        DDocStd::GetDocument(aNameVar, aDoc, false);
      }
    }
    else if (aFilePath.IsEmpty())
    {
      aFilePath = theArgVec[anArgIter];
    }
    else
    {
      theDI << "Syntax error at argument '" << theArgVec[anArgIter] << "'\n";
      return 1;
    }
  }
  if (aDocShapeName.IsEmpty() || aFilePath.IsEmpty())
  {
    theDI << "Syntax error: wrong number of arguments\n";
    return 1;
  }
  if (aDoc.IsNull() && !isNoDoc)
  {
    theDI << "Error: incorrect document\n";
    return 1;
  }
  occ::handle<DE_Wrapper>            aConf = DE_Wrapper::GlobalWrapper()->Copy();
  occ::handle<XSControl_WorkSession> aWS   = XSDRAW::Session();
  bool                               aStat = true;
  if (!aConfString.IsEmpty())
  {
    aStat = aConf->Load(aConfString);
  }
  if (aStat)
  {
    if (isNoDoc)
    {
      TopoDS_Shape aShape = DBRep::Get(aDocShapeName);
      if (aShape.IsNull())
      {
        theDI << "Error: incorrect shape " << aDocShapeName << "\n";
        return 1;
      }
      aStat = aConf->Write(aFilePath, aShape, aWS);
    }
    else
    {
      aStat = aConf->Write(aFilePath, aDoc, aWS);
    }
  }
  if (!aStat)
  {
    return 1;
  }
  XSDRAW::CollectActiveWorkSessions(aFilePath);
  return 0;
}

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

void XSDRAWDE::Factory(Draw_Interpretor& theDI)
{
  static bool aIsActivated = false;
  if (aIsActivated)
  {
    return;
  }
  aIsActivated = true;

  //! Ensure DEBREP and DEXCAF plugins are registered
  DECascadeSingleton();

  const char* aGroup = "XDE translation commands";
  theDI.Add("DumpConfiguration",
            "DumpConfiguration [-path <path>] [-recursive {on|off}] [-format fmt1 fmt2 ...] "
            "[-vendor vend1 vend2 ...]\n"
            "\n\t\t: Dump special resource generated from global configuration."
            "\n\t\t:   '-path' - save resource configuration to the file"
            "\n\t\t:   '-recursive' - flag to generate a resource from providers. Default is On. "
            "Off disables other options"
            "\n\t\t:   '-format' - flag to generate a resource for chosen formats. If list is "
            "empty, generate it for all"
            "\n\t\t:   '-vendor' - flag to generate a resource for chosen vendors. If list is "
            "empty, generate it for all",
            __FILE__,
            DumpConfiguration,
            aGroup);
  theDI.Add(
    "LoadConfiguration",
    "LoadConfiguration conf [-recursive {on|off}]\n"
    "\n\t\t:   'conf' - path to the resource file or string value in the special format"
    "\n\t\t:   '-recursive' - flag to generate a resource for all providers. Default is true"
    "\n\t\t: Configure global configuration according special resource",
    __FILE__,
    LoadConfiguration,
    aGroup);
  theDI.Add(
    "CompareConfiguration",
    "CompareConfiguration conf1 conf2\n"
    "\n\t\t:   'conf1' - path to the first resource file or string value in the special format"
    "\n\t\t:   'conf2' - path to the second resource file or string value in the special format"
    "\n\t\t: Compare two configurations",
    __FILE__,
    CompareConfiguration,
    aGroup);
  theDI.Add("ReadFile",
            "ReadFile docName filePath [-conf <value|path>]\n"
            "\n\t\t: Read CAD file to document with registered format's providers. Use global "
            "configuration by default.",
            __FILE__,
            ReadFile,
            aGroup);
  theDI.Add("readfile",
            "readfile shapeName filePath [-conf <value|path>]\n"
            "\n\t\t: Read CAD file to shape with registered format's providers. Use global "
            "configuration by default.",
            __FILE__,
            ReadFile,
            aGroup);
  theDI.Add("WriteFile",
            "WriteFile docName filePath [-conf <value|path>]\n"
            "\n\t\t: Write CAD file to document with registered format's providers. Use global "
            "configuration by default.",
            __FILE__,
            WriteFile,
            aGroup);
  theDI.Add("writefile",
            "writefile shapeName filePath [-conf <value|path>]\n"
            "\n\t\t: Write CAD file to shape with registered format's providers. Use global "
            "configuration by default.",
            __FILE__,
            WriteFile,
            aGroup);

  // Load XSDRAW session for pilot activation
  XSDRAW::LoadDraw(theDI);
}

// Declare entry point PLUGINFACTORY
DPLUGIN(XSDRAWDE)
