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

#include <Config.hpp>
#include <GL.hpp>

#include <ClipRegion.hpp>
#include <CutSurf.hpp>
#include <EdgeData.hpp>
#include <ImageData.hpp>
#include <MGXViewer/qglviewer.h>
#include <Shader.hpp>
#include <VertexData.hpp>
#include <Geometry.hpp>

#include "LassoSelect.hpp"

class QDomElement;
class QShowEvent;
class MorphoGraphX;
class MGXCamera;

class MGXKeyFrameInterpolator : public qglviewer::KeyFrameInterpolator 
{
public:
  MGXKeyFrameInterpolator(qglviewer::Frame* fr = NULL);
  virtual void interpolateAtTime(float time);

  std::vector<float> zooms;
};

class MorphoViewer : public QGLViewer
{
  Q_OBJECT
public:
  enum FrameIdentity {FI_BACKGROUND, FI_FULL_IMG1, FI_FULL_IMG2, FI_CUR_PEEL,
    FI_VOLUME1, FI_VOLUME2, FI_OCCLUSION, NB_FRAMES };

  bool quitting;
  bool DrawCellMap;
  bool DrawClipBox;
  mgx::Point3f oldVPos;
  QPoint oldPos;
  QRect selectRect;
  LassoSelect selectLasso;
  bool voxelEditCursor;
  float pixelRadius;
  QPoint mousePos;
  mgx::Shader raycastingShader1, raycastingShader2;
  mgx::Shader colormap2Shader, index2Shader;
  mgx::Shader finalCombineShader, combineShader, renderDepthShader;
  mgx::Shader textureSurfShader, indexSurfShader, colorSurfShader;
  mgx::Shader volumeSurfShader1, volumeSurfShader2;
  mgx::Shader postProcessShader;
  mgx::Shader occlusionShader;

  GLuint baseFboId, fboId, fboCopyId;
  GLuint depthTexId[NB_FRAMES];
  GLuint colorTexId[NB_FRAMES];
  GLuint depthBuffer;
  std::vector<GLfloat> depthTexture;
  int drawWidth, drawHeight;
  int prevWidth, prevHeight;

  // GUI Interaction
  bool shiftPressed;
  bool altPressed;
  bool controlPressed;
  bool leftButton;
  bool rightButton;

  enum GuiAction {STACK_VOXEL_EDIT, STACK_PICK_LABEL, STACK_DEL_LABEL, STACK_DEL_PICKED, STACK_FILL_LABEL, STACK_PICK_FILL_LABEL,
     MESH_SEL_POINTS, MESH_SEL_LASSO, MESH_ADD_SEED, MESH_CURR_SEED, MESH_DRAW_SIGNAL, MESH_PICK_LABEL, MESH_SEL_LABEL, MESH_SEL_CONN, 
     MESH_SEL_TRIS, MESH_GRAB_SEED, MESH_FILL_LABEL, MESH_PICK_FILL_LABEL, MESH_PICK_FILL_PARENT };
  GuiAction guiAction;

  bool guiActionOn;
  bool meshPickFill = true, stackPickFill = true;

  QList<float> pickedLabels;

  // Clipping planes
  mgx::ClipRegion clip1, clip2, clip3;
  mgx::Clip* c1, *c2, *c3;

  // Confocal stack objects
  QPointer<mgx::ImgData> stack1, stack2;

  // Cutting surface
  mgx::CutSurf* cutSurf;

  float FlySpeed;
  int selectedLabel;
  float sampling;
  bool fast_draw;
  int texWidth, texHeight;
  int show_slice;
  int slice_type;

  int MaxNbPeels;
  float GlobalBrightness;
  float GlobalContrast;
  float GlobalShininess;
  float GlobalSpecular;
  float UnsharpStrength;
  float Spinning;
  float ZoomFactor() const;
  void setZoomFactor(float f);
  mgx::Matrix4d CameraFrame;
  MGXCamera* _camera;

  MorphoGraphX* mainWindow();

  void setSceneBoundingBox(const mgx::Point3f& bbox);

  float getSceneRadius() const { return SceneRadius; }
  void processRunning(bool state) { _processRunning = state; }
  bool processRunning() { return _processRunning; }

private:
  float SceneRadius;

  QString fullSnapshotFileName();
  void initObject(QWidget* parent);
  bool initialized;
  qglviewer::Camera* lastInitCamera;
  bool _processRunning;

  void initCamera();

public:
  MorphoViewer(QWidget* parent);
  MorphoViewer(QGLContext* context, QWidget* parent);
  MorphoViewer(const QGLFormat& format, QWidget* parent);
  virtual ~MorphoViewer();

  static void initFormat();

  virtual QDomElement domElement(const QString& name, QDomDocument& document) const;

  void drawSelectRect();
  void drawSelectLasso();
  void drawVoxelCursor();
  void checkVoxelCursor(bool altPressed);
  void drawColorBar();
  void drawScaleBar();
  void setLighting(mgx::ImgData *stack);
  void readParms(mgx::Parms& parms, QString section);
  void writeParms(QTextStream& pout, QString section);
  void updateAll();
  mgx::Point3f pointUnderPixel(const QPoint& pos, bool& found);
  void startScreenCoordinatesSystem(bool upward = false) const;

protected:
  void preDraw();
  void draw();
  void draw(QPaintDevice* device);
  void fastDraw();
  virtual void postDraw();
  virtual void init();
  void showEvent(QShowEvent* event);
  void resizeGL(int width, int height);
  void updateFBOTex(int width, int height, bool fast_draw = false);
  void drawColorTexture(int i, bool draw_depth = false);
  void alternatePeels(int& curPeelId, int& prevPeelId, int fullImgId);
  void combinePeels(int& fullImgId, int curPeelId, int volume1, int volume2);
  void setupCopyFB(GLuint depth, GLint color);
  void setupFramebuffer(GLuint depth, GLuint color, GLbitfield clear = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  void resetupFramebuffer(GLuint depth, GLuint color, GLbitfield clear = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  void resetFramebuffer();
  void leaveEvent(QEvent* e);
  void enterEvent(QEvent* e);
  void keyReleaseEvent(QKeyEvent* e);
  void keyPressEvent(QKeyEvent* e);
  void mouseMoveEvent(QMouseEvent* e);
  void mousePressEvent(QMouseEvent* e);
  void mouseReleaseEvent(QMouseEvent* e);
  void mouseDoubleClickEvent(QMouseEvent* e);

private:
  mgx::ImgData* findSelectStack();
  mgx::ImgData* findSelectSurf();
  mgx::ImgData* findSelectMesh();
  void findSelectTriangle(mgx::ImgData* stk, uint x, uint y, std::vector<uint>& vlist, int& label,
                          bool useParentLabel = true);

  void getGUIFlags(QMouseEvent* e);
  void getGUIFlags(QKeyEvent* e);
  void drawCellMap();

  Qt::MouseButtons lastButtons;

  void clipEnable();
  void clipDisable();

  // Gui actions
  bool callGuiAction(GuiAction guiAction, QMouseEvent *e);

  // Stack gui actions
  bool stackPickLabel(QMouseEvent *e);
  bool stackFillLabel(QMouseEvent *e);
  bool stackPickFillLabel(QMouseEvent *e);
  bool stackVoxelEdit(QMouseEvent *e);
  bool stackDeleteLabel(QMouseEvent *e);

  // Mesh gui actions
  bool meshMovePoints(QMouseEvent *e);
  bool meshSelectPoints(QMouseEvent *e);
  bool meshSelectLasso(QMouseEvent *e);
  bool meshPickLabel(QMouseEvent *e);
  bool meshAddSeed(QMouseEvent *e);
  bool meshCurrentSeed(QMouseEvent *e);
  bool meshDrawSignal(QMouseEvent *e);
  bool meshFillLabel(QMouseEvent *e);
  bool meshPickFillLabel(QMouseEvent *e);
  bool meshPickLabelFillParent(QMouseEvent *e);
  bool meshSelectLabel(QMouseEvent *e);
  bool meshSelectConnected(QMouseEvent *e);
  bool meshSelectTriangle(QMouseEvent *e);
  bool meshGrabSeed(QMouseEvent *e);

  int findLabel(const mgx::Store* store, mgx::Point3f start, mgx::Point3f dir) const;

  QPaintDevice* current_device;

  void ClipEnableSlot(mgx::Clip* c, bool _val);
  void ClipGridSlot(mgx::Clip* c, bool _val);
  void ClipWidthSlot(mgx::Clip* c, int _val);

public slots:
  void setLabel(int label);
  void selectMeshLabel(int label, int repeat, bool replace = true);
  void ReloadShaders();
  void UpdateSlot() {
    updateAll();
  }
  void loadFile(const QString& pth, bool stack2, bool load_work);
  void UpdateLabels();

  //  Takes care of GUI clips
  void Clip1EnableSlot(bool _val) { ClipEnableSlot(c1, _val); }
  void Clip1GridSlot(bool _val) { ClipGridSlot(c1, _val); }
  void Clip1WidthSlot(int _val) { ClipWidthSlot(c1, _val); }
  void Clip2EnableSlot(bool _val) { ClipEnableSlot(c2, _val); }
  void Clip2GridSlot(bool _val) { ClipGridSlot(c2, _val); }
  void Clip2WidthSlot(int _val) { ClipWidthSlot(c2, _val); }
  void Clip3EnableSlot(bool _val) { ClipEnableSlot(c3, _val); }
  void Clip3GridSlot(bool _val) { ClipGridSlot(c3, _val); }
  void Clip3WidthSlot(int _val) { ClipWidthSlot(c3, _val); }

  void FlySpeedSlot(int val) 
  {
    FlySpeed = float(val) / 10000.0;
  }
  void DrawCellMapSlot(bool val)
  {
    DrawCellMap = val;
    updateAll();
  }
  void SlicesSlot(int val)
  {
    mgx::ImgData::Slices = val;
    updateAll();
  }
  void ScreenSamplingSlot(int val);
  void LabelColorSlot();
  void ResetViewSlot();

  void recordMovie(bool on);

  void saveScreenshot(bool automatic = false, bool overwrite = false)
  {
    return QGLViewer::saveSnapshot(automatic, overwrite);
  }
  void saveScreenshot(const QString& fileName, bool overwrite = false)
  {
    return QGLViewer::saveSnapshot(fileName, overwrite);
  }
  void setScreenshotFileName(const QString& name) 
  {
    return QGLViewer::setSnapshotFileName(name);
  }
  void setScreenshotFormat(const QString& format) 
  {
    return QGLViewer::setSnapshotFormat(format);
  }
  void setScreenshotCounter(int counter) 
  {
    return QGLViewer::setSnapshotCounter(counter);
  }
  void setScreenshotQuality(int quality) 
  {
    return QGLViewer::setSnapshotQuality(quality);
  }
  bool openScreenshotFormatDialog() 
  {
    return QGLViewer::openSnapshotFormatDialog();
  }

  virtual void initFromDOMElement(const QDomElement& element);

  void ViewerUpdateSlot() { updateAll(); }

  virtual bool saveImageSnapshot(const QString& fileName, QSize finalSize, double oversampling = 1.0,
                                 bool expand = false, bool has_gui = false);

  bool saveImageSnapshot(const QString& fileName, QSize finalSize, double oversampling, bool expand, bool has_gui,
                         QString* error);

  void showEntireScene() { camera()->showEntireScene(); }

signals:
  void deleteSelection();
  void modified();
  void stepDrawFinished(bool);
  void setLabelColor(QIcon&);
  void recordingMovie(bool);
  void changeSceneRadius(float);
  void selectLabelChanged(int);
};

#endif
