//
// 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 MESH_PROCESS_STRUCTURE_HPP
#define MESH_PROCESS_STRUCTURE_HPP

#include <Process.hpp>

#include <Misc.hpp>
#include <Subdivide.hpp>

namespace mgx
{
  ///\addtogroup MeshProcess
  ///@{
  
  /**
   * \class TransformMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Apply an affine transformation to the vertices of a mesh (all of them or just 
   * the selected ones).
   */
  class mgxBase_EXPORT TransformMesh : public Process
  {
  public:
    TransformMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Transform Mesh");
	  setDesc("Apply an affine transformation to all vertices of a mesh");
	  setIcon(QIcon(":/images/Resize.png"));

	  addParm("translation (µm)","Translation in micrometers. Enter x y z separated by spaces","0.0 0.0 0.0");
	  addParm("rotation axis","Vector representing the axis of rotation. Enter x y z separated by spaces","0.0 0.0 1.0");
	  addParm("angle (degree)","Angle of rotation in degrees","0.0");
	  addParm("scale","Global scaling factor","1.0");
		}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      Point3f trans, rot;
      {
        QString txt = parm("translation (µm)");
        QTextStream ts(&txt);
        ts >> trans;
      }
      {
        QString txt = parm("rotation axis");
        QTextStream ts(&txt);
        ts >> rot;
      }
      bool ok;
      float angle = parm("angle (degree)").toFloat(&ok) * M_PI / 180.f;
      if(!ok)
        return setErrorMessage("Error, the 'angle' parameter must be a number");
      float scale = parm("scale").toFloat(&ok);
      if(!ok)
        return setErrorMessage("Error, the 'scale' parameter must be a number");
      return run(currentMesh(), trans, rot, angle, scale);
    }
  
    bool run(Mesh* mesh, Point3f translation, Point3f rotation_axis, float rotation, 
      float scale);
      
  };
  
  /**
   * \class ReverseMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Reverse the orientation of the mesh.
   */
  class mgxBase_EXPORT ReverseMesh : public Process
  {
  public:
    ReverseMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Reverse Mesh");
	  setDesc("Reverse orientation of the mesh");
	  setIcon(QIcon(":/images/Invert.png"));	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }
    bool run(Mesh* mesh);

  };
  
  /**
   * \class SmoothMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Average each vertex position based on its neighbors.
   */
  class mgxBase_EXPORT SmoothMesh : public Process
  {
  public:
    SmoothMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Smooth Mesh");
	  setDesc("Average each vertex position based on its neighbors.");
	  setIcon(QIcon(":/images/SmoothMesh.png"));
	  
	  addParm("Passes","Passes","1");
	  addParm("Walls Only","Walls Only","No",booleanChoice());
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Passes").toUInt(), stringToBool(parm("Walls Only")));
    }
  
    bool run(Mesh* mesh, uint passes, bool wallsOnly = false);

  };
  
  /**
   * \class ShrinkMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Displace each vertex towards the mesh center, perpendicular to the surface.
   */
  class mgxBase_EXPORT ShrinkMesh : public Process
  {
  public:
    ShrinkMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Shrink Mesh");
	  setDesc("Displace each vertex towards the mesh center, perpendicular to the surface.");
	  setIcon(QIcon(":/images/ShrinkMesh.png"));

	  addParm("Distance(µm)","Vertex displacement. If negtive, the mesh will expand.","1.0");	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Distance(µm)").toFloat());
    }
  
    bool run(Mesh* mesh, float distance);

  };
  
  /**
   * \class LoopSubdivisionMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide the mesh uniformly using Loop subdivision.
   */
  class mgxBase_EXPORT LoopSubdivisionMesh : public Process
  {
  public:
    LoopSubdivisionMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Loop Subdivision");
	  setDesc("Subdivide the mesh uniformly using Loop subdivision.");	
	  setIcon(QIcon(":/images/SubdivideTri.png"));
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* mesh);

  };
  
  /**
   * \class SubdivideMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide the mesh uniformely, without displacing existing vertices.
   */
  class mgxBase_EXPORT SubdivideMesh : public Process
  {
  public:
    SubdivideMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Subdivide");
	  setDesc("Subdivide the mesh unifromly");	
	  setIcon(QIcon(":/images/SubdivideTri.png"));
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* mesh);
  };
  
  /**
   * \class AdaptiveSubdivideBorderMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide triangles around cell borders.
   */
  class mgxBase_EXPORT AdaptiveSubdivideBorderMesh : public Process
  {
  public:
    AdaptiveSubdivideBorderMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Subdivide Adaptive Near Borders");
	  setDesc("Subdivide triangles around cell borders");
	  setIcon(QIcon(":/images/SubdivideTriAdapt.png"));

	  addParm("Max Area(µm²)","Area threshold (in square microns) for subdivision, triangles smaller than this won't be subdivided","0.1");
	  addParm("Border Dist(µm)","Distance (in microns) from cell borders that triangles will be subdivided","1.0");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Max Area(µm²)").toFloat(), parm("Border Dist(µm)").toFloat());
    }
  
    bool run(Mesh* mesh, float cellMaxArea, float borderDist);

  };
  
  /**
   * \class AdaptiveSubdivideSignalMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide triangles depending on mesh signal. Triangle size is determined
   * by a high and low area, which is interpolated based on the minimum and
   * maximum signals
   */
  class mgxBase_EXPORT AdaptiveSubdivideSignalMesh : public Process
  {
  public:
    AdaptiveSubdivideSignalMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Subdivide Adaptive by Signal");
	  setDesc("Subdivide triangles depending on mesh signal. Triangle size \N"
	  "is determined by a high and low area, which is interpolated \N"
	  "based on the minimum and maximum signals");
	  setIcon(QIcon(":/images/SubdivideTriAdapt.png"));

	  addParm("Low Max Area(µm²)","Maximum area (square microns) for low instentity voxels","1.0");
	  addParm("High Max Area(µm²)","Maximum area (square microns) for high instentity voxels","0.1");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Low Max Area(µm²)").toFloat(), parm("High Max Area(µm²)").toFloat());
    }
  
    bool run(Mesh* mesh, float cellMaxAreaLow, float cellMaxAreaHigh);

  };
  
  /**
   * \class SubdivideBisectMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide triangles area with bisection.
   */
  class mgxBase_EXPORT SubdivideBisectMesh : public Process
  {
  public:
    SubdivideBisectMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Subdivide with Bisection MGX3D");
	  setDesc("Subdivide triangles area with bisection");
	  setIcon(QIcon(":/images/SubdivideTriAdapt.png"));

	  addParm("Max Area(µm²)","Max Area(µm²)","1.0");	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Max Area(µm²)").toFloat());
    }
  
    bool run(Mesh* mesh, float cellMaxArea);

  };

  /**
   * \class SubdivideBisectMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Subdivide triangles area with bisection.
   */
  class mgxBase_EXPORT SubdivideBisectMesh3D : public Process
  {
  public:
    SubdivideBisectMesh3D(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Subdivide with Bisection MGX3D");
	  setDesc("Subdivide triangles area with bisection");
	  setIcon(QIcon(":/images/SubdivideTriAdapt.png"));

	  addParm("Max Area(µm²)","Max Area(µm²)","1.0");	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Max Area(µm²)").toFloat());
    }
  
    bool run(Mesh* mesh, float cellMaxArea);
  
  };
  
  /**
   * \class ScaleMesh ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Scale the mesh. It is possible to specify a negative number, in which case the 
   * dimension will be mirrored.
   */
  class mgxBase_EXPORT ScaleMesh : public Process
  {
  public:
    ScaleMesh(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Scale Mesh");
	  setDesc("Scale Mesh, or a selected part of it. \n"
	  "It is possible to specify a negative number, in which case the dimension will be mirrored. \N"
	  "If either 1 or 3 axis are mirrored, then the whole mesh needs to be scaled, as these triangles will change orientation");
	  setIcon(QIcon(":/images/Scale.png"));

	  addParm("X Scale","X Scale","1.0");
	  addParm("Y Scale","Y Scale","1.0");
	  addParm("Z Scale","Z Scale","1.0");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("X Scale").toFloat(), parm("Y Scale").toFloat(), parm("Z Scale").toFloat());
    }
  
    bool run(Mesh* mesh, float scaleX, float scaleY, float scaleZ);

  };
  
  /**
   * \class MeshDeleteValence ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Delete vertices whose valence is wihin a given range.
   */
  class mgxBase_EXPORT MeshDeleteValence : public Process
  {
  public:
    MeshDeleteValence(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Delete Mesh Vertices by Valence");
	  setDesc("Delete mesh vertices that have valence within the specified range");
	  setIcon(QIcon(":/images/DeleteValence"));

	  addParm("Start Valence","Start Valence","0");
	  addParm("End Valence","End Valence","2");	
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh(), parm("Start Valence").toInt(), parm("End Valence").toInt());
    }
  
    bool run(Mesh* mesh, int startValence, int endValence);

  };
  
  /**
   * \class MergeVertices ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Merge selected vertices into one.
   */
  class mgxBase_EXPORT MergeVertices : public Process
  {
  public:
    MergeVertices(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Merge Vertices");
	  setDesc("Merge selected vertices into one.");	
	  setIcon(QIcon(":/images/MergeVertices"));
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* mesh);

  };

   /**
   * \class SplitEdges ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Split a edges in the graph.
   */
  class mgxBase_EXPORT SplitEdges : public Process
  {
  public:
    SplitEdges(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Split Edges");
	  setDesc("Split edges by inserting a new vertex in the middle.");
	  setIcon(QIcon(":/images/SplitEdge"));
	  
	  addParm("Max Dist","Maximum distance between vertices (um), 0 divides once","1.0");
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;

      Mesh *mesh = currentMesh();
      vvGraph &S = mesh->graph();

      bool result = run(mesh, parm("Max Dist").toDouble());
      SETSTATUS("Mesh " << mesh->userId() << " - Split edges, total vertices:" << S.size());
      return result;
    }
  
    bool run(Mesh* mesh, double maxDist, Subdivide *sDiv = 0);
  
  }; 

  /**
   * \class DeleteEdge ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Delete the edge between 2 selected vertices.
   */
  class mgxBase_EXPORT DeleteEdge : public Process
  {
  public:
    DeleteEdge(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Delete Edge");
	  setDesc("Delete edge between 2 selected vertices.");	
	  setIcon(QIcon(":/images/DeleteLabel.png"));		
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY))
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* mesh);

  };
  
  /**
   * \class MeshDeleteSelection ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Delete vertices selected in the current mesh, preserving cells.
   */
  class mgxBase_EXPORT MeshDeleteSelection : public Process
  {
  public:
    MeshDeleteSelection(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Delete Selection");
	  setDesc("Delete vertices selected in the current mesh, preserving cells.");	
	  setIcon(QIcon(":/images/DeleteLabel.png"));		
	}
  
    bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      return run(currentMesh());
    }
  
    bool run(Mesh* m);
  
  };
  
  /**
   * \class MeshKeepVertices ProcessStructure.hpp <MeshProcessStructure.hpp>
   *
   * Mark vertices so that they can survive as lines and points. Also prevents labels changing.
   */
  class mgxBase_EXPORT MeshKeepVertices : public Process
  {
  public:
    MeshKeepVertices(const Process& process) : Process(process) 
    {
	  setName("Mesh/Structure/Keep Vertices");
	  setDesc("Mark vertices so that they can survive as lines and points. Also prevents labels changing.");
	  setIcon(QIcon(":/images/KeepVertices.png"));		

	  addParm("Keep","Keep","Yes",booleanChoice());
	}
	
	bool run()
    {
      if(!checkState().mesh(MESH_NON_EMPTY | MESH_SHOW_MESH))
        return false;
      bool keep = stringToBool(parm("Keep"));
      return run(currentMesh(), keep);
    }
  
    bool run(Mesh* m, bool keep);	
  
  };
  ///@}
}
#endif
