// Created on: 2017-06-16
// Created by: Natalia ERMOLAEVA
// Copyright (c) 2017 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 <inspector/ToolsDraw.hxx>

#include <AIS_InteractiveContext.hxx>
#include <BRep_Builder.hxx>
#include <BRepTools.hxx>
#include <DBRep.hxx>
#include <DDocStd.hxx>
#include <Draw.hxx>
#include <Draw_PluginMacro.hxx>
#include <NCollection_DataMap.hxx>
#include <Standard_Stream.hxx>
#include <TDocStd_Application.hxx>
#include <TopoDS_Shape.hxx>
#include <ViewerTest.hxx>
#include <ViewerTest_DoubleMapOfInteractiveAndName.hxx>

#include <inspector/TInspectorAPI_PluginParameters.hxx>
#include <inspector/TInspector_Communicator.hxx>

#if ! defined(_WIN32)
extern ViewerTest_DoubleMapOfInteractiveAndName& GetMapOfAIS();
#else
Standard_EXPORT ViewerTest_DoubleMapOfInteractiveAndName& GetMapOfAIS();
#endif

static TInspector_Communicator* MyCommunicator;

// =======================================================================
// function : Communicator
// purpose  : defines plugin library name by the command argument
// =======================================================================
TInspector_Communicator* ToolsDraw::Communicator()
{
  return MyCommunicator;
}

// =======================================================================
// function : convertToPluginName
// purpose  : defines plugin library name by the command argument
// =======================================================================
Standard_Boolean convertToPluginName (const TCollection_AsciiString& theArgument,
                                      TCollection_AsciiString& thePluginName)
{
  TCollection_AsciiString anArgument = theArgument;
  anArgument.LowerCase();

  if (anArgument == "dfbrowser")       { thePluginName = "TKDFBrowser"; return Standard_True; }
  else if (anArgument == "shapeview")  { thePluginName = "TKShapeView"; return Standard_True; }
  else if (anArgument == "vinspector") { thePluginName = "TKVInspector"; return Standard_True; }
  else if (anArgument == "messageview") { thePluginName = "TKMessageView"; return Standard_True; }

  return Standard_False;
}

// =======================================================================
// function : getArgumentPlugins
// purpose  : fills container of plugin names by the next following plugin names
// =======================================================================
void getArgumentPlugins (Standard_Integer theArgsNb, const char** theArgs, Standard_Integer& theIt,
                         NCollection_List<TCollection_AsciiString>& thePlugins)
{
  while (theIt != theArgsNb)
  {
    TCollection_AsciiString aPluginName;
    if (convertToPluginName (theArgs[theIt], aPluginName))
    {
      if (!thePlugins.Contains (aPluginName))
        thePlugins.Append (aPluginName);
    }
    else
    {
      break;
    }
    theIt++;
  }
  theIt--; // the last not processed parameter is the next argument
}

// =======================================================================
// function : tinspector
// purpose  : 
// =======================================================================
static int tinspector (Draw_Interpretor& di, Standard_Integer theArgsNb, const char** theArgs)
{
  if (theArgsNb < 1)
  {
    std::cout << "Error: wrong number of arguments.\n";
    return 1;
  }

  // parse command arguments
  NCollection_List<TCollection_AsciiString> aPlugins;
  NCollection_DataMap<TCollection_AsciiString, NCollection_List<Handle(Standard_Transient)> > aParameters;
  NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString > anOpenFileParameters;
  TCollection_AsciiString aPluginNameToActivate;
  Standard_Boolean aNeedToUpdateContent = Standard_False,
                   aNeedToHideInspector = Standard_False,
                   aNeedToShowInspector = Standard_False,
                   aNeedToPrintState = Standard_False,
                   aNeedDirectory = Standard_False;
  TCollection_AsciiString aTemporaryDirectory;

  NCollection_List<Handle(Standard_Transient)> aDefaultParameters;
  TCollection_AsciiString aDefaultOpenFileParameter;

  NCollection_List<Handle(Standard_Transient)> anObjectsToSelect;
  NCollection_List<TCollection_AsciiString> anItemNamesToSelect;

  for (Standard_Integer anIt = 1; anIt < theArgsNb; ++anIt)
  {
    TCollection_AsciiString aParam (theArgs[anIt]);
    aParam.LowerCase();

    if (aParam.IsEqual ("-plugins")) // [-plugins {name1 [name2] ... [name3] | all}]
    {
      anIt++;
      getArgumentPlugins (theArgsNb, theArgs, anIt, aPlugins);
    }
    else if (aParam.IsEqual ("-activate")) // [-activate name]
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      TCollection_AsciiString aPluginName;
      if (convertToPluginName (theArgs[anIt], aPluginName))
        aPluginNameToActivate = aPluginName;
    }
    else if (aParam.IsEqual ("-shape")) // [-shape object [name1] ... [nameN]]
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      TopoDS_Shape aShape = DBRep::Get (theArgs[anIt]);
      anIt++;
      if (aShape.IsNull())
      {
        std::cout << "Wrong shape name: " << aParam << ".\n";
        return 1;
      }
      NCollection_List<TCollection_AsciiString> anArgPlugins;
      getArgumentPlugins (theArgsNb, theArgs, anIt, anArgPlugins);

      if (anArgPlugins.IsEmpty())
      {
        aDefaultParameters.Append (aShape.TShape());
        anItemNamesToSelect.Append (TInspectorAPI_PluginParameters::ParametersToString (aShape));
      }
      else
      {
        for (NCollection_List<TCollection_AsciiString>::Iterator anArgIt (anArgPlugins);
             anArgIt.More(); anArgIt.Next())
        {
          NCollection_List<Handle(Standard_Transient)> aPluginParameters;
          aParameters.Find (anArgIt.Value(), aPluginParameters);
          aPluginParameters.Append (aShape.TShape());
          anItemNamesToSelect.Append (TInspectorAPI_PluginParameters::ParametersToString (aShape));
          aParameters.Bind (anArgIt.Value(), aPluginParameters);
        }
      }
    }
    else if (aParam.IsEqual ("-open")) // [-open file_name [name1] ... [nameN]]
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      TCollection_AsciiString aFileName (theArgs[anIt]);
      anIt++;

      NCollection_List<TCollection_AsciiString> anArgPlugins;
      getArgumentPlugins(theArgsNb, theArgs, anIt, anArgPlugins);
      if (anArgPlugins.IsEmpty())
        aDefaultOpenFileParameter = aFileName;
      else
      {
        for (NCollection_List<TCollection_AsciiString>::Iterator anArgIt (anArgPlugins);
          anArgIt.More(); anArgIt.Next())
        {
          NCollection_List<Handle(Standard_Transient)> aPluginParameters;
          aParameters.Find (anArgIt.Value(), aPluginParameters);
          anOpenFileParameters.Bind (anArgIt.Value(), aFileName);
        }
      }
    }
    else if (aParam.IsEqual ("-directory")) // [-directory path]"
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      aNeedDirectory = true;
      aParam = theArgs[anIt];
      aTemporaryDirectory = aParam.IsEqual ("default") ? "" : aParam;
    }
    else if (aParam.IsEqual ("-state")) // [-state]
    {
      aNeedToPrintState = Standard_True;
    }
    else if (aParam.IsEqual ("-update")) // [-update]
    {
      aNeedToUpdateContent = Standard_True;
    }
    else if (aParam.IsEqual ("-select")) // [-select {name|object}]
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      // search shape with given name
      TopoDS_Shape aShape = DBRep::Get (theArgs[anIt]);
      if (!aShape.IsNull())
      {
        anObjectsToSelect.Append (aShape.TShape());
        anItemNamesToSelect.Append (TInspectorAPI_PluginParameters::ParametersToString (aShape));
      }
      // search prsentations with given name
      Handle(AIS_InteractiveObject) anIO;
      GetMapOfAIS().Find2 (theArgs[anIt], anIO);
      if (!anIO.IsNull())
      {
        anObjectsToSelect.Append (anIO);
      }
      // give parameters as a container of names
      aParam = TCollection_AsciiString (theArgs[anIt]);
      while (!aParam.StartsWith ("-"))
      {
        anItemNamesToSelect.Append (aParam);
        anIt++;
        if (anIt >= theArgsNb)
          break;
        aParam = theArgs[anIt];
      }
      anIt--;
    }
    else if (aParam.IsEqual ("-show")) // [-show {0|1} = 1]
    {
      anIt++;
      if (anIt == theArgsNb)
      {
        std::cout << "Empty argument of '" << aParam << "'.\n";
        return 1;
      }
      aNeedToHideInspector = Draw::Atoi (theArgs[anIt]) == 0;
      aNeedToShowInspector = Draw::Atoi (theArgs[anIt]) > 0;
    }
    else
    {
      std::cout << "Wrong argument of command: " << aParam.ToCString() << "\n";
      return 1;
    }
  }

  // start inspector
  Standard_Boolean isTInspectorCreation = !MyCommunicator;
  if (!MyCommunicator)
    MyCommunicator = new TInspector_Communicator();

  Handle(AIS_InteractiveContext) aContext = ViewerTest::GetAISContext();
  if (!aContext.IsNull())
    aDefaultParameters.Append (aContext);

  // Sets OCAF application into DFBrowser
  const Handle(TDocStd_Application)& anApplication = DDocStd::GetApplication();
  // Initialize standard document formats at creation - they should
  // be available even if this DRAW plugin is not loaded by pload command
  if (!anApplication.IsNull())
  {
    NCollection_List<Handle(Standard_Transient)> aDFBrowserParameters;
    aParameters.Find ("TKDFBrowser", aDFBrowserParameters);
    aDFBrowserParameters.Append (anApplication);
    aParameters.Bind ("TKDFBrowser", aDFBrowserParameters);
  }

  // by starting, if the plugns were not defined, register all
  if (isTInspectorCreation)
  {
    if (aPlugins.IsEmpty())
    {
      aPlugins.Append ("TKDFBrowser");
      aPlugins.Append ("TKShapeView");
      aPlugins.Append ("TKVInspector");
      aPlugins.Append ("TKMessageView");
    }
    aPluginNameToActivate = !aPluginNameToActivate.IsEmpty() ? aPluginNameToActivate : aPlugins.First();
  }

  // register plugin from parameters
  for (NCollection_List<TCollection_AsciiString>::Iterator aPluginNameIt (aPlugins);
       aPluginNameIt.More(); aPluginNameIt.Next())
    MyCommunicator->RegisterPlugin (aPluginNameIt.Value());

  // init all registered plugins with the default and parameters values
  NCollection_List<TCollection_AsciiString> aRegisteredPlugins = MyCommunicator->RegisteredPlugins();
  for (NCollection_List<TCollection_AsciiString>::Iterator anIterator (aRegisteredPlugins);
    anIterator.More(); anIterator.Next())
  {
    TCollection_AsciiString aPluginName = anIterator.Value();
    NCollection_List<Handle(Standard_Transient)> aParameterValues;
    aParameters.Find (aPluginName, aParameterValues);

    for (NCollection_List<Handle(Standard_Transient)>::Iterator aDefIt (aDefaultParameters);
         aDefIt.More(); aDefIt.Next())
      aParameterValues.Append (aDefIt.Value());
    MyCommunicator->Init (aPluginName, aParameterValues, Standard_True);
  }

  if (!aPluginNameToActivate.IsEmpty())
    MyCommunicator->Activate (!aPluginNameToActivate.IsEmpty() ? aPluginNameToActivate : aPlugins.First());

  if (!anOpenFileParameters.IsEmpty())
  {
    for (NCollection_DataMap<TCollection_AsciiString, TCollection_AsciiString >::Iterator anOpenIt
      (anOpenFileParameters); anOpenIt.More(); anOpenIt.Next())
      MyCommunicator->OpenFile (anOpenIt.Key(), anOpenIt.Value());
  }
  else if (!aDefaultOpenFileParameter.IsEmpty()) // open file in active plugin
    MyCommunicator->OpenFile ("", aDefaultOpenFileParameter);

  if (!anObjectsToSelect.IsEmpty())
    MyCommunicator->SetSelected (anObjectsToSelect);

  if (!anItemNamesToSelect.IsEmpty())
    MyCommunicator->SetSelected (anItemNamesToSelect);

  if (aNeedDirectory)
    MyCommunicator->SetTemporaryDirectory (aTemporaryDirectory);

  if (aNeedToUpdateContent)
    MyCommunicator->UpdateContent();

  if (isTInspectorCreation || aNeedToShowInspector)
    MyCommunicator->SetVisible (true);

  if (aNeedToHideInspector)
    MyCommunicator->SetVisible (false);

  if (aNeedToPrintState)
  {
    Standard_SStream aSStream;
    MyCommunicator->Dump (aSStream);
    di << aSStream << "\n";
  }

  return 0;
}

// =======================================================================
// function : Commands
// purpose  : 
// =======================================================================
void ToolsDraw::Commands(Draw_Interpretor& theCommands)
{
  const char *group = "Tools";

  // display
  theCommands.Add ("tinspector",
    "tinspector [-plugins {name1 ... [nameN] | all}]"
    "\n\t\t:            [-activate name]"
    "\n\t\t:            [-shape object [name1] ... [nameN]]"
    "\n\t\t:            [-open file_name [name1] ... [nameN]]"
    "\n\t\t:            [-update]"
    "\n\t\t:            [-select {object | name1 ... [nameN]}]"
    "\n\t\t:            [-show {0|1} = 1]"
    "\n\t\t:            [-directory path|<default>]"
    "\n\t\t:            [-state]"
    "\n\t\t: Starts tool of inspection."
    "\n\t\t: Options:"
    "\n\t\t:  -plugins enters plugins that should be added in the inspector."
    "\n\t\t:           Available names are: dfbrowser, vinspector, shapeview and messageview."
    "\n\t\t:           Plugins order will be the same as defined in arguments."
    "\n\t\t:           'all' adds all available plugins in the order:"
    "\n\t\t:                 DFBrowser, VInspector, ShapeView and MessageView."
    "\n\t\t:           If at the first call this option is not used, 'all' option is applied;"
    "\n\t\t:  -activate activates the plugin in the tool view."
    "\n\t\t:           If at the first call this option is not used, the first plugin is activated;"
    "\n\t\t:  -shape initializes plugin/s by the shape object. If 'name' is empty, initializes all plugins;"
    "\n\t\t:  -open gives the file to the plugin/s. If the plugin is active, after open, update content will be done;"
    "\n\t\t:  -update updates content of the active plugin;"
    "\n\t\t:  -select sets the parameter that should be selected in an active tool view."
    "\n\t\t:          Depending on active tool the parameter is:"
    "\n\t\t:          ShapeView: 'object' is an instance of TopoDS_Shape TShape,"
    "\n\t\t:          DFBrowser: 'name' is an entry of TDF_Label and name2(optionally) for TDF_Attribute type name,"
    "\n\t\t:          VInspector: 'object' is an instance of AIS_InteractiveObject;"
    "\n\t\t:  -show sets Inspector view visible or hidden. The first call of this command will show it."
    "\n\t\t:  -directory sets Inspector temporary directory. Preferences file is stored there."
    "\n\t\t:  -state print some current information about inspector, like name of active plugin, temporary director.",
      __FILE__, tinspector, group);

}

// =======================================================================
// function : Factory
// purpose  : 
// =======================================================================
void ToolsDraw::Factory (Draw_Interpretor& theDI)
{
  // definition of Tools Command
  ToolsDraw::Commands (theDI);

#ifdef OCCT_DEBUG
      theDI << "Draw Plugin : OCC Tools commands are loaded\n";
#endif
}

// Declare entry point PLUGINFACTORY
DPLUGIN (ToolsDraw)
