//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2016 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include <Process.hpp>
#include <Version.hpp>
#include <ImageData.hpp>
#include <PrivateProcess.hpp>
#include <Forall.hpp>
#include <QTextStream>
#include <stdio.h>
#include <ProcessThread.hpp>
#include <QMutexLocker>
#include <Information.hpp>
#include <memory>
#include <exception>
#include <Dir.hpp>
#include <Misc.hpp>

#include <fstream>

static QTextStream err(stderr);

namespace mgx 
{
  std::vector<Colorf> &Process::LabelColors = ImgData::LabelColors;

  unsigned int Process::processVersion = PROCESS_VERSION;
  
  Process::Process() : p(0) {}
  
  Process::Process(const Process &proc) : p(proc.p) {}

  bool Process::setCurrentParms()
  {
    ProcessDefinition* def = getProcessDefinition(name());
    if(def) {
      setParms(def->parms);
      return true;
    }
    return false;
  }

  QString Process::pythonCall(const QStringList &parms) const
  {
    QStringList all_parms;
    forall(QString s, parms)
      all_parms << QString("'%1'").arg(shield_python(s));
    QString name = this->name();
    name.replace('/', "__");
    name.replace(' ', "_");
    return QString("Process.%1(%2)").arg(name).arg(all_parms.join(", "));
  }
  
  QString Process::actingFile() const 
  {
    return p->actingFile;
  }
  
  void Process::actingFile(const QString& filename, bool project_file)
  {
    if(!filename.isEmpty() and (project_file or file().isEmpty())) {
      QString dir = getDir(filename);
      bool changed_dir = (dir != currentPath());
      setCurrentPath(dir);
      p->actingFile = filename;
      if((project_file and file().isEmpty()) or changed_dir) {
        QFile f("MorphoGraphX.py");
        if(f.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
          QTextStream ss(&f);
          QDateTime now = QDateTime::currentDateTime();
          ss << "######## New MorphoGraphX session v" << VERSION << " r" << REVISION << ": "
             << now.toString("yyyy-MM-dd hh:mm:ss") << endl;
          ss << p->currentPythonCall << endl;
          p->changedFolder = true;
          f.close();
        }
      }
    }
  }
  
  int Process::stackCount() const 
  {
    return p->_stacks.size();
  }
  
  std::pair<Process::stack_iterator, Process::stack_iterator> Process::stacks()
  {
    return std::make_pair(p->_stacks.begin(), p->_stacks.end());
  }
  
  std::pair<Process::const_stack_iterator, Process::const_stack_iterator> Process::stacks() const
  {
    return std::make_pair(p->_stacks.begin(), p->_stacks.end());
  }
  
  Stack* Process::stack(int i)
  {
    if(i == -1)
      return currentStack();
    if(i < 0 or i >= (int)p->_stacks.size())
      return 0;
    return p->_stacks[i];
  }
  
  Stack* Process::currentStack()
  {
    if(currentStackId() == -1)
      return 0;
    return p->_stacks[currentStackId()];
  }
  
  Stack* Process::otherStack()
  {
    if(otherStackId() < 0)
      return 0;
    return p->_stacks[otherStackId()];
  }
  
  int Process::currentStackId() const 
  {
    return p->current_stack;
  }
  
  int Process::otherStackId() const 
  {
    if(p->current_stack < 0)
			return -1;
		else if(p->current_stack == 0)
		  return 1;
		else if(p->current_stack == 1)
		  return 0;
		
		return -1;
  }
  
  void Process::setCurrentStackId(int i)
  {
    if(i >= -1 and i < (int)p->_stacks.size())
      p->current_stack = i;
  }
  
  Stack* Process::addStack()
  {
    Stack* s = new Stack((int)p->_stacks.size());
    p->_stacks.push_back(s);
    return s;
  }
  
  bool Process::deleteStack(int i)
  {
    if(i >= 0 and i < (int)p->_stacks.size()) {
      delete p->_stacks[i];
      p->_stacks[i] = 0;
      p->_stacks.erase(p->_stacks.begin() + i);
      for(int j = 0; j < (int)p->_stacks.size(); ++j)
        p->_stacks[j]->setId(j);
      return true;
    }
    return false;
  }
  
  int Process::meshCount() const {
    return p->_meshes.size();
  }
  
  std::pair<Process::mesh_iterator, Process::mesh_iterator> Process::meshes()
  {
    return std::make_pair(p->_meshes.begin(), p->_meshes.end());
  }
  
  std::pair<Process::const_mesh_iterator, Process::const_mesh_iterator> Process::meshes() const
  {
    return std::make_pair(p->_meshes.begin(), p->_meshes.end());
  }
  
  Mesh* Process::mesh(int i)
  {
    if(i < 0 or uint(i) >= p->_meshes.size())
      return currentMesh();
    return p->_meshes[i];
  }
  
  Mesh* Process::currentMesh()
  {
    if(currentMeshId() == -1)
      return 0;
    return p->_meshes[currentMeshId()];
  }
  
  Mesh* Process::otherMesh()
  {
    if(otherMeshId() < 0)
      return 0;
    return p->_meshes[otherMeshId()];
  }
  
  int Process::currentMeshId() const {
    return p->current_mesh;
  }
  
  int Process::otherMeshId() const {
    if(p->current_mesh < 0)
			return -1;
		else if(p->current_mesh == 0)
		  return 1;
		else if(p->current_mesh == 1)
		  return 0;

    return -1;
  }
  
  void Process::setCurrentMeshId(int i)
  {
    if(i >= -1 and i < (int)p->_meshes.size())
      p->current_mesh = i;
  }
  
  Mesh* Process::addMesh(const Stack* stack)
  {
    Mesh* m = new Mesh((int)p->_meshes.size(), stack);
    p->_meshes.push_back(m);
    return m;
  }
  
  bool Process::deleteMesh(int i)
  {
    if(i >= 0 and i < (int)p->_meshes.size()) {
      delete p->_meshes[i];
      p->_meshes[i] = 0;
      p->_meshes.erase(p->_meshes.begin() + i);
      for(int j = 0; j < (int)p->_meshes.size(); ++j)
        p->_meshes[j]->setId(j);
      return true;
    }
    return false;
  }
  
  int Process::selectedLabel() const {
    return p->selected_label;
  }
  
  void Process::setSelectedLabel(int label) {
    p->selected_label = label;
  }
  
  float Process::globalBrightness() {
    return p->globalBrightness;
  }
  float Process::globalContrast() {
    return p->globalContrast;
  }
  float Process::globalShininess() {
    return p->globalShininess;
  }
  float Process::globalSpecular() {
    return p->globalSpecular;
  }
  
  
  
  void Process::setGlobalBrightness(float value)
  {
    if(value > 1.0)
      p->globalBrightness = 1.0f;
    else if(value < -1)
      p->globalBrightness = -1.0f;
    else
      p->globalBrightness = value;
  }
  
  void Process::setGlobalContrast(float value)
  {
    if(value > 2.0)
      p->globalContrast = 2.0f;
    else if(value < 0)
      p->globalContrast = 0.0f;
    else
      p->globalContrast = value;
  }
  
  void Process::setGlobalShininess(float value)
  {
    if(value > 128.0)
      p->globalShininess = 128.0f;
    else if(value < 0)
      p->globalShininess = 0.0f;
    else
      p->globalShininess = value;
  }
  
  void Process::setGlobalSpecular(float value)
  {
    if(value > 1.0)
      p->globalSpecular = 1.0f;
    else if(value < 0)
      p->globalSpecular = 0.0f;
    else
      p->globalSpecular = value;
  }
  
  bool Process::meshSelection() const {
    return p->mesh_selection;
  }
  
  bool Process::lineBorderSelection() const {
    return p->line_border_selection;
  }
  
  bool Process::setErrorMessage(const QString& str)
  {
    p->error = str;
    return false;
  }
  
  QString Process::errorMessage() const { return p->error; }
  
  void Process::setWarningMessage(const QString& str) { p->warning = str; }
  
  QString Process::warningMessage() const { return p->warning; }
  
  bool Process::updateState()
  {
    return systemCommand(UPDATE_STATE, QStringList());
  }

  bool Process::updateViewer() 
  {
    return systemCommand(UPDATE_VIEWER, QStringList());
  }

  bool Process::loadView(const QString &fileName) 
  {
    return systemCommand(LOAD_VIEW, QStringList() << fileName);
  } 

  bool Process::saveView(const QString &fileName) 
  {
    return systemCommand(SAVE_VIEW, QStringList() << fileName);
  } 

  bool Process::setCurrentStack(int id, const QString &store)
  {
    if(id >= stackCount()) {
      setErrorMessage(
        QString("There are only %1 stacks, cannot made stack %2 the current one")
                                                       .arg(stackCount()).arg(id));
      return false;
    }
    return systemCommand(SET_CURRENT_STACK, QStringList() << store << QString::number(id));
  }
  
  bool Process::takeSnapshot(QString fileName, float overSampling, int width, int height, 
                                                            int quality, bool expandFrustum)
  {
    return systemCommand(TAKE_SNAPSHOT, QStringList() << fileName
       << QString::number(overSampling) << QString::number(width) << QString::number(height) 
       << QString::number(quality) << boolToString(expandFrustum));
  }

  // Put a message on the status bar
  bool Process::setStatus(const QString &msg, bool alsoPrint)
  {
    return systemCommand(SET_STATUS, QStringList() << msg << boolToString(alsoPrint));
  }

  // Set the visualization for a frame
  bool Process::setFrameVis(const QString &name, ulong flags, const Point3d &pos, const Quaternion &orient)
  {
    return systemCommand(SET_FRAME_VIS, QStringList() << name << QString::number(flags)
        << QString("%1 %2 %3").arg(pos.x()).arg(pos.y()).arg(pos.z()) 
        << QString("%1 %2 %3 %4").arg(orient.x()).arg(orient.y()).arg(orient.z()).arg(orient.w()));
  }

  // Set the visualization for camera
  bool Process::setCameraVis(const Point3d &pos, const Quaternion &orient, const Point3d &center, double zoom)
  {
    return systemCommand(SET_CAMERA_VIS, QStringList() << QString("%1 %2 %3").arg(pos.x()).arg(pos.y()).arg(pos.z())
        << QString("%1 %2 %3 %4").arg(orient.x()).arg(orient.y()).arg(orient.z()).arg(orient.w())
        << QString("%1 %2 %3").arg(center.x()).arg(center.y()).arg(center.z()) << QString::number(zoom));
  }

  // Send a signal to Gui thread to run a system command
  bool Process::systemCommand(int command, const QStringList &parms) 
  {
    if(isGuiThread())
      emit systemCommandGui(this, command, parms);
    else
      emit systemCommandProcess(this, command, parms);
    return true;
  }
  
  MGXCamera *Process::camera() { return p->camera; }

  Clip *Process::clip1() { return &p->clip1; }
  Clip *Process::clip2() { return &p->clip2; }
  Clip *Process::clip3() { return &p->clip3; }
  const Clip *Process::clip1() const { return &p->clip1; } 
  const Clip *Process::clip2() const { return &p->clip2; } 
  const Clip *Process::clip3() const { return &p->clip3; }
  
  CuttingSurface* Process::cuttingSurface() { return &p->cuttingSurface; }
  const CuttingSurface* Process::cuttingSurface() const { return &p->cuttingSurface; }
  
  const QString& Process::file() const { return p->filename; }
  
  QMap<QString, ProcessDefinition> processes;
  
//  static const ProcessDefinition* getProcessDefinition(
//      const QMap<QString, ProcessDefinition> &processes, const QString& processName)
//  {
//    if(!processes.contains(processName))
//      return 0;
//    const ProcessDefinition& def = processes[processName];
//    return &def;
//  }
  
  static ProcessDefinition* getProcessDefinition(
       QMap<QString, ProcessDefinition>& processes, const QString& processName)
  {
    if(!processes.contains(processName))
      return 0;
    ProcessDefinition& def = processes[processName];
    return &def;
  }
  
  ProcessDefinition* getProcessDefinition(const QString& processName)
  {
    return getProcessDefinition(processes, processName);
  }
  
  bool getLastParms(const Process& proc, QStringList& parms) 
  {
    return getLastParms(proc.name(), parms);
  }
  
  bool getLastParms(const QString& processName, QStringList& parms)
  {
    ProcessDefinition* def = getProcessDefinition(processName);
    if(!def)
      return false;
    parms = def->parms;
    return true;
  }
  
  bool getDefaultParms(const Process& proc, QStringList& parms)
  {
    return getDefaultParms(proc.name(), parms);
  }
  
  bool getDefaultParms(const QString& processName, QStringList& parms)
  {
    SetupProcess setup;
    Process* p = setup.makeProcess(processName);
    if(!p)
      return false;
    parms = p->parmDefaults();
    return true;
  }
  
  bool saveDefaultParms(const Process& proc, const QStringList &parms)
  {
    return saveDefaultParms(proc.name(), parms);
  }
  
  bool saveDefaultParms(const QString& processName, const QStringList &parms)
  {
    ProcessDefinition* def = getProcessDefinition(processName);
    if(!def)
      return false;
    if(parms.size() != def->parms.size())
      return false;
    def->parms = parms;
    return true;
  }
  
  bool checkProcessParms(const Process& proc, const QStringList &parms, size_t *nbParms)
  {
    return checkProcessParms(proc.name(), parms, nbParms);
  }
  
  bool checkProcessParms(const QString& processName, const QStringList &parms, size_t *nbParms)
  {
    ProcessDefinition* def = getProcessDefinition(processName);
    if(!def)
      return false;
    if(nbParms)
      *nbParms = def->parmNames.size();
    if(parms.size() != def->parms.size())
      return false;
    return true;
  }
  
  Process* Process::makeProcess(const QString& processName)
  {
    if(!processes.contains(processName))
      return 0;
    Process* proc = (*processes[processName].factory)(*this);
    ProcessDefinition* def = getProcessDefinition(processName);
    // Grab the parms from the GUI
    // RSS this assumes this is the only place the factory is called
    proc->setParms(def->parms);
    return proc;
  }
  
  QStringList listProcesses()
  {
    QStringList res;
  
    for(const ProcessDefinition &def : processes)
      res << def.name;
    return res;
  }

  bool getProcessText(const QString &text, QString &tab, QString &folder, QString &name)
  {
    // The tab is the first part upto "/" and the name is the last part after the last "/"
    // The rest is the folder and currently it should not be empty
    QStringList list = text.split("/", QString::SkipEmptyParts);
    if(list.size() < 3) {
      Information::out << "Invalid process tab/folder/name: " << text << endl;
      return false;
    }
    tab = list[0];
    name = list[list.size() - 1];
    list.removeFirst();
    list.removeLast();
    folder = list.join("/");

    return true;
  } 

//  Process *getProcessParms(Process *p, const QString &procName, QStringList &parms)
//	{
//    if(!p)
//      return 0;
//
//    QString pName;
//    if(procName.indexOf("/") >= 0)
//      pName = procName;
//    else {
//      QString tab, folder, name;
//      getProcessText(p->name(), tab, folder, name);
//      pName = tab + "/" + folder + "/" + procName;
//    }
//
//    Process *process = p->makeProcess(pName);
//    if(!process)
//      return 0;
//
//    getLastParms(*process, parms);
//
//    return process;
//  }

  bool validProcessName(const QString& processName)
  {
    return processes.contains(processName);
  }
  
  bool stringToMainStore(const QString& string)
  {
    QString s = string.toLower();
    if(s == "main")
      return true;
    return false;
  }
  
  bool stringToWorkStore(const QString& string)
  {
    QString s = string.toLower();
    if(s == "work")
      return true;
    return false;
  }
  
  bool stringToBool(const QString& string)
  {
    static QStringList t = QStringList() << "t" << "true" << "on" << "yes" << "y" << "1";
    return t.contains(string.toLower());
  }
  
  bool Process::runProcess(const QString& processName, QStringList &parms) throw()
  {
    try {
      Process *proc = makeProcess(processName);
      if(!proc)
        return setErrorMessage(QString("Error, cannot create process %1").arg(processName));
      if(!checkProcessParms(processName, parms))
        return setErrorMessage(
          QString("Error, incorrect parameters for process %1.%2").arg(processName));
 
      return runProcess(*proc, parms);
    }
    catch(std::exception& ex) {
      return setErrorMessage(
        QString("Exception caught while creating process %1: %2").arg(processName)
                                                     .arg(processName).arg(ex.what()));
    }
    catch(...) {
      return setErrorMessage(
        QString("Unknown exception caught while creating process %1.").arg(processName));
    }
  }
  
  bool Process::runProcess(Process &proc, QStringList &parms) throw()
  {
    try {
      // Hook up system calls
      if(p->parent) {
        connect(&proc, SIGNAL(systemCommandProcess(mgx::Process *, int, QStringList)), 
           proc.p->parent, SLOT(systemCommand(mgx::Process *, int, QStringList)), 
           Qt::ConnectionType(Qt::BlockingQueuedConnection | Qt::UniqueConnection));
        connect(&proc, SIGNAL(systemCommandGui(mgx::Process *, int, QStringList)), 
           proc.p->parent, SLOT(systemCommand(mgx::Process *, int, QStringList)), Qt::UniqueConnection);
      }
      proc.setParms(parms);
      // RSS this is a bit of a hack, initialize should be called in GUI thread
      if(!proc.initialize(0))
        return setErrorMessage(proc.errorMessage());
      if(!proc.run())
        return setErrorMessage(proc.errorMessage());
    }
    catch(QString& err) {
      return setErrorMessage(QString("Error in %1:\n%2").arg(proc.name()).arg(err));
    }
    catch(std::string& err) {
      return setErrorMessage(QString("Error in %1:\n%2").arg(proc.name()).arg(err.c_str()));
    }
    catch(std::exception& ex) {
      return setErrorMessage(QString("Error in %1:\n%2").arg(proc.name()).arg(ex.what()));
    }
    catch(...) {
      setErrorMessage(QString("Error in %1:\nUnknown C++ exception").arg(proc.name()));
      return false;
    }
    return true;
  }
  
  QString Process::stackError(int type, int i)
  {
    QString msg;
    if(i == -1)
      msg = "the selected stack to be %1";
    else
      msg = QString("the stack %1 to be %2").arg(i);
    QStringList what;
    if(type == STORE_ANY)
      what << "valid";
    else {
      if(type & STACK_NON_EMPTY)
        what << "non-empty";
      if(type & STACK_VISIBLE)
        what << "visible";
      if(type & STACK_EMPTY)
        what << "empty";
      if(type & STACK_SCALED)
        what << "scaled";
      if(type & STACK_TRANSFORMED)
        what << "transformed";
      if(type & STACK_NON_SCALED)
        what << "not scaled";
      if(type & STACK_NON_TRANSFORMED)
        what << "not transformed";
    }
    return msg.arg(what.join(", "));
  }
  
  QString Process::storeError(int type, int i)
  {
    QString msg;
    if(i == -1)
      msg = "the current%1 stack to be %2";
    else
      msg = QString("the%2 stack %1 to be %3").arg(i);
    QString which;
    QStringList what;
    if(type & STORE_WORK)
      which = " work";
    else if(type & STORE_MAIN)
      which = " main";
    else
      which = "";
    if(type == STORE_ANY)
      what << "valid";
    else {
      if(type & STORE_VISIBLE)
        what << "visible";
      if(type & STORE_NON_EMPTY)
        what << "non-empty";
      if(type & STORE_EMPTY)
        what << "empty";
      if(type & STORE_LABEL)
        what << "labeled";
      else if(type & STORE_NON_LABEL)
        what << "non-labeled";
      if(type & STORE_SCALED)
        what << "scaled";
      if(type & STORE_TRANSFORMED)
        what << "transformed";
      if(type & STORE_NON_SCALED)
        what << "not scaled";
      if(type & STORE_NON_TRANSFORMED)
        what << "not transformed";
    }
    return msg.arg(which).arg(what.join(", "));
  }
  
  QString Process::meshError(int type, int i)
  {
    QString msg;
    if(i == -1)
      msg = "the selected mesh to be %1";
    else
      msg = QString("the mesh %1 to be %2").arg(i);
    QStringList what;
    if(type == MESH_ANY)
      what << "valid";
    else {
      if(type & MESH_VISIBLE)
        what << "visible";
      if(type & MESH_NON_EMPTY)
        what << "non-empty";
  
      if(type & MESH_HEAT)
        what << "showing heat";
      if(type & MESH_LABEL)
        what << "showing labels";
      if(type & MESH_USE_PARENTS)
        what << "showing parents";
      if(type & MESH_NORMAL)
        what << "showing normal";
      if(type & MESH_SIGNAL)
        what << "showing signal";
      if(type & MESH_TEXTURE)
        what << "showing texture";
      if(type & MESH_IMAGE)
        what << "showing image";
      if(type & MESH_SHOW_MESH)
        what << "showing mesh";
      if(type & MESH_SHOW_SURF)
        what << "showing surface";
      if(type & MESH_ALL)
        what << "showing the whole mesh";
      if(type & MESH_BORDER)
        what << "showing the border";
      if(type & MESH_CELLMAP)
        what << "showing the cell mapping";
      if(type & MESH_CELLS)
        what << "a cell mesh";
      if(type & MESH_IMG_TEX)
        what << "having an image texture";
      if(type & MESH_SCALED)
        what << "scaled";
      if(type & MESH_TRANSFORMED)
        what << "transformed";
      if(type & MESH_EMPTY)
        what << "empty";
      if(type & MESH_NON_CELLS)
        what << "not a cell mesh";
      if(type & MESH_NON_IMG_TEX)
        what << "nat having an image texture";
      if(type & MESH_NON_SCALED)
        what << "not scaled";
      if(type & MESH_NON_TRANSFORMED)
        what << "not transformed";
    }
    return msg.arg(what.join(", "));
  }

  // RSS These checking mechanisms are not really scalable, consider replacing at some point
  bool Process::stackCheck(int checks, int which)
  {
    Stack* s = (which == CHECK_CURRENT) ? currentStack() : stack(which);
    return (s and not (checks & STACK_NON_EMPTY and s->empty())
            and not (checks & STACK_VISIBLE 
            and not (s->main()->isVisible() or s->work()->isVisible()))
            and not (checks & STACK_EMPTY 
              and not s->empty())and not (checks & STACK_SCALED and not s->showScale())
            and not (checks & STACK_TRANSFORMED and not s->showTrans())
            and not (checks & STACK_NON_SCALED and s->showScale())
            and not (checks & STACK_NON_TRANSFORMED and s->showTrans()));
  }
  
  bool Process::storeCheck(int checks, int which)
  {
    Stack* s = (which == CHECK_CURRENT) ? currentStack() : stack(which);
    if(!s)
      return false;
    Store* store = 0;
    if(checks & STORE_MAIN)
      store = s->main();
    else if(checks & STORE_WORK)
      store = s->work();
    else
      store = s->currentStore();
    return (store and not (checks & STORE_NON_EMPTY and store->empty())
            and not (checks & STORE_VISIBLE and not store->isVisible())
            and not (checks & STORE_EMPTY and not store->empty())
            and not (checks & STORE_LABEL and not store->labels())
            and not (checks & STORE_NON_LABEL and store->labels())
            and not (checks & STORE_SCALED and not store->stack()->showScale())
            and not (checks & STORE_TRANSFORMED and not store->stack()->showTrans())
            and not (checks & STORE_NON_SCALED and store->stack()->showScale())
            and not (checks & STORE_NON_TRANSFORMED and store->stack()->showTrans()));
  }
  
  bool Process::meshCheck(int checks, int which)
  {
    Mesh* m = (which == CHECK_CURRENT) ? currentMesh() : mesh(which);
    return (m and not (checks & MESH_NON_EMPTY and m->empty())
            and not (checks & MESH_VISIBLE and not (m->showSurface() or m->showMesh()))
            //and not (checks & MESH_HEAT and not (m->toShow() == Mesh::HEAT))
            //and not (checks & MESH_LABEL and not (m->toShow() == Mesh::LABEL))
            and not (checks & MESH_USE_PARENTS and not (m->useParents()))
            //and not (checks & MESH_NORMAL and not (m->toShow() == Mesh::NORMAL))
            //and not (checks & MESH_SIGNAL and not (m->coloring() == Mesh::SIGNAL))
            //and not (checks & MESH_TEXTURE and not (m->coloring() == Mesh::TEXTURE))
            //and not (checks & MESH_IMAGE and not (m->coloring() == Mesh::IMAGE))
            and not (checks & MESH_SHOW_MESH and not m->showMesh())
            and not (checks & MESH_SHOW_SURF and not m->showSurface())
            and not (checks & MESH_ALL and not (m->meshView() == "All"))
            and not (checks & MESH_BORDER and not (m->meshView() == "Border"))
            and not (checks & MESH_CELLMAP and not (m->meshView() == "Cells"))
            and not (checks & MESH_CELLS and (m->meshType() != "MGXC"))
            and not (checks & MESH_IMG_TEX and not m->hasImgTex())
            and not (checks & MESH_SCALED and not m->scaled())
            and not (checks & MESH_TRANSFORMED and not m->transformed())
            and not (checks & MESH_EMPTY and not m->empty())
            and not (checks & MESH_NON_CELLS and (m->meshType() == "MGXC"))
            and not (checks & MESH_NON_IMG_TEX and m->hasImgTex())
            and not (checks & MESH_NON_SCALED and m->scaled())
            and not (checks & MESH_NON_TRANSFORMED and m->transformed()));
  }
  
  Process::CheckState Process::checkState() { return CheckState(this); }
  
  Process::CheckState::CheckState(const CheckState& copy) 
             : reqs(copy.reqs), process(copy.process) {}
  
  Process::CheckState::CheckState(Process* proc) : process(proc) {}
  
  Process::CheckState& Process::CheckState::store(int checks, int which)
  {
    ProcessReqs r = { CHECK_STORE, checks, which };
    reqs << r;
    return *this;
  }
  
  Process::CheckState& Process::CheckState::stack(int checks, int which)
  {
    ProcessReqs r = { CHECK_STACK, checks, which };
    reqs << r;
    return *this;
  }
  
  Process::CheckState& Process::CheckState::mesh(int checks, int which)
  {
    ProcessReqs r = { CHECK_MESH, checks, which };
    reqs << r;
    return *this;
  }
  
  void Process::CheckState::setError()
  {
    QStringList msgs;
    forall(const ProcessReqs& r, reqs) {
      switch(r.type) {
      case CHECK_STACK:
        msgs << process->stackError(r.checks, r.which);
        break;
      case CHECK_STORE:
        msgs << process->storeError(r.checks, r.which);
        break;
      case CHECK_MESH:
        msgs << process->meshError(r.checks, r.which);
      default:
        break;
      }
    }
    QString msg = QString("This process requires %1").arg(msgs.join(" ; "));
    process->setErrorMessage(msg);
  }
  
  Process::CheckState::operator bool()
  {
    forall(const ProcessReqs& r, reqs) {
      switch(r.type) {
      case CHECK_STACK:
        if(!process->stackCheck(r.checks, r.which)) {
          setError();
          return false;
        }
        break;
      case CHECK_STORE:
        if(!process->storeCheck(r.checks, r.which)) {
          setError();
          return false;
        }
        break;
      case CHECK_MESH:
        if(!process->meshCheck(r.checks, r.which)) {
          setError();
          return false;
        }
      default:
        break;
      }
    }
    return true;
  }

//  #ifdef WIN32
//    struct mgx_EXPORT Registration;
//  #else
//    Registration::factoryList Registration::factories;
//  #endif

  Registration::Registration(ProcessFactoryPtr f, const char* class_name, 
                      unsigned int compiledVersion) : factory(), classname(class_name)
  {
    if(DEBUG) {
      Information::out << "Registration of process " << qdemangle(classname) 
//                       << " at address 0x" << QString::number(f.id(), 16)
                       << endl << "Type of data: " << qdemangle(typeid(*f).name()) << endl;
    }
    if(Process::processVersion != compiledVersion) {
      QTextStream err(stderr);
      err << "Error registering process " << qdemangle(class_name) << endl
          << " It has been compiled against a different revision of MorphoGraphX:" 
          << QString::number(compiledVersion)
          << endl << " current version:" << QString::number(Process::processVersion) << endl;
    } else {
      factory = f;
      if(!factory and DEBUG)
        Information::out << "Storing null factory!" << endl;
      processFactories() << factory;

      // Report the name, description, etc. of the process to a file.
      // {
      //   // Create the process with a dummy process as parent.
      //   Process *process = (*factory)(SetupProcess());
      //   if(process) {
      //     // Helper function to convert a QString into a C string.
      //     QByteArray arr;
      //     auto cstr = [&arr](QString qstr) -> const char* {
      //       arr = qstr.toLocal8Bit();
      //       return arr.constData();
      //     };

      //     // class_name is a reasonable enough name, although it's mangled a bit.
      //     std::ofstream out(class_name , std::ios::out | std::ios::trunc);

      //     // Name and description are simple.
      //     out << "setName(\"" << cstr(process->name()) << "\");" << std::endl;
      //     out << "setDesc(\"" << cstr(process->description()) << "\");" << std::endl;

      //     // Now on to parameter information.
      //     QStringList names = process->parmNames();
      //     QStringList descs = process->parmDescs();
      //     QStringList  defs = process->parmDefaults();
      //     ParmChoiceMap choices = process->parmChoice();

      //     // The default numParms function returns the number of default values;
      //     // surely it's more correct to return the number of names?
      //     uint numParms = names.length();
      //     if(numParms > 0) out << std::endl;

      //     // We have to output parameter data one-by-one.
      //     for(uint i = 0 ; i < numParms ; i++) {
      //       out << "addParm(";
      //       out << "\"" << cstr(names[i]) << "\",";
      //       out << "\"" << (i < descs.length() ? cstr(descs[i]) : "") << "\",";
      //       out << "\"" << (i < defs.length() ? cstr(defs[i]) : "") << "\"";
      //       // Only add a choice map if we have one.
      //       if(choices.contains(i)) {
      //         QStringList cmap = choices[i];
      //         // Check known choice maps.
      //         if(cmap == Process::booleanChoice())
      //           out << ",booleanChoice()";
      //         else if(cmap == Process::storeChoice())
      //           out << ",storeChoice()";
      //         else
      //           // Otherwise, just output a construction of the choice map.
      //           out << ", QStringList() << \"" << cstr(cmap.join("\" << \"")) << "\"";
      //       }
      //       out << ");\t// " << i << std::endl;
      //     }

      //     out.close();

      //     // Destroy the process, as we will want to use a real parent later.
      //     factory->reload();
      //   }
      // }
    }
  }
  
  Registration::~Registration()
  {
    if(factory) {
      if(DEBUG) {
        Information::out << "Unregistration of process " << qdemangle(classname) 
//                         << " at address 0x" << QString::number(factory.id(), 16)
                         << endl << "Type of data: " << qdemangle(typeid(*factory).name()) << endl;
      }
      QList<ProcessFactoryPtr>& list = processFactories();
      int size_before = list.size();
      for(int i = 0; i < list.size(); ++i) {
        if(list[i] == factory) {
          list.removeAt(i);
          break;
        }
      }
      if(DEBUG and list.size() == size_before) {
        Information::out << "Error, could not find ProcessFactoryPtr to remove" << endl;
      }
    }
  } 

  QList<ProcessFactoryPtr>& Registration::processFactories()
  {
    // don't take it out of the function - this object is used in initialization all over the place and is subject to "static initialization disaster"
    // https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use
    static factoryList* factories = new factoryList();
    return *factories;
  }
}
