// Gmsh - Copyright (C) 1997-2015 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to the public mailing list <gmsh@geuz.org>.

#include <string>
#include <time.h>
#include "GmshConfig.h"
#include "GmshVersion.h"
#include "GmshMessage.h"
#include "GmshDefines.h"
#include "GmshRemote.h"
#include "GModel.h"
#include "OpenFile.h"
#include "CreateFile.h"
#include "Options.h"
#if defined(HAVE_PARSER)
#include "Parser.h"
#endif
#include "CommandLine.h"
#include "OS.h"
#include "Context.h"
#include "robustPredicates.h"
#if defined(HAVE_POST)
#include "PView.h"
#endif

#if defined(HAVE_ONELAB) && !defined(HAVE_ONELAB2)
#include "gmshLocalNetworkClient.h"
#endif

#if defined(HAVE_MESH)
#include "Generator.h"
#include "Field.h"
#include "meshPartition.h"
#endif

#if defined(HAVE_PLUGINS)
#include "PluginManager.h"
#endif

#if defined(HAVE_FLTK)
#include "FlGui.h"
#include "graphicWindow.h"
#include "drawContext.h"
#if defined(HAVE_ONELAB2)
#include "onelab2Group.h"
#else
#include "onelabGroup.h"
#endif
#endif

#if defined(HAVE_ONELAB2)
#include "OnelabServer.h"
#include "OnelabDatabase.h"
#include "OnelabException.h"
#endif

int GmshInitialize(int argc, char **argv)
{
  static bool isInitialized = false;
  if(isInitialized) return 1;
  isInitialized = true;

#if defined(HAVE_FLTK)
  RedirectIOToConsole();
#endif

  // we need at least one model during option parsing
  GModel *dummy = 0;
  if(GModel::list.empty()) dummy = new GModel();

  // Initialize messages (parallel stuff, etc.)
  Msg::Init(argc, argv);

  // Load default options
  InitOptions(0);

  // Read configuration files and command line options
  GetOptions(argc, argv);

  // Make sure we have enough resources (stack)
  CheckResources();

#if defined(HAVE_PLUGINS)
  // Initialize the default plugins
  PluginManager::instance()->registerDefaultPlugins();
#endif

  // Initialize robust predicates
  robustPredicates::exactinit();

  if(dummy) delete dummy;
  return 1;
}

int GmshSetMessageHandler(GmshMessage *callback)
{
  Msg::SetCallback(callback);
  return 1;
}

GmshMessage *GmshGetMessageHandler()
{
  return Msg::GetCallback();
}

int GmshSetBoundingBox(double xmin, double xmax,
                       double ymin, double ymax,
                       double zmin, double zmax)
{
  SetBoundingBox(xmin, xmax, ymin, ymax, zmin, zmax);
  return 1;
}

int GmshSetOption(const std::string &category, const std::string &name,
                  std::string value, int index)
{
  return StringOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

int GmshSetOption(const std::string &category, const std::string &name,
                  double value, int index)
{
  return NumberOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

int GmshSetOption(const std::string &category, const std::string &name,
                  unsigned int value, int index)
{
  return ColorOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

void GmshSetStringOption(const std::string &category, const std::string &name,
                         std::string value, int index)
{
  StringOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

void GmshSetNumberOption(const std::string &category, const std::string &name,
                         double value, int index)
{
  NumberOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

void GmshSetColorOption(const std::string &category, const std::string &name,
                        unsigned int value, int index)
{
  ColorOption(GMSH_SET|GMSH_GUI, category.c_str(), index, name.c_str(), value);
}

int GmshGetOption(const std::string &category, const std::string &name,
                  std::string &value, int index)
{
  return StringOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
}

int GmshGetOption(const std::string &category, const std::string &name,
                  double &value, int index)
{
  return NumberOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
}

int GmshGetOption(const std::string &category, const std::string &name,
                  unsigned int &value, int index)
{
  return ColorOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
}

std::string GmshGetStringOption(const std::string &category, const std::string &name,
                                int index)
{
  std::string value;
  StringOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
  return value;
}

double GmshGetNumberOption(const std::string &category, const std::string &name,
                           int index)
{
  double value;
  NumberOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
  return value;
}

int GmshGetColorOption(const std::string &category, const std::string &name,
                       int index)
{
  unsigned int value;
  ColorOption(GMSH_GET, category.c_str(), index, name.c_str(), value);
  return value;
}

int GmshRestoreDefaultOptions()
{
  ReInitOptions(0);
  InitOptionsGUI(0);
  return 1;
}

int GmshOpenProject(const std::string &fileName)
{
  OpenProject(fileName);
  return 1;
}

int GmshClearProject()
{
  ClearProject();
  return 1;
}

int GmshMergeFile(const std::string &fileName)
{
  return MergeFile(fileName, true);
}

int GmshMergePostProcessingFile(const std::string &fileName)
{
  return MergePostProcessingFile(fileName, CTX::instance()->solver.autoShowViews,
                                 CTX::instance()->solver.autoShowLastStep, true);
}

int GmshWriteFile(const std::string &fileName)
{
  CreateOutputFile(fileName, FORMAT_AUTO);
  return 1;
}

int GmshFinalize()
{
  return 1;
}

int GmshBatch()
{
  Msg::Info("Running '%s' [Gmsh %s, %d node%s, max. %d thread%s]",
            Msg::GetCommandLineArgs().c_str(), GMSH_VERSION,
            Msg::GetCommSize(), Msg::GetCommSize() > 1 ? "s" : "",
            Msg::GetMaxThreads(), Msg::GetMaxThreads() > 1 ? "s" : "");
  Msg::Info("Started on %s", Msg::GetLaunchDate().c_str());
#if defined(HAVE_ONELAB2)
  try {
    if(CTX::instance()->onelab.unixSock.size() > 0) { // UNIX
      std::ostringstream tmp;
      tmp << CTX::instance()->homeDir << CTX::instance()->onelab.unixSock;
      OnelabServer::instance()->listenOnUnix(tmp.str().c_str());
    }
    if(CTX::instance()->onelab.tcpSock.size() > 0) {
      std::size_t colon = CTX::instance()->onelab.tcpSock.find(":");
      OnelabServer::instance()->listenOnTcp(
        ip4_inet_pton(CTX::instance()->onelab.tcpSock.substr(0, colon).c_str()),
        atoi(CTX::instance()->onelab.tcpSock.substr(colon+1, CTX::instance()->onelab.tcpSock.size()-colon-1).c_str()));
    }
#if defined(HAVE_UDT)
    if(CTX::instance()->onelab.udtSock.size() > 0) {
      std::size_t colon = CTX::instance()->onelab.tcpSock.find(":");
      OnelabServer::instance()->listenOnUdt(
        ip4_inet_pton(CTX::instance()->onelab.tcpSock.substr(0, colon).c_str()),
        atoi(CTX::instance()->onelab.tcpSock.substr(colon+1, CTX::instance()->onelab.tcpSock.size()-colon-1).c_str()));
    }
#endif
  } catch(NetworkException e) {
    std::cout << e.what() << std::endl;
    Msg::Exit(1);
  }
  if(CTX::instance()->onelab.unixSock.size() > 0
    || CTX::instance()->onelab.tcpSock.size() > 0
    || CTX::instance()->onelab.udtSock.size() > 0) {
    std::cout << "Press any key to stop the server." << std::endl;
    std::cin.get();
    Msg::Exit(0);
  }
#endif

  OpenProject(GModel::current()->getFileName());
  bool open = false;
  for(unsigned int i = 0; i < CTX::instance()->files.size(); i++){
    if(i == 0 && CTX::instance()->files[0][0] != '-') continue;
    if(CTX::instance()->files[i] == "-new")
      new GModel();
    else if(CTX::instance()->files[i] == "-merge")
      open = false;
    else if(CTX::instance()->files[i] == "-open")
      open = true;
    else if(open)
      OpenProject(CTX::instance()->files[i]);
    else
      MergeFile(CTX::instance()->files[i]);
  }


#if defined(HAVE_POST) && defined(HAVE_MESH)
  if(!CTX::instance()->bgmFileName.empty()) {
    MergePostProcessingFile(CTX::instance()->bgmFileName);
    if(PView::list.size())
      GModel::current()->getFields()->setBackgroundMesh(PView::list.size() - 1);
    else
      Msg::Error("Invalid background mesh (no view)");
  }
#endif

  if(CTX::instance()->batch == -3){
#if !defined(HAVE_ONELAB2)
    GmshRemote();
#endif
  }
  else if(CTX::instance()->batch == -2){
    GModel::current()->checkMeshCoherence(CTX::instance()->geom.tolerance);
#if defined(HAVE_PARSER)
    std::vector<std::string> s;
    PrintParserSymbols(0, s);
    for(unsigned int i = 0; i < s.size(); i++)
      Msg::Direct("%s", s[i].c_str());
#endif
  }
  else if(CTX::instance()->batch == -1){
    CreateOutputFile(CTX::instance()->outputFileName,
                     CTX::instance()->outputFileName.empty() ? FORMAT_GEO :
                     FORMAT_AUTO);
  }
  else if(CTX::instance()->batch > 0){
#if defined(HAVE_MESH)
    if(CTX::instance()->batch < 4)
      GModel::current()->mesh(CTX::instance()->batch);
    else if(CTX::instance()->batch == 4)
      AdaptMesh(GModel::current());
    else if(CTX::instance()->batch == 5)
      RefineMesh(GModel::current(), CTX::instance()->mesh.secondOrderLinear);
#if defined(HAVE_CHACO) || defined(HAVE_METIS)
    if(CTX::instance()->batchAfterMesh == 1){
      if (CTX::instance()->partitionOptions.num_partitions > 1)
        PartitionMesh(GModel::current(), CTX::instance()->partitionOptions);
      if (CTX::instance()->partitionOptions.renumber)
        RenumberMesh(GModel::current(), CTX::instance()->partitionOptions);
    }
#endif
#endif
    std::string name = CTX::instance()->outputFileName;
    if(name.empty()){
      if(CTX::instance()->mesh.fileFormat == FORMAT_AUTO)
        name = GetDefaultFileName(FORMAT_MSH);
      else
        name = GetDefaultFileName(CTX::instance()->mesh.fileFormat);
    }
    CreateOutputFile(name, CTX::instance()->mesh.fileFormat);
  }

  // launch solver (if requested)
#if defined(HAVE_ONELAB)
  solver_batch_cb((void*)CTX::instance()->launchSolverAtStartup);
#endif

  time_t now;
  time(&now);
  std::string currtime = ctime(&now);
  currtime.resize(currtime.size() - 1);
  Msg::Info("Stopped on %s", currtime.c_str());

  return 1;
}

int GmshFLTK(int argc, char **argv)
{
#if defined(HAVE_FLTK) && defined(HAVE_POST)
  // create the GUI
  FlGui::instance(argc, argv);

  // display GUI immediately for quick launch time
  FlGui::instance()->check();

  // open project file and merge all other input files
  if(FlGui::getOpenedThroughMacFinder().empty()){
    OpenProject(GModel::current()->getFileName());
    bool open = false;
    for(unsigned int i = 0; i < CTX::instance()->files.size(); i++){
      if(i == 0 && CTX::instance()->files[0][0] != '-') continue;
      if(CTX::instance()->files[i] == "-new"){
        GModel::current()->setVisibility(0);
        new GModel();
      }
      else if(CTX::instance()->files[i] == "-merge")
        open = false;
      else if(CTX::instance()->files[i] == "-open")
        open = true;
      else if(open)
        OpenProject(CTX::instance()->files[i]);
      else
        MergeFile(CTX::instance()->files[i]);
    }
  }
  else{
    OpenProject(FlGui::getOpenedThroughMacFinder());
  }

  if(CTX::instance()->post.combineTime){
    PView::combine(true, 2, CTX::instance()->post.combineRemoveOrig);
    FlGui::instance()->updateViews(true, true);
  }

  // init first context
  switch (CTX::instance()->initialContext) {
  case 1: FlGui::instance()->openModule("Geometry"); break;
  case 2: FlGui::instance()->openModule("Mesh"); break;
  case 3: FlGui::instance()->openModule("Solver"); break;
  case 4: FlGui::instance()->openModule("Post-processing"); break;
  default: // automatic
    if(PView::list.size()) FlGui::instance()->openModule("Post-processing");
    break;
  }

  // read background mesh if any
  if(!CTX::instance()->bgmFileName.empty()) {
    MergePostProcessingFile(CTX::instance()->bgmFileName);
    if(PView::list.size())
      GModel::current()->getFields()->setBackgroundMesh(PView::list.size() - 1);
    else
      Msg::Error("Invalid background mesh (no view)");
  }
#if !defined(HAVE_ONELAB2)
  // listen to external solvers
  if(CTX::instance()->solver.listen){
    gmshLocalNetworkClient *c = new gmshLocalNetworkClient("Listen", "");
    c->run();
  }
#endif

  // launch solver (if requested) and fill onelab tree
  solver_cb(0, (void*)CTX::instance()->launchSolverAtStartup);

  // loop
  return FlGui::instance()->run();
#else
  Msg::Error("GmshFLTK unavailable: please recompile with FLTK support");
  return 0;
#endif
}
