// ONELAB - Copyright (C) 2010-2012 C. Geuzaine, F. Henrotte
//
// See the README.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.

#ifndef ONELAB_CLIENTS_H
#define ONELAB_CLIENTS_H

#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <sstream>

#include "OS.h"
#include "onelab.h"
#include "OnelabMessage.h"
#include "StringUtils.h"

// Onelab file extension
static std::string onelabExtension(".ol");
static std::string localFileTag("_");

// Possible actions for clients
enum parseMode { REGISTER, ANALYZE, COMPUTE, EXIT };

// TOOLS

std::string itoa(const int i);
std::string ftoa(const double x);
bool checkIfPresent(std::string fileName);
bool chmod(std::string fileName);
int mySystem(std::string commandLine);
std::string getCurrentWorkdir();
std::string getUserHomedir();
std::string sanitize(const std::string &in);
std::string sanitizeString(const std::string &in, const std::string &forbidden);
std::string removeBlanks(const std::string &in);
std::vector<std::string> SplitOLFileName(const std::string &in);
std::vector<std::string> SplitOLHostName(const std::string &in);
std::string FixOLPath(const std::string &in);
std::string FixPathName(const std::string &in);
std::string FixWindowsQuotes(const std::string &in);
std::string QuoteExecPath(const std::string &in);
std::string unquote(const std::string &in);

// Parser TOOLS
int enclosed(const std::string &in, std::vector<std::string> &arguments,
             size_t &end);
int extract(const std::string &in, std::string &paramName, std::string &action,
            std::vector<std::string> &arguments);
std::string extractExpandPattern(const std::string &str);

typedef std::vector<std::vector<double> > array;
array read_array(std::string fileName, char sep);
double find_in_array(int i, int j,
                     const std::vector<std::vector<double> > &data);

static std::string getShortName(const std::string &name)
{
  std::string s = name;
  // remove path
  std::string::size_type last = name.find_last_of('/');
  if(last != std::string::npos) s = name.substr(last + 1);
  // remove starting numbers
  while(s.size() && s[0] >= '0' && s[0] <= '9') s = s.substr(1);
  return s;
}

class ShortNameLessThan {
public:
  bool operator()(const std::string p1, const std::string p2) const
  {
    return getShortName(p1) < getShortName(p2);
  }
};

void preProcess(const std::string &client, const std::string &fullName);

/*
VIRTUAL and BASE CLASSES

"localSolverClient" is the base class
for all onelab clients called from a metamodel.
It inherits from the "LocalClient" class of "onelab.h"
and has the additional metamodel specific functions:
analyze(), compute(), checkIfPresent(), checkCommandLine(),
inputFile(), outputFile()

The subclass "localSolverNetworkClient" of "localSolverClient"
is for native clients (Gmsh and getDP so far)
that communicate with the server through sockets.
It has a GmshServer member,
uses localNetworkSolverClient::run instead of onelab::client::run().

Both "localSolverNetworkClient" and "localSolverClient" are pure virtual.

Encapsulated clients are interfaced clients called through a gmsh capsule
so that the GUI may remain active.

The base class "remoteClient" is a base class
for clients running on a remote host.
It provides appropriate versions of checkIfPresent() and checkCommandLine().

The combination of native/interfaced/encapsulated and local/remote
yields 6 clients:
NativeClient, RemoteINterfacedClient
InterfacedClient, RemoteInterfacesClient
Encapsulated, RemoteEncapsulated
*/
class localSolverClient : public onelab::localClient {
private:
  std::string _commandLine;
  std::string _workingDir;
  bool _remote;
  int _active;
  bool _onelabBlock;
  std::set<std::string, ShortNameLessThan> _parameters;
  std::string longName(const std::string name);

public:
  localSolverClient(const std::string &name, const std::string &cmdl,
                    const std::string &wdir)
    : onelab::localClient(name), _commandLine(cmdl), _workingDir(wdir),
      _remote(false), _active(0), _onelabBlock(false)
  {
  }
  virtual ~localSolverClient() {}
  const std::string &getCommandLine() { return _commandLine; }
  const std::string &getWorkingDir() { return _workingDir; }
  virtual void setCommandLine(const std::string &s) { _commandLine = s; }
  virtual void setWorkingDir(const std::string &s) { _workingDir = s; }

  void setAction(const std::string action);
  const std::string getString(const std::string what);
  const bool getList(const std::string type, std::vector<std::string> &choices);
  const bool isRemote() { return _remote; }
  const void setRemote(bool flag) { _remote = flag; }
  const bool isActive() { return (bool)_active; }
  const void setActive(int val) { _active = val; }
  int getActive() { return _active; }
  virtual std::string toChar();

  // parser commands
  void modify_tags(const std::string lab, const std::string com);
  const bool isOnelabBlock() { return _onelabBlock; }
  const void openOnelabBlock() { _onelabBlock = true; }
  const void closeOnelabBlock() { _onelabBlock = false; }
  std::string resolveString(const std::string &line);
  std::string resolveGetVal(std::string line);
  bool resolveLogicExpr(std::vector<std::string> arguments);
  bool resolveRange(const std::string &in, std::vector<double> &arguments);
  void parse_sentence(std::string line);
  void parse_oneline(std::string line, std::ifstream &infile);
  bool parse_block(std::ifstream &infile);
  bool parse_ifstatement(std::ifstream &infile, bool condition);
  bool parse_onefile(std::string ifileName, bool mandatory = true);
  void convert_oneline(std::string line, std::ifstream &infile,
                       std::ofstream &outfile);
  bool convert_ifstatement(std::ifstream &infile, std::ofstream &outfile,
                           bool condition);
  void convert_onefile(std::string ifileName, std::ofstream &outfile);
  virtual void client_sentence(const std::string &name,
                               const std::string &action,
                               const std::vector<std::string> &arguments);

  // execution
  bool buildRmCommand(std::string &cmd);
  virtual bool isNative() { return false; }
  void FixExecPath(const std::string &in);
  virtual bool checkCommandLine();
  virtual void analyze() = 0;
  virtual void compute() = 0;
  void addNumberChoice(std::string name, double val, bool readOnly = false);
  void PostArray(std::vector<std::string> choices);
  void GmshMerge(std::vector<std::string> choices);
};

class localNetworkSolverClient : public localSolverClient {
private:
  // command line option to specify socket
  std::string _socketSwitch;
  // pid of the remote network client
  int _pid;
  // underlying GmshServer
  GmshServer *_gmshServer;
  // flag indicating whether socket communication should be monitored
  bool _socketMsg;

public:
  localNetworkSolverClient(const std::string &name, const std::string &cmdl,
                           const std::string &wdir)
    : localSolverClient(name, cmdl, wdir), _socketSwitch("-onelab"), _pid(-1),
      _gmshServer(0), _socketMsg(false)
  {
  }
  virtual ~localNetworkSolverClient() {}
  virtual bool isNetworkClient() { return true; }
  const std::string &getSocketSwitch() { return _socketSwitch; }
  void setSocketSwitch(const std::string &s) { _socketSwitch = s; }
  int getPid() { return _pid; }
  void setPid(int pid) { _pid = pid; }
  GmshServer *getGmshServer() { return _gmshServer; }
  void setGmshServer(GmshServer *server) { _gmshServer = server; }
  bool receiveMessage();

  bool isNative() { return true; }
  virtual std::string buildCommandLine();
  std::string appendArguments();
  virtual bool run();
  virtual bool kill();

  virtual void analyze() = 0;
  virtual void compute() = 0;
};

class remoteClient {
private:
  std::string _remoteHost;
  std::string _remoteDir;

public:
  remoteClient(const std::string &host, const std::string &rdir)
    : _remoteHost(host), _remoteDir(rdir)
  {
  }
  ~remoteClient() {}

  const std::string &getRemoteHost() const { return _remoteHost; }
  const std::string &getRemoteDir() const { return _remoteDir; }

  bool checkCommandLine(const std::string &commandLine);
  bool checkIfPresentRemote(const std::string &fileName);
  bool syncInputFile(const std::string &wdir, const std::string &fileName);
  bool syncOutputFile(const std::string &wdir, const std::string &fileName);
};

// ONELAB CLIENTS

class MetaModel : public localSolverClient {
private:
  // clients in order of appearance in the metamodel
  std::vector<localSolverClient *> _clients;
  // action performed at this metamodel call
  parseMode _todo;
  // remains false as long as the successive clients need no recomputation
  bool _started;

public:
  MetaModel(const std::string &cmdl, const std::string &wdir,
            const std::string &cname, const std::string &fname)
    : localSolverClient(cname, cmdl, wdir)
  {
    clientName = cname;
    genericNameFromArgs = fname.size() ? fname : cmdl;
    setWorkingDir(wdir); // wdir from args
    _todo = REGISTER;
    _started = false;
    construct();
  }
  ~MetaModel() {}
  typedef std::vector<localSolverClient *>::iterator citer;
  void setTodo(const parseMode x) { _todo = x; }
  parseMode getTodo() { return _todo; }
  bool isTodo(const parseMode x) { return (_todo == x); }
  bool isStarted(bool x)
  {
    _started = _started || x;
    return _started;
  }
  citer firstClient() { return _clients.begin(); }
  citer lastClient() { return _clients.end(); }
  int getNumClients() { return _clients.size(); };

  void registerClient(const std::string &name, const std::string &type,
                      const std::string &cmdl, const std::string &host,
                      const std::string &rdir);
  bool checkCommandLines();
  void saveCommandLines();
  bool findCommandLine(const std::string &client, const std::string &host);
  localSolverClient *findClientByName(std::string name)
  {
    for(unsigned int i = 0; i < _clients.size(); i++)
      if(_clients[i]->getName() == name) return _clients[i];
    return 0;
  }
  void showClientStatus()
  {
    for(citer it = firstClient(); it < lastClient(); it++) {
      bool changed = onelab::server::instance()->getChanged((*it)->getName());
      std::cout << "(" << changed << ") " << (*it)->getName() << std::endl;
    }
  }

  std::string genericNameFromArgs, clientName;
  void client_sentence(const std::string &name, const std::string &action,
                       const std::vector<std::string> &arguments);
  std::string toChar() { return ""; }
  void PostArray(std::vector<std::string> choices);
  void construct();
  void analyze();
  void compute();
};

class InterfacedClient : public localSolverClient {
  // n'utilise pas localNetworkSolverClient::run mais client::run()
public:
  InterfacedClient(const std::string &name, const std::string &cmdl,
                   const std::string &wdir)
    : localSolverClient(name, cmdl, wdir)
  {
  }
  ~InterfacedClient() {}

  void analyze();
  void convert();
  virtual void compute();
};

class NativeClient : public localNetworkSolverClient {
public:
  NativeClient(const std::string &name, const std::string &cmdl,
               const std::string &wdir)
    : localNetworkSolverClient(name, cmdl, wdir)
  {
  }
  ~NativeClient() {}

  virtual void analyze();
  virtual void compute();
};

class EncapsulatedClient : public localNetworkSolverClient {
public:
  EncapsulatedClient(const std::string &name, const std::string &cmdl,
                     const std::string &wdir)
    : localNetworkSolverClient(name, cmdl, wdir)
  {
    setSocketSwitch("-lol");
  }
  ~EncapsulatedClient() {}

  std::string buildCommandLine();
  void analyze();
  void convert();
  void compute();
};

class RemoteInterfacedClient
  : public InterfacedClient
  , public remoteClient {
public:
  RemoteInterfacedClient(const std::string &name, const std::string &cmdl,
                         const std::string &wdir, const std::string &host,
                         const std::string &rdir)
    : InterfacedClient(name, cmdl, wdir), remoteClient(host, rdir)
  {
    setRemote(true);
  }
  ~RemoteInterfacedClient() {}

  bool checkCommandLine();
  // uses InterfacedClient::analyze()
  void compute();
};

class RemoteNativeClient
  : public NativeClient
  , public remoteClient {
public:
  RemoteNativeClient(const std::string &name, const std::string &cmdl,
                     const std::string &wdir, const std::string &host,
                     const std::string &rdir)
    : NativeClient(name, cmdl, wdir), remoteClient(host, rdir)
  {
    setRemote(true);
  }
  ~RemoteNativeClient() {}

  std::string buildCommandLine();
  bool checkCommandLine();
  void analyze();
  void compute();
};

class RemoteEncapsulatedClient
  : public EncapsulatedClient
  , public remoteClient {
public:
  RemoteEncapsulatedClient(const std::string &name, const std::string &cmdl,
                           const std::string &wdir, const std::string &host,
                           const std::string &rdir)
    : EncapsulatedClient(name, cmdl, wdir), remoteClient(host, rdir)
  {
    setRemote(true);
  }
  ~RemoteEncapsulatedClient() {}

  std::string buildCommandLine();
  bool checkCommandLine();
  void compute();
};
#endif
