//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 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 <MeshProcessSelection.hpp>
#include <GraphUtils.hpp>

#include <MeshProcessPDG.hpp> //jInfo

#include <algorithm>    // std::find

namespace mgx
{
  bool MeshSelectAll::run(Mesh* m)
  {
    forall(const vertex& v, m->graph())
      v->selected = true;
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectAll);

  bool MeshSelectBadNormals::run(Mesh* m)
  {
    int count = 0;
    vvGraph &S = m->graph();
    forall(const vertex& v, S) {
      v->selected = !setNormal(S, v);
      if(v->selected)
        count++;
    }
    SETSTATUS("Selected " << count << " vertices with bad normals");
    m->updateSelection();
    return true;
  }
  REGISTER_PROCESS(MeshSelectBadNormals);

  bool MeshUnselect::run(Mesh* m)
  {
    forall(const vertex& v, m->graph())
      v->selected = false;
    m->updateSelection();
    return true;
  }
  REGISTER_PROCESS(MeshUnselect);

  bool MeshInvertSelection::run(Mesh* m)
  {
    forall(const vertex& v, m->graph())
      v->selected ^= true;
    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }

  REGISTER_PROCESS(MeshInvertSelection);

  bool MeshSelectUnlabeled::run(Mesh* m, bool replace)
  {
    forall(const vertex& v, m->graph())
      if(v->label == 0)
        v->selected = true;
      else if(replace and v->label != 0)
        v->selected = false;

    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectUnlabeled);

  bool MeshSelectLabeled::run(Mesh* m, bool replace)
  {
    forall(const vertex& v, m->graph()) {
      if(v->label > 0)
        v->selected = true;
      else if(replace and v->label == 0)
        v->selected = false;
    }
    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectLabeled);

  bool MeshSelectLabels::run(Mesh* m, const IntSet &labels, bool replaceSelection)
  {
    for(const vertex &v : m->graph()) {
      if(replaceSelection)
        v->selected = false;
      if(!v->selected and labels.count(v->label) > 0)
        v->selected = true;
    }

    m->correctSelection(true);
    m->updateSelection();

    return true;
  }
  REGISTER_PROCESS(MeshSelectLabels);

  bool MeshSelectValence::run(Mesh* m, int start, int end)
  {
    vvGraph &S = m->graph();
    forall(const vertex& v, m->graph()) {
      if(S.valence(v) >= uint(start) and S.valence(v) <= uint(end))
        v->selected = true;
    }

    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectValence);

  bool MeshUnselectLabel::run(Mesh* m, int label)
  {
    if(label <= 0)
      label = selectedLabel();
    if(label <= 0)
      throw(QString("Cannot unselect label, no current label is defined"));

    forall(const vertex& v, m->graph()) {
      if(v->label == label)
        v->selected = false;
    }

    m->correctSelection(true);

    m->updateSelection();

    SETSTATUS(QString("Unselected label %1, vertices selected: %2").arg(label).arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshUnselectLabel);

  bool MeshSelectClip::run(Mesh* m)
  {
    forall(const vertex& v, m->graph()) {
      const Stack* s = m->stack();
      bool clipped = false;
      Point3f p = Point3f(s->frame().inverseCoordinatesOf(qglviewer::Vec(v->pos)));
      if(clip1()->isClipped(p))
        clipped = true;
      if(clip2()->isClipped(p))
        clipped = true;
      if(clip3()->isClipped(p))
        clipped = true;
      if(!clipped)
        v->selected = true;
    }
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectClip);

  bool MeshSelectWholeLabelExtend::run(Mesh* m)
  {
    std::set<int> labels;

    forall(const vertex& v, m->graph())
      if(v->selected and v->label > 0)
        labels.insert(v->label);

    forall(const vertex& v, m->graph())
      if(labels.find(v->label) != labels.end())
        v->selected = true;

    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectWholeLabelExtend);

  bool MeshSelectDuplicateCells::run(Mesh* m)
  {
    // Labels to select
    std::map<int, int> LabCount;
    std::map<int, vertex> Labels;
    std::set<vertex> Vertices;

    // Grab one vertex for each label
    vvGraph& S = m->graph();
    forall(const vertex& v, S) {
      Vertices.insert(v);
      Labels[v->label] = v;
    }

    // Find contigs
    while(!Vertices.empty()) {
      // Grab any vertex and save the label
      vertex v = *Vertices.begin();
      Vertices.erase(v);
      int label = v->label;

      // Start growing neighbor set
      std::set<vertex> Nbs;
      std::set<vertex> NewNbs;
      Nbs.insert(v);
      do {
        forall(const vertex& u, Nbs)
          forall(const vertex& n, S.neighbors(u))
            if(Vertices.count(n) > 0 and n->label == label) {
              Vertices.erase(n);
              NewNbs.insert(n);
            }
        Nbs = NewNbs;
        NewNbs.clear();
      } while(!Nbs.empty());

      // One more region for this label
      LabCount[label]++;
    }

    // Mark labels with more than one region selected
    forall(const vertex& v, S)
      if(LabCount[v->label] > 1)
        v->selected = true;
      else
        v->selected = false;

    m->correctSelection(true);
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(MeshSelectDuplicateCells);

  bool ExtendByConnectivity::run(Mesh* m)
  {
    vvGraph& S = m->graph();
    std::vector<vertex> vs = m->activeVertices();
    // Either all vertices are selected, or none
    if(vs.size() == S.size())
      return true;
    std::set<vertex> selected(vs.begin(), vs.end());
    // Tabular approach
    for(size_t i = 0; i < vs.size(); ++i) {
      vertex v = vs[i];
      forall(const vertex& n, S.neighbors(v)) {
        if(selected.count(n))
          continue;
        n->selected = true;
        selected.insert(n);
        vs.push_back(n);
      }
    }
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(ExtendByConnectivity);

  bool SelectByNormal::run(Mesh *m, double tolerance)
  {
    vvGraph& S = m->graph();

    Point3d nrml (0,0,0);
    int counter = 0;

    // take average normal selected vertices
    forall(const vertex& v, S){
      if(v->selected){
        nrml += v->nrml;
        counter++;
      }
    }

    nrml /= counter;
    nrml /= norm(nrml);

    forall(const vertex& v, S){
      double dis = v->nrml * nrml; // 1 for identical, -1 for opposite
      if(dis > 1-tolerance) v->selected = true;
    }

    m->updateAll();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(SelectByNormal);

  bool SelectSharedTriangles::run(Mesh* m)
  {
	  if(m->meshType() != "MGX3D")
		  throw(QString("Mesh type (%1) doesn't have shared triangles, mesh type must be (MGX3D)").arg(m->meshType()));
	  const cellGraph &Cells = m->cells();

    // first unselect everything
    forall(const vertex& v, m->graph())
      v->selected = false;
    m->updateSelection();

    // list of triangle, to find duplicates
    std::set<Triangle> triList;
	  std::pair<std::set<Triangle>::iterator,bool> newTri, newTriRot;

    // look which triangles belong to more than one cell
	  forall(const cell &c, Cells)
      forall(const vertex &v, c->S)
		    forall(const vertex &n, c->S.neighbors(v)) {
				  vertex m = c->S.nextTo(v, n);
			    if(!c->S.uniqueTri(v, n, m))
				    continue;

				  newTri = triList.insert(Triangle(v,n,m));
					// triangle(v,n,m) in one cells will be oriented (v,m,n) in the other cell
				  newTriRot = triList.insert(Triangle(v,m,n));
					// if triangle already exist in another cell, select its vertices
					if(newTri.second == false or newTriRot.second == false) {
					  n->selected = true;
					  v->selected = true;
					  m->selected = true;
					}
				}
    m->updateSelection();

    SETSTATUS(QString("%1 vertices selected").arg(m->selectedCount()));
    return true;
  }
  REGISTER_PROCESS(SelectSharedTriangles);

  bool AreaSelectedTris::run(Mesh* m, QString mode)
  {

    double area = 0;

    const std::vector<vertex>& vs = m->selectedVertices();
    vvGraph& S = m->graph();

    if(mode == "Tris inside Vtxs"){
      forall(const vertex& v, vs){
        forall(const vertex& n, S.neighbors(v)){
          if(!n->selected) continue;
          vertex m = S.nextTo(v,n);
          if(!S.uniqueTri(v,n,m) or !m->selected) continue;

          area += triangleArea(v->pos, n->pos, m->pos);
        }
      }

    } else if(mode == "Tris neighboring Vtxs"){
      forall(const vertex& v, S){
        forall(const vertex& n, S.neighbors(v)){
          vertex m = S.nextTo(v,n);
          if(!v->selected and !n->selected and !m->selected) continue;
          if(!S.uniqueTri(v,n,m)) continue;

          area += triangleArea(v->pos, n->pos, m->pos);
        }
      }
    }


    SETSTATUS(QString("Area of selected triangles: %1").arg(area));
    return true;
  }
  REGISTER_PROCESS(AreaSelectedTris);

  bool CountSelectedCells::run(Mesh* m)
  {

    std::set<int> selectedLabels, selectedParents;

    forall(vertex v, m->graph()){
      if(!v->selected or v->label < 1) continue;
      selectedLabels.insert(v->label);
      selectedParents.insert(m->parents()[v->label]);
    }

    std::cout << "Selected Cells: ";
    forall(int l, selectedLabels){
      std::cout << l << " ";
    }
    std::cout << std::endl;

    std::cout << "Selected Parents: ";
    forall(int l, selectedParents){
      std::cout << l << " ";
    }
    std::cout << std::endl;

    std::cout << "Number of selected cells: " << selectedLabels.size() << std::endl;
    std::cout << "Number of selected parents: " << selectedParents.size() << std::endl;

    SETSTATUS(QString("Number of selected cells: %1").arg(selectedLabels.size()));
    return true;
  }
  REGISTER_PROCESS(CountSelectedCells);

  bool ExtendSelectionByNeighbors::run(Mesh* m, bool shrink)
  {

    vvGraph& S = m->graph();

    std::set<vertex> newSelect;

    if(shrink){

      forall(const vertex& v, m->selectedVertices()){
        bool border = false;
        forall(const vertex& n, S.neighbors(v)){
          if(n->selected) continue;
          border = true;
        }
        if(border) newSelect.insert(v);
      }

    } else {
      forall(const vertex& v, m->selectedVertices()){
        if(!v->selected) continue;
        forall(const vertex& n, S.neighbors(v)){
          if(n->selected) continue;
          newSelect.insert(n);
        }
      }
    }

    forall(const vertex& v, newSelect){
      if(shrink) v->selected = false;
      else v->selected = true;
    }

    m->updateSelection();

    return true;
  }
  REGISTER_PROCESS(ExtendSelectionByNeighbors);

bool SelectSpecial::run(Mesh *m, bool borderIn, bool borderOut, bool junctions, bool cells)
    {

      vvGraph& S = m->graph();

      // take normal of ONE selected vertex
      forall(const vertex& v, S){
        v->selected = false;
      }

      forall(const vertex& v, S){
        if(borderIn and v->label==-1 and !v->margin) v->selected = true;
        if(borderOut and v->label==-1 and v->margin) v->selected = true;
        if(cells and v->label>-1) v->selected = true;

        std::set<int> neighborLabels;
        forall(const vertex& n, S.neighbors(v)){
          if(n->label < 1) continue;
          neighborLabels.insert(n->label);
        }
        if(neighborLabels.size() > 2){
          if(junctions) v->selected = true;
          else v->selected = false;
        }
      }

      m->updateAll();
      return true;
    }
    REGISTER_PROCESS(SelectSpecial);


      bool SelectShortWalls::run(Mesh* m, double threshold, bool exclusive)
      {
      vvGraph& S = m->graph();

      JunctionInformation jInfo;
      findCellJunctions(m, S, jInfo);
      findCellJunctionsInOrder(m, S, jInfo);

      JunctionCellsMap jCells = jInfo.junctionLabels1;
      CellJunctionsInOrderMap cellJunctionInOrder = jInfo.cellJunctionInOrder1;

      std::cout << "s" << jCells.size() << "/" << cellJunctionInOrder.size() << std::endl;

      std::map<vertex, std::pair<vertex, vertex> > vtxToMergerList;
      std::set<std::pair<vertex, vertex> > mergerList;

        forall(vertex v, S){
          v->selected = false;
        }

      forall(auto p, cellJunctionInOrder){
        std::vector<vertex> junctions = p.second;
        //std::cout << "jj " << p.first << "/" << p.second.size() << std::endl;
        if(junctions.empty()) continue;
        vertex last = junctions[junctions.size()-1];
        for(uint i=0;i<junctions.size();i++){
          vertex current = junctions[i];
          vertex last = junctions[junctions.size()-1];
          if(i != 0) last = junctions[i-1];
          if(norm(last->pos-current->pos) < threshold){
            // merge them
            std::pair<vertex, vertex> vtxP = std::make_pair(last, current);
            if(last > current) vtxP = std::make_pair(current, last);
            if(exclusive and (vtxToMergerList.find(last) != vtxToMergerList.end() or vtxToMergerList.find(current) != vtxToMergerList.end())) continue; // for now ignore if one already exists
            vtxToMergerList[last] = vtxP;
            vtxToMergerList[current] = vtxP;
            mergerList.insert(vtxP);
          }

        }
      }

      // find connection between junctions
      forall(auto juncPair, mergerList){

        vertex v1 = juncPair.first;
        vertex v2 = juncPair.second;

        std::map<vertex, std::vector<vertex> > currentVtxToPath;
        std::vector<vertex> finalPath;

        currentVtxToPath[v1].push_back(v1);
        bool stop = false;


        while(!stop){
          std::map<vertex, std::vector<vertex> > currentVtxToPathNew;
          forall(auto p, currentVtxToPath){
            forall(vertex n, S.neighbors(p.first)){
              if(n->label >= 0) continue;
              if(n == v2){
                finalPath = p.second;
                finalPath.push_back(v2);
                stop = true;
              } else {
                std::vector<vertex> path = p.second;
                path.push_back(n);
                currentVtxToPathNew[n] = path;
              }
            }
          }
          currentVtxToPath = currentVtxToPathNew;
        }

        // find connection of the two
        forall(vertex v, finalPath){
          // if(onlySelect){
            v->selected = true;
          // }
        }


      }

      m->updateAll();

      return true;

      }

      REGISTER_PROCESS(SelectShortWalls);


  bool DistanceCells::run(Mesh* m)
  {
  vvGraph& S = m->graph();

  m->updateCentersNormals();
  IntPoint3fAttr centers = (m->useParents()? m->parentCenter():m->labelCenter());

  std::set<int> selectedCells = findAllSelectedLabels(S);
  std::set<int> selectedParents;

  selectedCells.erase(-1);

  if(m->useParents()){
    std::cout << "Distance Parent Labels:"<< std::endl;
    forall(int l1, selectedCells){
      selectedParents.insert(m->parents()[l1]);
    }
    selectedCells = selectedParents;
  } else {
    std::cout << "Distance Cell Labels:"<< std::endl;
  }

  forall(int l1, selectedCells){
    forall(int l2, selectedCells){
      if(l2 <= l1) continue;
      double dis = norm(centers[l1] - centers[l2]);
      std::cout << l1 << " to " << l2 << ": " << dis << std::endl;
    }
  }

  return true;

  }

  REGISTER_PROCESS(DistanceCells);

}
