//
// 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.
// 
#ifndef DYNAMX_PROCESS_MORPHOGENS_HPP
#define DYNAMX_PROCESS_MORPHOGENS_HPP

#include <Process.hpp>
#include <Solver.hpp>
#include <DynamXProcessCellTissue.hpp>
#include <DynamXProcessCellDivide.hpp>

//
// Morphogen simulations
//

namespace mgx
{
  class mgxBase_EXPORT MeinhardtAI : public Process 
  {
  public:    
    // Cell data, used for Meinhardt-style reaction-diffusion simulation
    struct CellData
    {
      bool ProdA;      // Cell produces activator 
      bool ProdH;      // Cell produces inhibitor
  
      double A;       // Activator concentration
      double H;       // Inhibitor concentration
  
      double dA;      // Derivatives
      double dH;
      
      // Constructor, set initial values
      CellData() : ProdA(true), ProdH(true), A(0), H(0), dA(0), dH(0) {}

      bool operator==(const CellData &other) const
      {
        if(ProdA == other.ProdA and ProdH == other.ProdH
           and A == other.A and H == other.H
           and dA == other.dA and dH == other.dH)
          return true;
        return false;
      }
    };
  
    // Define the attribute map
    typedef AttrMap<cell, CellData> CellAttr;
  
    // Class to define methods for subdivision
    class mgxBase_EXPORT Subdivide : virtual public mgx::Subdivide
    {
    public:
      Subdivide(CellAttr *cAttr) : cellAttr(cAttr) {}
  
      virtual bool updateCellData(cell c, cell cl, cell cr);
      
      CellAttr *cellAttr;
    };

    // Class to define methods for solver
    class mgxBase_EXPORT Solver : public mgx::Solver
    {
    public:
      Solver(CellAttr *cAttr, Mesh* m, cellGraph& C, SolvingMethod method, int variables, double stepSize)
        : mgx::Solver(m, C, method,variables,stepSize), cellAttr(cAttr) {}
      
      std::vector<double> getValues(const cell& c);
      bool setValues(const cell& c, const std::vector<double> &values);
      std::vector<double> getDerivatives(const cell& c);
      void updateDerivatives(const cell& c);

      void setValues(const cell& c, std::vector<double> values);

      MeinhardtAI* m;
      CellAttr *cellAttr;
    };


    // Parameters
    enum ParmNames { pDrawSteps, pStatStep, pDt, pAProd, pAAProd, pADecay, pADiff, pANoise,
                     pAMax, pAViewMax, pHProd, pHAProd, pHDecay, pHDiff, pHMax, pSolvingMethod, pNumParms };

    ~MeinhardtAI() {}

    MeinhardtAI(const Process &process) : Process(process) {}

    void calcDerivatives(const cell& c);

    // Initialize simulation, called from GUI thread
    bool initialize(QStringList &parms, QWidget* parent = 0);

    // Process model parameters
    void processParms(const QStringList &parms);
  
    // Run a step of the simulation
    bool step(const QStringList &parms);

    // Run a step of the simulation
    bool step(const QStringList &parms, Subdivide *sDiv);

    // Rewind of the simulation
    bool rewind(QStringList &parms, QWidget *parent);
      
     // Clip max qtys and report
    void checkMaxQtys(CellTissue *T); 

    // Put the signal in the graph
    void updateSignal();

    // Process long description
    QString description() const { return "Meinhardt-style activator-inhibitor system"; }

    QStringList parmNames() const 
    {
      QVector <QString> vec(pNumParms);

      vec[pDrawSteps] = "DrawSteps";
      vec[pStatStep] = "StatStep";
      vec[pDt] = "Dt";
      vec[pAProd] = "AProd";
      vec[pAAProd] = "AAProd";
      vec[pADecay] = "ADecay";
      vec[pADiff] = "ADiff";
      vec[pANoise] = "ANoise";
      vec[pAMax] = "AMax";
      vec[pAViewMax] = "AViewMax";
      vec[pHProd] = "HProd";
      vec[pHAProd] = "HAProd";
      vec[pHDecay] = "HDecay";
      vec[pHDiff] = "HDiff";
      vec[pHMax] = "HMax";
      vec[pSolvingMethod] = "Forward Euler";
      
      return vec.toList();
    }
    QStringList parmDescs() const 
    {
      QVector <QString> vec(pNumParms);

      vec[pDrawSteps] = "Simulation steps between drawing";
      vec[pStatStep] = "Time between statistics reporting, 0 = none";
      vec[pDt] = "Step duration";
      vec[pAProd] = "Activator default production";
      vec[pAAProd] = "Activator production, self enhanced";
      vec[pADecay] = "Activator decay";
      vec[pADiff] = "Activator diffusion";
      vec[pANoise] = "Activator noise";
      vec[pAMax] = "Activator max concentration";
      vec[pAViewMax] = "Activator max for viewing";
      vec[pHProd] = "Inhibitor default production";
      vec[pHAProd] = "Inhibitor production, self enhanced";
      vec[pHDecay] = "Inhibitor decay";
      vec[pHDiff] = "Inhibitor diffusion";
      vec[pHMax] = "Inhibitor max concentration";
      vec[pSolvingMethod] = "ODE Solving Method (Euler or Backward Euler)";

      return vec.toList();
    }
    QStringList parmDefaults() const 
    {
      QVector <QString> vec(pNumParms);

      vec[pDrawSteps] = "10";
      vec[pStatStep] = "1.0";
      vec[pDt] = "0.001";
      vec[pAProd] = "0.1";
      vec[pAAProd] = "100";
      vec[pADecay] = "1";
      vec[pADiff] = "0.01";
      vec[pANoise] = "0.1";
      vec[pAMax] = "20000";
      vec[pAViewMax] = "25";
      vec[pHProd] = "0";
      vec[pHAProd] = "500";
      vec[pHDecay] = "50";
      vec[pHDiff] = "100";
      vec[pHMax] = "20000";
      vec[pSolvingMethod] = "Yes";

      return vec.toList();
    }

    // Gui dropdowns
    ParmChoiceMap parmChoice() const
    {
      ParmChoiceMap map;
      map[pSolvingMethod] = booleanChoice();
      return map;
    }

    // Icon file
    QIcon icon() const { return QIcon(":/images/MeinhardtAI.png"); }

    private: 
      Mesh *mesh;
      CellTissue *T;
      // vertex attributes required for simulation 
      CellAttr *cellAttr;  

      // Parameters
      int drawSteps;              // Integration steps
      double statStep;           // Time between stats printouts
      double dtRD;               // Step duration
  
      double aProd;              // Activator default production constant
      double aaProd;             // Activator self-enhanced production
      double aDecay;             // Activator decay constant
      double aDiff;              // Activator diffusion constant
      double aNoise;             // Activator noise
      double aMax;               // Activator max amount
      double aViewMax;           // Activator max for viewing
    
      double hProd;              // Inhibitor default production constant
      double haProd;             // Inhibitor self-enhanced production
      double hDecay;             // Inhibitor decay constant
      double hDiff;              // Inhibitor diffusion constant
      double hMax;               // Inhibitor max amount
     
      double time;               // Current time in simulation
      double lastStat;           // Time of last statistics printout

      bool solvingMethod;     // ODE solving method
  };

  // Read/write Cell data
  bool inline readAttr(MeinhardtAI::CellData &m, const QByteArray &ba, size_t &pos) 
  { 
    return readChar((char *)&m, sizeof(MeinhardtAI::CellData), ba, pos);
  }
  bool inline writeAttr(const MeinhardtAI::CellData &m, QByteArray &ba)
  { 
    return writeChar((char *)&m, sizeof(MeinhardtAI::CellData), ba);
  }
}
#endif
