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

#include <Process.hpp>

namespace mgx
{
  ///\addtogroup MeshProcess
  ///@{
  /**
   * \class MeshSelectAll
   *
   * Select all the vertices in the current mesh.
   */
  class mgxBase_EXPORT MeshSelectAll : public Process
  {
  public:
    MeshSelectAll(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select All");
    setDesc("Select all vertices of the current mesh");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshSelectBadNormals
   *
   * Select all the vertices with invalid normals in the current mesh
   */
  class mgxBase_EXPORT MeshSelectBadNormals : public Process
  {
  public:
    MeshSelectBadNormals(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Bad Normals");
    setDesc("Select all vertices of the current mesh with normals that cannot be calculated");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshUnselect
   *
   * Ensure no vertex is selected in the current mesh.
   */
  class mgxBase_EXPORT MeshUnselect : public Process
  {
  public:
    MeshUnselect(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Unselect");
    setDesc("Unselect the vertices of the current mesh");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshInvertSelection
   *
   * Invert the selection status of all the vertices in the current mesh.
   */
  class mgxBase_EXPORT MeshInvertSelection : public Process
  {
  public:
    MeshInvertSelection(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Invert Selection");
    setDesc("Invert the selection of the current mesh.");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshSelectUnlabeled
   *
   * Select all the unlabel vertices of the current mesh. Other vertices may either
   * be unselected, or stay in their current selection state.
   */
  class mgxBase_EXPORT MeshSelectUnlabeled : public Process {
  public:
    MeshSelectUnlabeled(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Unlabeled");
    setDesc("Add to or replace the selection with the unlabeled vertices.");

    addParm("Replace selection","Replace selection","No",booleanChoice());
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      bool replace = stringToBool(parm("Replace selection"));
      return run(currentMesh(), replace);
    }

    bool run(Mesh* m, bool replace);

  };

  /**
   * \class MeshSelectLabeled
   *
   * Select all labeled vertices in the current mesh.
   */
  class mgxBase_EXPORT MeshSelectLabeled : public Process
  {
  public:
    MeshSelectLabeled(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Labeled");
    setDesc("Add to or replace the selection with the labeled vertices.");

    addParm("Replace selection","Replace selection","No",booleanChoice());
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      bool replace = stringToBool(parm("Replace selection"));
      return run(currentMesh(), replace);
    }

    bool run(Mesh* m, bool replace);

  };

  /**
   * \class MeshSelectLabels
   */
  class mgxBase_EXPORT MeshSelectLabels : public Process
  {
  public:
    MeshSelectLabels(const Process& process) : Process(process)
    {
      setName("Mesh/Selection/Select Labels");
      setDesc("Add to or replace the selection with the labeled vertices.");

      addParm("Labels","List of labels to select, 0 for current","0");
      addParm("Replace Selection","Replace selection", "No", booleanChoice());
    }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      bool replaceSelection = stringToBool(parm("Replace Selection"));
      Mesh *mesh = currentMesh();
      if(!mesh)
        throw QString("%1::run No current mesh:");

      IntSet labels;
      for(auto s : parm("Labels").split(QRegExp("[ ,;:]"))) {
        s = s.trimmed();
        if(s.isEmpty())
          continue;
        int label = s.toInt();
        if(s == "0")
          label = mesh->viewLabel();
        labels.insert(label);
      }
      return run(mesh, labels, replaceSelection);
    }
    bool run(Mesh* m, const IntSet &labels, bool replace = false);
  };

   /**
   * \class MeshSelectValence
   */
  class mgxBase_EXPORT MeshSelectValence : public Process
  {
  public:
    MeshSelectValence(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select By Valence");
    setDesc("Select vertices by Valence");

    addParm("Begin Valence","","1");
    addParm("End Valence","","5");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh(), parm("Begin Valence").toInt(), parm("End Valence").toInt());
    }

    bool run(Mesh* m, int start, int end);

  };

  /**
   * \class MeshUnselectLabel ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Unselect all the vertices having a given label.
   */
  class mgxBase_EXPORT MeshUnselectLabel : public Process
  {
  public:
    MeshUnselectLabel(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Unselect Label");
    setDesc("Remove the vertices of a given label (0 for current label) from the selection.");

    addParm("Label (0 for current)","Label (0 for current)","0");  // 0
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh(), parm("Label (0 for current)").toInt());
    }

    bool run(Mesh* m, int label);

  };

  /**
   * \class MeshSelectClip ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Select all vertices within the clipped region.
   */
  class mgxBase_EXPORT MeshSelectClip : public Process
  {
  public:
    MeshSelectClip(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Clip Region");
    setDesc("Add vertices in clip region to selection.");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshSelectWholeLabelExtend ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Extent the current selection so each label having at least one vertex
   * selected will be fully selected.
   */
  class mgxBase_EXPORT MeshSelectWholeLabelExtend : public Process
  {
  public:
    MeshSelectWholeLabelExtend(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Extend to Whole Cells");
    setDesc("Extend Selection to Whole Cells");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class MeshSelectDuplicateCells ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Select vertices if the region with their label is not contiguous
   * (e.g. will be seen as more than one cell).
   */
  class mgxBase_EXPORT MeshSelectDuplicateCells : public Process
  {
  public:
    MeshSelectDuplicateCells(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Duplicate Cells");
    setDesc("Select cells with duplicate labels.");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };

  /**
   * \class ExtendByConnectivity ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Extend the current selection to all vertices that are connected to a
   * currently selected vertex.
   */
  class mgxBase_EXPORT ExtendByConnectivity : public Process
  {
  public:
    ExtendByConnectivity(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Extend by Connectivity");
    setDesc("Extend the selection to connected regions");
    setIcon(QIcon(":/images/SelectConnected.png"));
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };
  ///@}

  /**
   * \class SelectByNormal ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Selects vertices according to how close their normal is compared to pre-selected reference vertices.
   * (originally from Cell Maker)
   */
class mgxBase_EXPORT SelectByNormal : public Process
  {
    public:
      SelectByNormal(const Process& process) : Process(process)
      {
    setName("Mesh/Selection/Extend Selection by Normal");
      setDesc("Selects vertices according to how close their normal is compared to pre-selected reference vertices.");
      setIcon(QIcon(":/images/SelectConnected.png"));

      addParm("Tolerance","Tolerance threshold of the scalar of the averaged normal of selected vertices and other vertices","0.01");
    }

    bool run()
      {
        if(!checkState().mesh())
          return false;
        Mesh *m = currentMesh();
        return run(m, parm("Tolerance").toDouble());
      }

      bool run(Mesh *m, double tolerance);

  };

   /**
   * \class SelectSharedTriangles ProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Extend the current selection to all vertices that are connected to a
   * currently selected vertex.
   */
  class mgxBase_EXPORT SelectSharedTriangles : public Process
  {
  public:
    SelectSharedTriangles(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Select Shared Triangles");
    setDesc("Select triangles shared by several cells");
    setIcon(QIcon(":/images/Triangle.png"));
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }

    bool run(Mesh* m);

  };
  ///@}

 /**
   * \class AreaSelectedTris MeshProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Calculate the area of selected triangles
   */
  class mgxBase_EXPORT AreaSelectedTris : public Process
  {
  public:
    AreaSelectedTris(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Area of Selected Triangles");
    setDesc("Returns the area of all selected triangles. Either triangles with all 3 vertices selected or triangles with at least one selected vertex");
    setIcon(QIcon(":/images/Hex.png"));

    addParm("Mode","Mode","Tris inside Vtxs", QStringList() << "Tris inside Vtxs" << "Tris neighboring Vtxs");
  }

    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      Mesh* m = currentMesh();
      return run(m, parm("Mode"));
    }

    bool run(Mesh* m, QString mode);

  };


 /**
   * \class CountSelectedCells MeshProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Counts how many cells are selected and outputs it on the console
   */
  class mgxBase_EXPORT CountSelectedCells : public Process
  {
  public:
    CountSelectedCells(const Process& process) : Process(process)
    {
    setName("Mesh/Selection/Count Selected Cells");
    setDesc("Counts how many cells are selected and prints information about selected labels in the console window");
    setIcon(QIcon(":/images/Hex.png"));
  }

    bool run()
    {
      Mesh* m = currentMesh();
      return run(m);
    }

    bool run(Mesh* m);

  };

 /**
   * \class CountSelectedCells MeshProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * Counts how many cells are selected and outputs it on the console
   */
  class mgxBase_EXPORT ExtendSelectionByNeighbors : public Process
  {
  public:
    ExtendSelectionByNeighbors(const Process& process) : Process(process)
    {
      setName("Mesh/Selection/Extend Selection By Neighbors");
      setDesc("Extend Selection By Neighbor Vertices");
      setIcon(QIcon(":/images/Hex.png"));

      addParm("Shrink Selection","Shrink Selection instead of extending it","No",booleanChoice());
    }

    bool run()
    {
      Mesh* m = currentMesh();
      return run(m, stringToBool(parm("Shrink Selection")));
    }

    bool run(Mesh* m, bool shrink);

  };


  /**
   * \class SelectSpecial MeshProcessSelection.hpp <MeshProcessSelection.hpp>
   *
   * SelectSpecial
   */
  class mgxBase_EXPORT SelectSpecial : public Process
  {
    public:
      SelectSpecial(const Process& process) : Process(process)
      {

      setName("Mesh/Selection/Select Special");
      setDesc("Selects vertices of a particular type");
      setIcon(QIcon(":/images/LineBorder.png"));

      addParm("Inner Border","Inner Border","Yes",booleanChoice());
      addParm("Outer Border","Outer Border","No",booleanChoice());
      addParm("Junctions","Junctions","No",booleanChoice());
      addParm("Cell Vtxs","Cell Vtxs","No",booleanChoice());


      }

    bool run()
      {
        if(!checkState().mesh())
          return false;
        Mesh *m = currentMesh();
        return run(m, stringToBool(parm("Inner Border")), stringToBool(parm("Outer Border")), stringToBool(parm("Junctions")), stringToBool(parm("Cell Vtxs")));
      }

      bool run(Mesh *m, bool borderIn, bool borderOut, bool junctions, bool cells);

  };

  /**
   * \class SelectShortWalls
   *
   * Selects cell walls shorter than a given threshold
   */
  class mgxBase_EXPORT SelectShortWalls : public Process {
  public:
    SelectShortWalls(const Process& proc) : Process(proc)
    {
    setName("Mesh/Selection/Select Short Walls");
    setDesc("Selects cell walls shorter than a given threshold.");
    setIcon(QIcon(":/images/open.png"));

    addParm("Junction Merging Threshold","Junction Merging Threshold","0.1");
    addParm("Exclusive","never select two touching walls","Yes", booleanChoice());
    }

    bool run()
    {
      Mesh* m = currentMesh();
      return run(m, parm("Junction Merging Threshold").toDouble(), stringToBool(parm("Exclusive")));
    }
    bool run(Mesh* m, double threshold, bool exclusive);

  };

  /**
   * \class SelectShortWalls
   *
   * Selects cell walls shorter than a given threshold
   */
  class mgxBase_EXPORT DistanceCells : public Process {
  public:
    DistanceCells(const Process& proc) : Process(proc)
    {
    setName("Mesh/Selection/Distance Selected Cells");
    setDesc("Outputs the distance of selected cells on the console");
    setIcon(QIcon(":/images/open.png"));

    }

    bool run()
    {
      Mesh* m = currentMesh();
      return run(m);
    }
    bool run(Mesh* m);

  };


}

#endif
