//
//  PickingPlugin.h
//  Preview3D
//
//  Created by Olga Diamanti on 9/21/11.
//  Copyright 2011 ETH Zurich. All rights reserved.
//

#ifndef PICKING_PLUGIN_H
#define PICKING_PLUGIN_H
#include "ViewerPlugin.h"

#include <stack>

void TW_CALL SaveSelectionCB(void *clientData);
void TW_CALL LoadSelectionCB(void *clientData);
void TW_CALL ResetCB(void *clientData);

//bfs for connected components
void bfs(const std::vector<int> &vertices, const int start_index, const std::vector< std::vector<IndexType > > &edges, int r, std::vector<int> &V );

//predicate for determining if a picked vertex has already been assigned a component
bool is0 (int i) ;

//connected components on the selected vertices
int connected_components (const std::vector<int> &vertices, // indices of the selected vertices
                          const std::vector<std::vector<IndexType > > &edges, // neighbors of *all* the vertices (i.e. also contains neighborhood info for vertices outside the selected set, and edges[i] != edges[vertices[i]])
                          std::vector<int> &component //connected component assigned to each vertex (1-based, 0 indicates no assigned component)
                          );


//determine whether (x,y) is inside/outside of a polyline
//1=inside, 0=outside
// from http://www.visibone.com/inpoly/
int inpoly ( const std::vector<std::vector<unsigned int > >&poly, //polygon points, [0]=x, [1]=y. Polyline need not be closed (i.e. first point != last point), the line segment between last and first selected points is constructed within this function.
            const unsigned int xt, //x (horizontal) of target point
            const unsigned int yt) //y (vertical) of target point
;

void adjacency_list(
    const FaceMatrixType& F, 
    std::vector<std::vector<IndexType> >& A);

class PickingPlugin : public PreviewPlugin
{
public:
  #define NUM_MOUSE_MODE 3
  enum MouseMode { TRANSLATE_HANDLE, ROTATE_HANDLE, DELETE_SELECTION };
  
  static PickingPlugin& GetReference();

  PickingPlugin();
  ~PickingPlugin();

  // implement Serializable interface
  bool Serialize(tinyxml2::XMLDocument* doc, tinyxml2::XMLElement* element);
  bool Deserialize(tinyxml2::XMLDocument* doc, const tinyxml2::XMLElement* element);
  //void InitWithDefaultValue();
  
  // initialization (runs every time a mesh is loaded or cleared)
  void init(Preview3D *preview);
  
  // keyboard callback
  bool keyDownEvent(unsigned char key, int modifiers, int mouse_x, int mouse_y) ;
  
  //mouse callback
  bool mouseDownEvent(int mouse_x, int mouse_y, int button, int modifiers);
  bool mouseUpEvent(int mouse_x, int mouse_y, int button, int modifiers);
  bool mouseMoveEvent(int mouse_x, int mouse_y);
  bool mouseScrollEvent(int mouse_x, int mouse_y, float delta);
  
  //stuff that is drawn by the plugin before the previewer has displayed the mesh
  //first draw 3d, then 2d
  void preDraw(int currentTime);
  //stuff that is drawn by the plugin after the previewer has displayed the mesh
  //first draw 3d, then 2d
  void postDraw(int currentTime) ;
  

  
  //paints a cylinder with radius r between points a and b
  static void paintCylinder(const Vector3 &a, const Vector3 &b, const double radius);
  
  //paints a cone given its bottom and top points and its bottom radius
  static void paintCone(const Vector3 &bottom, const Vector3 &top, const double radius);
  
  
  //paints an arrow between from and to with a given radius
  static void paintArrow(const Vector3 &from, const Vector3 &to, double radius);
  
  
  //paints a coordinate frame (3 axes x,y,z in the form of arrows colored red green and blue).
  //selected: a 3-element boolean array. selected[i] = true signifies that the i-th axes is selected, in which case the painted arrow will be thicker
  static void paintCoordinateFrame(const Vector3&point, const double length, const double radius, const bool *selected = 0);
  
  //implements picking of one of the coordinate axes
  static int pick_axes(const Vector3 &point, //3d point corresponding to the center of the coordinate frame
                       const double length, //length of the arrows
                       const double radius, //radius of the arrows
                       const int &mouse_x, //mouse location
                       const int &mouse_y, //mouse location
                       GLint *viewport, //opengl viewport
                       const double *projection_matrix, //opengl projection matrix
                       const double *modelview_matrix, //opengl modelview matrix
                       const int w_x = 1,  // optional: width of window around the mouse in which we will look for the selected axes
                       const int w_y = 1  // optional: height of window around the mouse in which we will search for the selected axes
                       );
  
  //implements picking of a face and returns the index of the vertex in the face that was closest to the mouse point
  static int pick_face_and_vertex(const int &mouse_x, // mouse location
                                  const int &mouse_y,
                                  GLint *viewport, //viewport
                                  const double *projection_matrix, //opengl projection matrix
                                  const double *modelview_matrix, //opengl modelview matrix
                                  const PointMatrixType *vertices, //list of vertices
                                  const FaceMatrixType *faces, //list of faces
                                  std::vector< std::pair<double,IndexType> > &hit_face_list, // output: list of hit faces
                                  const std::vector<int > &face_indices = std::vector<int> (0), // optional: list of face indices within faces that should be tested for picking. 
                                  const std::vector<int > &face_labels = std::vector<int> (0), // optional: labels of faces that should be tested for picking. Should be of the same size as face_indices.
                                  const int w_x = 1, // optional: width of window around the mouse in which we will search for faces
                                  const int w_y = 1, // optional: height of window around the mouse in which we will search for faces
                                  const int *center_x = 0, // optional: center of the window around the mouse in which we will search for faces. If not provided, the mouse position will be used.
                                  const int *center_y = 0);
  
  static void computeRegionCentroid(const PointMatrixType *vertices,
                                    const std::vector<int> &labels,
                                    int r,
                                    RowVector3 &centroid);
  
  static void computeRegionBoundingBox(const PointMatrixType *vertices,
                                       const std::vector<int> &labels,
                                       int r,
                                       RowVector3 &aabb,
                                       RowVector3 &AABB);
  

  //saves the current selection to a file
  bool saveSelection();

  //loads a selection to a file
  bool loadSelection();
  
  //colors selection with different colors per region
  void colorSelection();
  
  void reset_mesh();

  // calculate a rotation given the mouse location and the mouse location when the dragging began
  void get_rotation(int mouse_x,
                           int from_x,
                           int mouse_y,
                           int from_y,
                           double depth,
                           double *modelview_matrix,
                           double *projection_matrix,
                           int *viewport,
                           float *rotation,
                           bool *selected_axis =0 //if this is set, we will only rotate along the axis i for which selected_axis[i] = true 
                           );

  // calculate a translation given the mouse location and the mouse location when the dragging began
  void get_translation(int mouse_x,
                              int from_x,
                              int mouse_y,
                              int from_y,
                              double depth,
                              double *modelview_matrix,
                              double *projection_matrix,
                              int *viewport,
                              float *translation,
                              bool *selected_axis =0  //if this is set, we will only rotate along the axis i for which selected_axis[i] = true 
                              );
  
  void get_constrained_positions(int r,
                                 std::vector<IndexType > &constrained_vertices,
                                 Eigen::Matrix<ScalarType, Eigen::Dynamic, Eigen::Dynamic> &constrained_positions,
                                 bool overwrite_original_positions);

  void selection_changed();
  void selected_vertices_moved();
  
protected:
  //enum, specifying what action the mouse will take
  MouseMode mouse_mode;
  
  //flag, shows whether the mouse is currently selecting
  bool selecting;

  // flag, if face or vertex selection mode
  bool vertexSelection;
  
  //flag, shows whether action (translation/rotation) should be taken in the next move (eg. if the mouse did not hit the mesh, no action will be taken)
  bool activate;
  
  //the key hit
  Preview3D::KeyModifier m_key_modifier;
  
  //stores the original mesh
  PointMatrixType *original_vertices;

  // stores the mesh before a translation/rotation
  PointMatrixType *previous_vertices;
  
  //stuff for mouse callbacks
  int from_x, from_y;
  float depth_at_mouse_pos;
  int to_x, to_y;
  
  //stores translations and rotations
  float translation[3];
  float rotation[4];

  //color of handle
  bool random_colors;
  float handle_color[4];
  
  //vector of size equal to the number of vertices
  //if selection_id[i] = -1,  then vertex i is free (will be deformed)
  //if selection_id[i] = r, vertex i belongs to handle #r (will be either translated/rotated directly, or it will stay put)
  std::vector<int > selection_id;
  
  //contains all the free region ids
  std::stack<int> m_free_regions;
  
  //index of the handle that is currenlty picked, and will be translated/rotated (the non-fixed handle)
  int m_currentRegion;
  
  // array containing random colors
  Eigen::Matrix<double,Eigen::Dynamic,3> region_colors;
  Eigen::Matrix<double,Eigen::Dynamic,3> random_region_colors;

  //list of constrained vertex indices and positions
  std::vector<IndexType > m_constrained_vertices;
  Eigen::Matrix<ScalarType, Eigen::Dynamic, Eigen::Dynamic>  m_constrained_vertex_positions;

  //vector of mouse position used by the lasso selection tool
  //anything inside the polyline specified by these points will be picked
  std::vector<std::pair<int, int> > mousePoints;
  
  
  //option to update the mesh while moving
  //if set, mesh will be updated while the used drags the mouse
  //otherwise, it will only be updated upon release of the mouse button
  bool m_UPDATE_WHILE_MOVING;
  
  //option to use a coordinate frame for the deformation or not
  //if set, user can pick an axis and only rotate/translate along that axis
  //otherwise, the rotation/translation is computed based on the mouse motion
  bool m_USE_DISPLAYED_AXES;
  
  // Pointer to the tweak bar
  igl::ReTwBar* bar;  
  
  //specifies whether the mesh is valid
  bool m_meshIsLoaded;
  
  //selected axis: if axis_is_selected[i] is on, then axis i is selected by the user (axes are in the x,y,z order)
  bool axis_is_selected[3];
  
  //options: length and radius of the axis arrows
  double axes_length, axes_radius;
  
  //the origin of the coordinate frame of the axes (if applicable)
  RowVector3 m_cpoint3;
  //the origin of the coordinate frame of the axes before current translation/rotation
  RowVector3 previous_m_cpoint3;

  //the selection file name
  std::string selFilename;
  
  
  
};

#endif