//
// This file is part of 3DCellAtlas. 
// Copyright (C) 2015 George W. Bassel and collaborators.
//
// If you use 3DCellAtlas in your work, please cite:
//   http://dx.doi.org/10.1105/tpc.15.00175
//
// 3DCellAtlas is an AddOn for MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// 3DCellAtlas and MorphoGraphX are free software, and are licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#ifndef RootCellProcessing_H
#define RootCellProcessing_H

#include "Geometry.hpp"
#include "Mesh.hpp"

#include <CellAtlasUtils.hpp>

using namespace std;

namespace mgx 
{
  // for the stack projection
  struct primDataStructure
  {
    std::map<int, Point3d> bez, diffBez, bMapRadRef;
    triVector meshTris;
  
    primDataStructure() {}
  
    bool operator==(const primDataStructure &other) const
    {
      if(bez == other.bez and diffBez == other.diffBez and bMapRadRef == other.bMapRadRef and
          meshTris == other.meshTris)
        return true;
      return false;
    }
  };
  
    // data structure for attribute map
    struct CellAtlasConfig
    {
  
      // simple data structures
      int cellAtlasType;
  
      bool twoParts;
  
      bool analyzed, cellTypesAssigned;
  
      int labelFirstCell, labelLastRootCapCell;
  
      int primCounter; // for meristem, counter on how many primordia there are
  
      //GUI options (not functional yet)
      int part1ChosenX, part2ChosenX, part1ChosenY, part2ChosenY;
      double sigma1, sigma2;
      std::vector<std::pair<int,Point2i> > clusters1, clusters2;
  
      // complicated data structures
  
      //map from cell label -> index in CellAtlasData
      std::map<int,int> labelIdxMap;
  
      // map from [label1,label2] -> shared wall area
      std::map<IntInt, double> wallArea;
  
      std::map<int, Point3d> bez, diffBezInterp;

      //double bezLength;
  
      //std::map<int, Point3d> bez, diffBez, bMapRadRef;
      //std::map<int, Point3d> diffBezInterp, bezInterp;
  
      primDataStructure prim1, prim2;
  
      bool operator==(const CellAtlasConfig &other) const
      {
        if(cellAtlasType == other.cellAtlasType and twoParts == other.twoParts and analyzed == other.analyzed and
            cellTypesAssigned == other.cellTypesAssigned and labelFirstCell == other.labelFirstCell and
            labelLastRootCapCell == other.labelLastRootCapCell and primCounter == other.primCounter and
            part1ChosenX == other.part1ChosenX and part2ChosenX == other.part2ChosenX and
            part1ChosenY == other.part1ChosenY and part2ChosenY == other.part2ChosenY and
            sigma1 == other.sigma1 and sigma2 == other.sigma2 and clusters1 == other.clusters1 and
            clusters2 == other.clusters2 and labelIdxMap == other.labelIdxMap and wallArea == other.wallArea and
            bez == other.bez /*and bezLength == other.bezLength*/ and diffBezInterp == other.diffBezInterp and prim1 == other.prim1 and prim2 == other.prim2)
          return true;
        return false;
      }
    };
  
    // data structure for attribute map
    struct CellAtlasData
    {
      int cellLabel, parentLabel;
  
      bool isColumella, isPart1, isPart2;
  
      Point3d centroid, signalCentroid;
  
      Point3d nearestSurfacePoint;
  
      Point3d dirLong, dirCirc, dirRad;
  
      double coordLong, coordLongUM, coordLongMinUM, coordLongMaxUM, coordCirc, coordRad, coordRadAbs;
      double volume, wallArea, nrNeighbors;
      double sizeLong, sizeCirc, sizeRad;
      int associatedCorticalCell;
  
      double outsideWallArea, outsideWallAreaPercent;
  
      double correctedLengths;
  
      bool mislabelled;
      bool badCell;
  
      CellAtlasData() {}
  
      bool operator==(const CellAtlasData &other) const
      {
        if(cellLabel == other.cellLabel and parentLabel == other.parentLabel 
            and isColumella == other.isColumella and isPart1 == other.isPart1 
            and isPart2 == other.isPart2 and centroid == other.centroid 
            and signalCentroid == other.signalCentroid and nearestSurfacePoint == other.nearestSurfacePoint 
            and dirLong == other.dirLong and dirCirc == other.dirCirc and dirRad == other.dirRad 
            and coordLong == other.coordLong and coordLongUM == other.coordLongUM
            and coordCirc == other.coordCirc and coordLongMinUM == other.coordLongMinUM and coordLongMaxUM == other.coordLongMaxUM
            and coordRad == other.coordRad and coordRadAbs == other.coordRadAbs 
            and volume == other.volume and wallArea == other.wallArea and nrNeighbors == other.nrNeighbors and sizeLong == other.sizeLong 
            and sizeCirc == other.sizeCirc and sizeRad == other.sizeRad 
            and associatedCorticalCell == other.associatedCorticalCell 
            and outsideWallArea == other.outsideWallArea 
            and outsideWallAreaPercent == other.outsideWallAreaPercent 
            and correctedLengths == other.correctedLengths and mislabelled == other.mislabelled 
            and badCell == other.badCell)
          return true;
        return false;
      }
    };
  
    // Read/write attr map
    bool inline readAttr(CellAtlasData &m, const QByteArray &ba, size_t &pos) 
    {
      return readChar((char *)&m, sizeof(CellAtlasData), ba, pos);
    }
    bool inline writeAttr(const CellAtlasData &m, QByteArray &ba) 
    {
      return writeChar((char *)&m, sizeof(CellAtlasData), ba);
    }
  
    // Read/write attr map
    bool inline readAttr(CellAtlasConfig &data, const QByteArray &ba, size_t &pos) 
    {
      readAttr(data.cellAtlasType, ba, pos);
  
      readAttr(data.twoParts, ba, pos);
  
      readAttr(data.analyzed, ba, pos);
      readAttr(data.cellTypesAssigned, ba, pos);
  
      readAttr(data.labelFirstCell, ba, pos);
      readAttr(data.labelLastRootCapCell, ba, pos);

      //readAttr(data.bezLength, ba, pos);
  
      readAttr(data.primCounter, ba, pos);
  
      readAttr(data.part1ChosenX, ba, pos);
      readAttr(data.part2ChosenX, ba, pos);
      readAttr(data.part1ChosenY, ba, pos);
      readAttr(data.part2ChosenY, ba, pos);
  
      uint sz;
      readAttr(sz, ba, pos);
      for(uint i = 0; i<sz; i++){
        int key;
        int value;
        readAttr(key, ba, pos);
        readAttr(value, ba, pos);
        data.labelIdxMap[key] = value;
      }
  
      readAttr(sz, ba, pos);
      for(uint i = 0; i<sz; i++){
        IntInt key;
        double value = 0;
        readAttr(key, ba, pos);
        readAttr(value, ba, pos);
        data.wallArea[key] = value;
      }
  
      readAttr(sz, ba, pos);
      for(uint i = 0; i<sz; i++){
        int key;
        Point3d value;
        readAttr(key, ba, pos);
        readAttr(value, ba, pos);
        data.bez[key] = value;
      }
  
      readAttr(sz, ba, pos);
      for(uint i = 0; i<sz; i++){
        int key;
        Point3d value;
        readAttr(key, ba, pos);
        readAttr(value, ba, pos);
        data.diffBezInterp[key] = value;
      }
      //return readChar((char *)&data, sizeof(CellAtlasConfig), ba, pos);
      return true;
    }
    bool inline writeAttr(const CellAtlasConfig &data, QByteArray &ba) 
    {
  
      writeChar((char *)&data.cellAtlasType, sizeof(int), ba);
  
      writeChar((char *)&data.twoParts, sizeof(bool), ba);
  
      writeChar((char *)&data.analyzed, sizeof(bool), ba);
      writeChar((char *)&data.cellTypesAssigned, sizeof(bool), ba);
  
      writeChar((char *)&data.labelFirstCell, sizeof(int), ba);
      writeChar((char *)&data.labelLastRootCapCell, sizeof(int), ba);

      //writeChar((char *)&data.bezLength, sizeof(double), ba);
  
      writeChar((char *)&data.primCounter, sizeof(int), ba);
  
      writeChar((char *)&data.part1ChosenX, sizeof(int), ba);
      writeChar((char *)&data.part2ChosenX, sizeof(int), ba);
      writeChar((char *)&data.part1ChosenY, sizeof(int), ba);
      writeChar((char *)&data.part2ChosenY, sizeof(int), ba);
  
      //writeAttr(data.labelIdxMap, ba);
      uint sz;
  
      sz = data.labelIdxMap.size();
      writeChar((char *)&sz, sizeof(uint), ba);
      for(std::map<int,int>::const_iterator it = data.labelIdxMap.begin(); it!=data.labelIdxMap.end(); it++){
        writeAttr(it->first, ba);
        writeAttr(it->second, ba);
      }
  
      //writeAttr(data.wallArea, ba);
  
      sz = data.wallArea.size();
      writeChar((char *)&sz, sizeof(uint), ba);
      for(std::map<IntInt,double>::const_iterator it = data.wallArea.begin(); it!=data.wallArea.end(); it++){
        writeAttr(it->first, ba);
        writeAttr(it->second, ba);
      }
  
      //writeAttr(data.bez, ba);
  
      sz = data.bez.size();
      writeChar((char *)&sz, sizeof(uint), ba);
      for(std::map<int,Point3d>::const_iterator it = data.bez.begin(); it!=data.bez.end(); it++){
        writeAttr(it->first, ba);
        writeAttr(it->second, ba);
      }
  
      sz = data.diffBezInterp.size();
      writeChar((char *)&sz, sizeof(uint), ba);
      for(std::map<int,Point3d>::const_iterator it = data.diffBezInterp.begin(); it!=data.diffBezInterp.end(); it++){
        writeAttr(it->first, ba);
        writeAttr(it->second, ba);
      }
  
      return true;
    }  
  
    typedef AttrMap<int, CellAtlasData> CellAtlasAttr;
    typedef AttrMap<int, CellAtlasConfig> CellAtlasConfigAttr;
  
  
  
  // structure that saves general root cell information
  struct rootDataStructure
  {
    ////// general overall data /////
  
    bool hasRadicle;
  
    int rootType; // 1 = embryo, 2 = hypocotyl, 3 = root
  
    // first and last root cap 
    int labelFirstCell, labelLastRootCapCell;
  
    int primCounter;
  
    // vector with all labels
    std::vector<int> uniqueLabels;
  
    int numCells; // number of cells
  
    //intToVec cellConnections;
    //intToVec cellWallAreas;
    //P2iDMap
    std::map<IntInt, double> wallArea;
    std::map<int,double> cellWallArea;
    std::map<int,double> outsideWallArea, nrNeighbors;
  
  
    std::map<int,double> correctedLengths;
  
    std::map<int, Point3d> bez, diffBez, bMapRadRef;
    std::map<int, Point3d> diffBezInterp, bezInterp;
  
    IntIntMap associatedCorticalCell;
  
    std::map<int,bool> mislabelled;

    double bezLength;
  
    // currently not needed
    // map of: label -> all vertices
    //labelVertexMap lvMap;
  
    // currently not needed
    // map of: label -> all unique triangles
    //labelTriMap cellTriangles;
  
    ////// cell data /////
  
    // currently not needed
    // label -> center of cell
    //labelPosMap cellCentroids;
  
    // label -> volume of cell
    std::map<int,double> cellVolumes;
  
    // label -> boolean that is true when cell is bad (too small or no ray intercept)
    std::map<int, bool> cellBad;
  
    std::map<int,double> radialDis;
    std::map<int,double> scaledRadialDis;
    std::map<int,double> azimuthal;
  
    std::map<int, Point3d> cellCentroids;
  
     std::map<int, Point3d> signalCentroids;
  
    std::map<int, Point3d> nearestSurfacePoint;
  
    // label -> location on Bezier (0...1)
    std::map<int,double> arclengths;
    std::map<int,double> arclengthsUM;
    std::map<int,double> arclengthsMinUM;
    std::map<int,double> arclengthsMaxUM;
  
    // label -> radial/longitudinal/circumferential vector
    std::map<int, Point3d> dirRad, dirLong, dirCirc;
  
    // label -> radial/longitudinal/circumferential cell length
    std::map<int,double> lengthRad, lengthLong, lengthCirc;
  
  };
  
  // structure that saves heatmap specific data
  struct heatMapDataStructure
  {
    // labeldata maps for the generation of the landscape
    std::map<int, double> x, y;
    int chosenY;
    int chosenX;
  
    int gridSize;
    double sigma;
    Point4d heatMapMinMax;
    Point4d heatMapStatistics;
  
    // label -> boolean mainBody, rootCap, columella
    //std::map<int, bool> cells;
    std::set<int> cells;
    int cellCounter; // cell counter columella
  
    // landscape function of 2 characteristics with gaussian bell for each cell
    cellMorphLandscape heatMap;
    double high;
  
    // location of all cells within the heatmap
    cellMorphLandscape points;
    std::map<Point2d,int> pointsLabel;
    std::map<int,Point2d> pointsPos;
  
    std::map<int,Point2d> maximaHeatMapAll;
    std::map<int,Point2d> maximaHeatMapSelected;
    IntIntMap maximaHeatMapSelectedLabel;
  
    // map that relates each cell to the nearest maximum
    std::map<int, double> nearestMaximumOfCell;
  
    int maximaHeatMapSelectedCounter;
  
    int numberOfClusters;
    int activatedMaximum;
  
    std::map<int, bool> preselect;    
  
    std::map<int, double> cellLabel;
  
    heatMapDataStructure() : maximaHeatMapSelectedCounter(0) {}
  };
  
  class RootCellProcessing
  {
  
  public:
  
    CellAtlasAttr *data;
    CellAtlasConfigAttr *config;
    bool pointersSet;
  
    int numCells;
    std::map<int,int> labelMap, parentsMap;
  
    bool outlierDetection;
  
  
    void createHeatMapNoCellAtlas(AttrMap<int, double>& dataX, AttrMap<int, double>& dataY);
    void associateNearestCluster(heatMapDataStructure& body);
  
    RootCellProcessing(): pointersSet(false), outlierDetection(true) {}
  
    void setDataPointers(CellAtlasAttr *cellAtlasAttr, CellAtlasConfigAttr *cellAtlasConfigAttr){
      data = cellAtlasAttr;
      config = cellAtlasConfigAttr;
      pointersSet = true;
      numCells = (*data).size();
      std::map<int,int> newLabelMap;
      for(int i = 0; i<numCells; i++){
        int currentLabel = (*data)[i].cellLabel;
        newLabelMap[i] = currentLabel;
      }
      labelMap = newLabelMap;
    }
  
    //void setHeatMaps();
  
    std::map<int, double> getMapFromAttributes(QString mapName);
  
    void initHeatMapDataStructure(heatMapDataStructure& body, double sig, int& chosenX, int& chosenY);
  
  
  //  double interpolateArrayIndex(double value, double min, double max, double newRange);
  
    // finds min, max, avg and std of the cells, needed for scaling the heatmap size
    void findHeatStatistics(heatMapDataStructure& body);
  
    // generates the heatmap (=cellMorphLandscape) of the cell set (=body)
    cellMorphLandscape generateCellMorphologyLandscape(heatMapDataStructure& body);
  
    // find all local maxima in a cellMorphLandscape (=heatmap) and save their positions
    std::map<int,Point2d> findMaxima(cellMorphLandscape& landsc);
  
    // label all cells according to a label-to-maximum and a maximum-to-referenceMaximum map
    std::map<int,double> labelCells(std::map<int,double>& labelToMax, std::map<int,double>& maxToRef,
                                                         heatMapDataStructure& body);
  
    // reverse the arclengths if the root has the wrong orientation
    void correctDirections();
  
    // assign the root regions according the selected cells and arclengths
    void assignRootRegions();
  
    // relate each cell in the root to a maxima in the heatmap by following the steepest 
    // gradient upwards until a maximum is reached
    std::map<int,double> relateCellstoMaxima(heatMapDataStructure& body, cellMorphLandscape& landsc, 
                                           std::map<int,double>& x, std::map<int,double>& y, std::map<int,Point2d> maxima);
  
    // calculates the mininmum distance to a maximum from a given maximum
    double minDisToMax(std::map<int,Point2d>& maxima, std::map<int,double>& usedMax, int currentIdx);
  
    // function to assign cluster automatically by selecting the highest maxima
    void findAutomaticCluster(heatMapDataStructure& body, int nrOfClusters);
  
    void setAutoCluster(int nrCluster, bool ismainBody);
  
    // deletes a cluster from the current selected ones
    void delIdx(heatMapDataStructure& body, int idx);
  
    // big procedure to find the correct labels, calls earlier functions
    std::map<int,double> clusterCells(heatMapDataStructure& body);
  
    // sets parameters that come from MGX
    void setParameter(bool outputType, int label1, int label2);
  
    // finds highest heatmap value for scaling the colours in the heatmap
    double findHighMax(heatMapDataStructure& body);
  
    // sets the X coord of the heatmap
    void setHeatmap(heatMapDataStructure& body, QString optionGUI, int& chosenOption, bool x);
  
    // big procedure to generate the heatmap and label the cells
    void geometryBasedLabelling();
  
    // reset preselection
    void resetHeat(heatMapDataStructure& body);
  
    // take all currently selected clusters, look for nearest maximum and handle 
    // them as preselected and remove them from the heatmap
    void preselectCells(heatMapDataStructure& body);
  
    // label columella cells
    // cells with small wall area to neighbors become root cap
    // remaining cells: small become vasc, big col
    //void reassignColumella(int labelRootCap, int labelCol);//, int labelVasc)
  
  
    void assignColumella(int labelRootCap, int labelCol, double rat_val, double sca_val);
  
  
    // assign cortical cells subroutine to create the correctedlengths and to dffind 
    // the min and max of the arclengths
    void assignCortCellsGetMinMax(int labelCort, double& minS, double& maxS);
  
    // assign the cortical cell number to each cell in the root
    void assignCortCells(int labelCort, std::vector<double>& sSmooth, std::map<int, Point3d>& bMapS, int length);
  
  
    rootDataStructure rootData;
  
    heatMapDataStructure mainBody;
    heatMapDataStructure radicle;
    heatMapDataStructure columella;
    //labelDataMap cellLabel;
  
    primDataStructure prim1, prim2;
  
  };
  
    void writeAttrMaps(RootCellProcessing rcp, CellAtlasAttr *cellAtlasAttr, CellAtlasConfigAttr *cellAtlasConfigAttr);
  /*
  // if in 2D mode only key 0 and 1 are used
  struct ManualCluster{
  
    std::map<int, double> val;
    int label;
  
  };
  */
  /*
  double realToImageCoord(Point2d minMax, double value, double gridSize);
  double imageToRealCoord(Point2d minMax, double value, double gridSize);
  Point2d minMaxFromMap(AttrMap<int,double>& inputMap, std::set<int>& keys);
  
  // TODO refactor
  // new class just to take care of the heatmap
  class ClusterMap{
  
    public:
  
    // cell data points
    std::set<int> cellLabels;
    std::vector<AttrMap<int, double> > cellFeatures;
    std::map<QString, int> attrIdxMap;
  
    std::vector<Point2d> minMaxV;
  
    // min and max values for the display
    Point2d xMinMax;
    Point2d yMinMax;
  
    bool customMinMax;
  
    bool mode2D;
    std::pair<int, int> attrIdx2D;
  
    double heatMax;
  
    bool showParentLabels;
  
    // manually specified clusters
    std::vector<ManualCluster> cluster; // each cluster is a double (pos) defined by the attrIdx
  
    int activeCluster;
  
    std::vector<Point2d> maximaHeatMap;
    std::map<int,int> cellLabelMaximaIdxMap;
    std::map<int,int> maximaIdxManualClusterIdxMap;
  
    QString selectedX, selectedY, selectedHeat;
  
    // gaussian clusters
    double sigma;
  
    // size of the GUI image
    bool largeDots;
  
    int gridSize;
    int gridFactor;
    // view array (data of the GUI image)
    std::vector<std::vector<double> > viewData;
  
    // the current QImage
    QImage clusterMapImage;
  
    std::map<int, int> parentLabels;
    std::map<std::pair<int, int>, int> imageLabelMap;
  
  
    // input values
    void addCellFeature(QString featureName, AttrMap<int, double>& newFeature) {
      if(cellFeatures.size() == 0){
        typedef std::pair<int, double> IntDoubleP;
        std::set<int> allLabels;
        forall(IntDoubleP p, newFeature){
          allLabels.insert(p.first);
        }
        cellLabels = allLabels;
        
      }
      std::cout << "in add " << featureName.toLocal8Bit().constData() << std::endl;
      if(attrIdxMap.find(featureName) == attrIdxMap.end()){
        std::cout << "added " << std::endl;
        cellFeatures.push_back(newFeature);
        attrIdxMap[featureName] = cellFeatures.size();
        Point2d minMax = minMaxFromMap(cellFeatures[attrIdxMap[featureName]-1], cellLabels);
        minMaxV.push_back(minMax);
      }
      
    }
    double scaledValue(double value, int measureIdx)
    {
      double diff = minMaxV[measureIdx].y()-minMaxV[measureIdx].x();
      if(diff == 0) return 0;
      return ((value - minMaxV[measureIdx].x())/diff);
    }
  
    void resetCellFeatures() {
      std::vector<AttrMap<int, double> > cellFeaturesNew;
      std::vector<Point2d> minMaxVNew;
  
      std::map<QString, int> attrIdxMapNew;
      cellFeatures = cellFeaturesNew;
      attrIdxMap = attrIdxMapNew;
      minMaxV = minMaxVNew;
    }
  
    void setActiveMeasures(QString measure1, QString measure2, QString measureHeat);
  
    // create the cell display
    void createCellView();
  
    // create the heatmap display
    void createHeatMap2D();
  
    QImage createImage(bool heatMapMode); // in heatmapsmode: draw local maxima crosses and scale automatically
  
    // set cluster postion
    void addCluster(QString measure1, QString measure2, double value1, double value2);
    void updateCluster(int clusterIdx, QString measure, double value, bool xCoord = true);
    bool nearbyManualCluster(QString measure1, QString measure2, Point2d pos, int& maxIdx);
    void resetClusters(){
      std::vector<ManualCluster> newClusters;
      cluster = newClusters;
    }
  
    void setParentsCellView();
    void setParentsHeatMapView();
  
    void findMaximaHeatMap();
    void relateCellsToMaxima();
  
    // initialize the data array with the right size (gridSize)
    void initDataArray();
  
    void calcHeatMax();
  
    // return the attr value for a specific label and measure
    double getValue(QString measure, int label);
  
  
    double getClusterValue(QString measure, int clusterIdx);
  
  };
  */
  
  
}
#endif
