// Main class of the Viewer

#ifndef VIEWER_H
#define VIEWER_H

// Compilation options

// Disable shaders (useful on virtual machines with problematic video drivers)
// #define PREVIEW3D_NO_SHADERS


#include "types.h"

#ifdef _WIN32
#include <GL/glew.h>
#endif

#ifdef __APPLE__
#define _MACOSX
#endif

#include <AntTweakBar.h>
#include <vector>
#include <set>

#ifdef __APPLE__
#   include <OpenGL/gl.h>
#else
#   ifdef _WIN32
#       include <windows.h>
#   endif
#   include <GL/gl.h>
#endif

#ifndef PREVIEW3D_NO_SHADERS
#include "shaders.h"
#endif

// Forward declaration of the interface for meshplot.m 
class MatlabIO;

namespace igl
{
  class Timer;
  class ReTwBar;
  class XMLSerializer;
}

// Predefined color presets
enum ColorPresetID
{
  GOLD_ON_DARK_BLUE,
  SILVER_ON_DARK_BLUE,
  SILVER_ON_WHITE,
  GOLD_ON_WHITE,
  CUSTOM
};
#define NUM_COLOR_PRESET_IDS 5
#define NUM_COLOR_PRESETS NUM_COLOR_PRESET_IDS-1

// A color preset
class ColorPreset
{
public:
  float ambient[4];
  float diffuse[4];
  float specular[4];
  float back[4];
  ColorPreset(){};
  ColorPreset(
              const float ambient[4],
              const float diffuse[4],
              const float specular[4],
              const float back[4])
  {
    this->ambient[0] = ambient[0];
    this->ambient[1] = ambient[1];
    this->ambient[2] = ambient[2];
    this->ambient[3] = ambient[3];
    this->diffuse[0] = diffuse[0];
    this->diffuse[1] = diffuse[1];
    this->diffuse[2] = diffuse[2];
    this->diffuse[3] = diffuse[3];
    this->specular[0] = specular[0];
    this->specular[1] = specular[1];
    this->specular[2] = specular[2];
    this->specular[3] = specular[3];
    this->back[0] = back[0];
    this->back[1] = back[1];
    this->back[2] = back[2];
    this->back[3] = back[3];
  };
  bool operator==(const ColorPreset &other) const
  {
    return 
    this->ambient[0] == other.ambient[0] &&
    this->ambient[1] == other.ambient[1] &&
    this->ambient[2] == other.ambient[2] &&
    this->ambient[3] == other.ambient[3] &&
    this->diffuse[0] == other.diffuse[0] &&
    this->diffuse[1] == other.diffuse[1] &&
    this->diffuse[2] == other.diffuse[2] &&
    this->diffuse[3] == other.diffuse[3] &&
    this->specular[0] == other.specular[0] &&
    this->specular[1] == other.specular[1] &&
    this->specular[2] == other.specular[2] &&
    this->specular[3] == other.specular[3] &&
    this->back[0] == other.back[0] &&
    this->back[1] == other.back[1] &&
    this->back[2] == other.back[2] &&
    this->back[3] == other.back[3] ;
    
  };
  
};

// Type of normals
enum NormalsType
{
  PER_FACE,   // Use normals defined on every face
  PER_VERTEX, // Use normals defined on every vertex
  PER_CORNER  // Use normals defined on corners of triangles (multiple normals per vertex)
};
#define NUM_NORMALS_TYPE 3


////////////////////////////////////////////////////////////////////////////////
// Preview3D Class
////////////////////////////////////////////////////////////////////////////////

class Preview3D
{
  // The plugin class must be friend to be able to access the private fields of Preview3D
  friend class PreviewPlugin;

public:
  typedef std::pair< IndexType,IndexType > vertexPair;
  typedef std::pair< vertexPair, double > isoPoint;

#ifndef PREVIEW3D_NO_SHADERS
public:
  
  
  // Selected the active shader
  enum ShaderMode{
    OFF,                   // Shaders are disabled
    DIRECTIONAL_PER_PIXEL  // Phong shader (it only supports 1 light and per-vertex color)
  };
#define NUM_SHADER_MODE 2
#endif

public:

  /********* Mesh Data *********/
  // Vertices of the current mesh (#V x 3)
  // The i-th row contains the 3d coordinates of the i-th vertex
  PointMatrixType *vertices;
  // Face list of current mesh    (#F x 3) or (#F x 4)
  // The i-th row contains the indices of the vertices that forms the i-th face in ccw order (triangles only)
  FaceMatrixType *faces;

  // Face Neighbors
  std::vector<std::vector<IndexType> > vertex_to_faces;
  // Vertex Neighbors
  std::vector<std::vector<IndexType> > vertex_to_vertices;
  // Corner normals
  PointMatrixType * corner_normals;
  // list of face indices into corner normals
  FaceMatrixType *fNormIndices;   
  // One normal per face
  PointMatrixType * face_normals;
  // One color per face
  PointMatrixType * face_colors;
  // One scalar per face
  VectorX *face_property;
  // One normal per vertex
  PointMatrixType * vertex_normals;
  // One color per vertex
  PointMatrixType * vertex_colors;
  // One scalar per vertex
  VectorX *vertex_property;
  // Texture coordinates
  UVMatrixType *texCoords;
  // List of face indices into TC
  FaceMatrixType *fTexIndices;   
  // Per-face flag to enable/disable texturing of the face
  std::vector<bool> fUseTexture;

  // number of vertices in mesh should == vertices->rows()
  // This field is used only to visualize the number of vertices in the
  // AntTweakBar
  size_t number_of_vertices;
  // number of faces in mesh should == faces->rows()
  // This field is used only to visualize the number of faces in the
  // AntTweakBar
  size_t number_of_faces;
  
  // Name of the texture to load
  std::string texFilename;
  
  /*********************************/

  /******** Overlays ***************/
  // Lines plotted over the scene 
  // (Every row contains 9 doubles in the following format S_x, S_y, S_z, T_x, T_y, T_z, C_r, C_g, C_b),
  // with S and T the coordinates of the two vertices of the line in global coordinates, and C the color in floating point rgb format
  std::vector<std::vector<double > > lines;
  // Points plotted over the scene
  // (Every row contains 7 doubles in the following format P_x, P_y, P_z, size, C_r, C_g, C_b),
  // with P the position in global coordinates of the center of the point, size its dimension in screen pixels, and C the color in floating point rgb format
  std::vector<std::vector<double > > points;
  // Text labels plotted over the scene
  // Textp contains, in the i-th row, the position in global coordinates where the i-th label should be anchored
  // Texts contains in the i-th position the text of the i-th label
  std::vector<std::vector<double > > textp;
  std::vector<std::string > texts;
  /*********************************/
  
  /********* Display Lists and VBOs*********/
  // OpenGL Display list of mesh
  int faces_display_list;
  // Display list without colors
  int lines_display_list;
  // Display list without colors
  int overlay_display_list;
  int isolines_display_list;
  /*********************************/
  
  /********* Display Options *********/
  igl::ReTwBar* bar;
  bool enable_autoRefresh;
  double corner_threshold;
  bool show_overlay;
  bool show_overlay_depth;
  bool show_trackball;
  bool useOthographic; 
  bool show_isolines;
  bool show_texture;
  int linewidth;
  bool g_AutoRotate;
  bool show_faces;
  bool show_lines;
  bool show_vertid;
  bool show_faceid;
  bool invert_normals;
  float radius;
  // Light parameter
  bool use_lighting;
  NormalsType normals_type;
  float g_LightMultiplier;
  float g_LightDirection[3];
  // Shapes material
  float g_MatAmbient[4];
  float g_MatDiffuse[4];
  float g_MatSpecular[4];
  float g_MatShininess;
  float background_color[4];
  float line_color[4];
  int numIsoLevels;
  // Shaders
#ifndef PREVIEW3D_NO_SHADERS
  struct GLSL_Program s_directionalPerPixelProgram;
  struct GLSL_Program s_directionalPerPixelColorProgram;
  ShaderMode shader_mode;
  int shader_id;
#endif
  bool draw_grid;

  bool has_wedge_texture;
  /***********************************/
  
  
  /********* Scene/Camera Options *********/
  float eye[3];
  float up[3];
  float center[3];
  // Scene frustum shift
  double frustum_shift_x;
  // Scene scale
  float g_Zoom;
  // zoom and shift are set once when mesh is loaded to center and normalize
  // shape to fit unit box
  float zoom;
  Eigen::Vector3f g_Translation; 
  double view_angle;
  double dnear;
  double dfar;
  double m_projection_matrix[16];
  double m_modelview_matrix[16];
  GLint m_viewport[4];
  // Shape orientation (stored as a quaternion)
  float g_Rotation[4];
  RowVector3 aabb_min;
  RowVector3 aabb_max;
  RowVector3 aabb_center;
  double diameter;
  double avg_edge_length;
  /***********************************/
  
  
  /********* Window Options *********/
  double width_percentage;
  double height_percentage;
  int width;
  int height;
  int barWidth;
  int barHeight;
  int maxBarHeight;
  /***********************************/


  /********* Other Variables *********/
  igl::XMLSerializer* serializer;
  igl::Timer* timer;
  // number of frames per second
  double fps;
  float down_translation[3];

  enum MouseMode { NOTHING, ROTATION, ZOOM, PAN, TRANSLATE} mouse_mode;
  enum KeyModifier { NO_KEY = TW_KMOD_NONE, SHIFT = TW_KMOD_SHIFT, CTRL =TW_KMOD_CTRL, ALT = TW_KMOD_ALT } key_modifier;

  std::vector<std::vector< std::pair< isoPoint, isoPoint > > > isoSegments;
  std::vector< double > isoLevels;
  // delete the opened file on exit
  bool delete_on_exit;
  GLuint texture_id;

  // disable rotation capability
  bool enable_rotation;
  // Auto rotate
  bool auto_rotate_restart;
  int g_RotateTime;
  float g_RotateStart[4];
  // snap to nearest canonical view quaternion
  double trackball_snap_to_canonical;
  bool use_lighting_at_compile;
  bool is_compiled;
  NormalsType normals_type_at_compile;
  bool show_vertid_at_compile;
  bool show_faceid_at_compile;
  bool down;
  int last_x;
  int last_y;
  int down_x;
  int down_y;
  float down_z;
  float scroll_position;
  float down_rotation[4];
  // Path of the temporary file to delete
  std::string file_to_delete;
  std::string filename;
  /***********************************/
  
  
  /********* Static Variables ***************/
  static float GOLD_AMBIENT[4];
  static float GOLD_DIFFUSE[4];
  static float GOLD_SPECULAR[4];
  static float SILVER_AMBIENT[4];
  static float SILVER_DIFFUSE[4];
  static float SILVER_SPECULAR[4];
  static float DARK_BLUE[4];
  static float BLACK[4];
  static float WHITE[4];
  
  static float CANONICAL_VIEW_QUATERNIONS[][4] ;
  
  static ColorPreset color_presets[NUM_COLOR_PRESETS] ;
  

  static float ZERO[];
  static float DEFAULT_QUATERNION[] ;
  
  static float DEFAULT_LIGHT_DIRECTION[];
  
  /******************************************/
  
  
  ////////////////////////////////////////////////////////////////////////////
  // Functions
  ////////////////////////////////////////////////////////////////////////////

  /********* Initialization *********/
  // Needs time at initialization so that auto-rotation can animate
  Preview3D(int current_time, bool deleteonexit = false);

  /********* De-Initialization *********/
  ~Preview3D();

  /********* Loading-Saving Mesh*********/
  bool load_mesh_from_file(const char* mesh_file_name);
  bool save_mesh_to_file(const char* mesh_file_name);
  void clear_mesh();
  
  /********* Trackball *********/
  void DrawPlaneHandle ();
  void DrawCircle ();
  void DrawSphereIcon();
  
  
  /********* Handle key/mouse events*********/
  bool key_down(unsigned char key, int modifier, int mouse_x, int mouse_y);
  bool key_up(unsigned char key, int modifier, int mouse_x, int mouse_y);
  bool mouse_down(int mouse_x, int mouse_y, int button, int key_pressed);
  bool mouse_up(int mouse_x, int mouse_y, int button, int key_pressed);
  bool mouse_move(int mouse_x, int mouse_y);
  bool mouse_scroll(int mouse_x, int mouse_y, float delta_y);

  /********* Loading-Saving Camera*********/
  bool loadScene();
  bool saveScene();
  bool saveCamera();
  bool loadCamera();
  bool read_camera_from_file(FILE *f);
  bool write_camera_to_file(FILE *f);
  void alignCameraCenter();

  /********* Compiling / Drawing *********/
  void draw(int current_time);
  // push scene matrices
  void push_scene(int current_time);
  // pop scene matrices
  void pop_scene();
  // OpenGL context resize
  void resize(int w, int h);
  
  void drawSelectedFace(int selected_face);
  
  // draw a mesh
  void draw_mesh();
  // Compile mesh into opengl display list
  void compile_mesh();
  
  // Compile the overlay into opengl display list
  void compile_overlay();
  
  // Compile strictly quad mesh into opengl display list
  void compile_triangle_mesh();
  void compile_triangle_mesh_aux(bool color);
  
  // Compile strictly triangle mesh into opengl display list
  void compile_quad_mesh();
  void compile_quad_mesh_aux();
  // Compile strictly triangle mesh into opengl display list
  void compile_polygon_mesh();
  void compile_polygon_mesh_aux();
  
  static void draw_text(double x, double y, double z, std::string str, bool small_b);
  void drawgrid(const RowVector3 &aabb_min,
                const RowVector3 &aabb_max,
                const RowVector3 &aabb_center);
  
  void build_vbo();
  void delete_vbo();
  void draw_vbos(bool color);
  // Computes surface normals
  void recompute_all_normals();
  void recompute_face_normals();
  void recompute_vertex_normals();
  void recompute_corner_normals();

  /********* Setters/Getters *********/
  void set_linewidth(int value);

  void SetAutoRotate(bool value);
  bool GetAutoRotate();

  void set_color_preset(const ColorPresetID id);
  ColorPresetID get_color_preset();
  
#ifndef PREVIEW3D_NO_SHADERS
  void set_shader_mode(const ShaderMode id);
  ShaderMode get_shader_mode();
#endif
  
  void SetTexFilename(const std::string fname);
  std::string GetTexFilename();
  
  void SetShowTexture(const bool value);
  bool GetShowTexture();

  void set_toggle_ortho(bool value);
  bool get_toggle_ortho();

  void set_corner_threshold(double value);
  double get_corner_threshold();

  void set_numIsoLevels(int value);
  int get_numIsoLevels();


  /********* Viewing *********/
  void view_xy_plane();
  void view_xz_plane();
  void view_yz_plane();
  // Snap the quaternion q to the nearest canonical view quaternion
  // Input:
  //   q  quaternion to be snapped (also see Outputs)
  //   threshold  (optional) threshold:
  //     1.0 --> snap any input
  //     0.5 --> snap inputs somewhat close to canonical views
  //     0.0 --> snap no input
  // Output:
  //   q  quaternion possibly set to nearest canonical view
  // Return:
  //   true only if q was snapped to the nearest canonical view
  static bool snap_to_canonical_quaternion(
                                           float q[4],
                                           const double threshold=1.0);

  
  /********* Mesh Computation Helpers *********/
  
  // Computes a color for each face
  static void compute_face_colors(
                                  const VectorX *face_property, 
                                  PointMatrixType * face_color);
  // Computes a color for each vertex
  static void compute_vertex_colors(
                                    const VectorX *vertex_property, 
                                    PointMatrixType *vertex_colors);
  // Compute bounding box and centroid
  static void compute_bounding_box(
                                                const PointMatrixType *vertices, 
                                                RowVector3 & min_point,
                                                RowVector3 & max_point);
  // Compute bounding box and centroid
  static void compute_centroid(
                                                const PointMatrixType *vertices,
                                                RowVector3 & centroid);
  // Determines how much to zoom and shift such that the mesh fills the unit
  // box (centered at the origin)
  static void get_scale_and_shift_to_fit_mesh(
                                              const PointMatrixType *vertices, 
                                              float & zoom,
                                              Eigen::Vector3f& shift);
  // Returns true if normals are inverted
  static bool test_for_inverted_normals(
                                        const PointMatrixType *vertices, 
                                        const PointMatrixType *vertex_normals);
  
  void compute_isolevels();

  void resetTexCoords(bool reset_uv, bool reset_valid);

  Eigen::Vector3f screen_to_world(float x, float y, float z);

  /********* Rendering Helpers *********/
  static std::string convertInt(int number);
  static std::string convertDouble(double number);
  
  /********* Math Helpers (used to convert anttweakbar trackball to opengl matrices)*********/
  static void SetQuaternionFromAxisAngle(const float *axis, float angle, float *quat);
  static void ConvertQuaternionToMatrix(const float *quat, float *mat);
  static void MultiplyQuaternions(const float *q1, const float *q2, float *qout);
  static void CopyArray3(float a[], float b[3])
  {
    b[0] = a[0];
    b[1] = a[1];
    b[2] = a[2];
  };
  static void CopyArray4(float a[], float b[4])
  {
    b[0] = a[0];
    b[1] = a[1];
    b[2] = a[2];
    b[3] = a[3];
  };
  static bool ArrayEquals4(float a[], float b[4])
  {
    return 
    b[0] == a[0] &&
    b[1] == a[1] &&
    b[2] == a[2] &&
    b[3] == a[3];
  };
  
  /********* AntTweakBar callbacks *********/
  static void TW_CALL SetAutoRotateCB(const void *value, void *clientData);
  static void TW_CALL GetAutoRotateCB(void *value, void *clientData);
  static void TW_CALL set_color_presetCB(const void *value, void *clientData);
  static void TW_CALL get_color_presetCB(void *value, void *clientData);
  static void TW_CALL set_shader_modeCB(const void *value, void *clientData);
  static void TW_CALL get_shader_modeCB(void *value, void *clientData);
  static void TW_CALL view_xy_planeCB(void *clientData);
  static void TW_CALL view_xz_planeCB(void *clientData);
  static void TW_CALL view_yz_planeCB(void *clientData);
  static void TW_CALL snap_to_canonical_quaternionCB(void *clientData);
  static void TW_CALL invert_normalsCB(void *clientData);
  static void TW_CALL SetShowTextureCB(const void *value, void *clientData);
  static void TW_CALL GetShowTextureCB(void *value, void *clientData);
  static void TW_CALL set_toggle_orthoCB(const void *value, void *clientData);
  static void TW_CALL get_toggle_orthoCB(void *value, void *clientData);
  static void TW_CALL set_corner_thresholdCB(const void *value, void *clientData);
  static void TW_CALL get_corner_thresholdCB(void *value, void *clientData);
  static void TW_CALL set_numIsoLevelsCB(const void *value, void *clientData);
  static void TW_CALL get_numIsoLevelsCB(void *value, void *clientData);
  static void TW_CALL SaveSceneCB(void *clientData);
  static void TW_CALL LoadSceneCB(void *clientData);
  static void TW_CALL SetLineWidthCB(const void *value, void *clientData);
  static void TW_CALL GetLineWidthCB(void *value, void *clientData);
  static void TW_CALL open_dialog_texture(void *clientData);
  static void TW_CALL open_dialog_mesh(void *clientData);
  static void TW_CALL alignCameraCenterCB(void *clientData);
};

#endif