// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#pragma once

#include "mutableprojectdefinition.hpp"
#include "libvideostitch/inputDef.hpp"
#include "libvideostitch-gui/utils/inputlensenum.hpp"
#include "libvideostitch-gui/utils/inputformat.hpp"
#include "libvideostitch-gui/widgets/crop/cropshapeeditor.hpp"
#include "libvideostitch-base/lockingproxy.hpp"

#include <QObject>
#include <QString>
#include <sstream>
#include <QMutex>

class ProjectDefinition;

typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::PanoDefinition, const ProjectDefinition>
    PanoDefinitionLocked;
typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::AudioPipeDefinition, const ProjectDefinition>
    AudioPipeDefinitionLocked;
typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageMergerFactory, const ProjectDefinition>
    ImageMergerFactoryLocked;
typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageFlowFactory, const ProjectDefinition>
    ImageFlowFactoryLocked;
typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageWarperFactory, const ProjectDefinition>
    ImageWarperFactoryLocked;
typedef VideoStitch::Helper::LockingProxy<VideoStitch::Core::StereoRigDefinition, const ProjectDefinition>
    StereoRigDefinitionLocked;

namespace VideoStitch {
namespace Core {
class ImageMergerFactory;
class ImageFlowFactory;
class ImageWarperFactory;
}  // namespace Core
}  // namespace VideoStitch

class SignalCompressionCaps;

/**
 * A class describing the project. Thread-safe.
 * Also hold a few runtime-parameters for the project,
 * so not stateless, compared to the underlying MutableProjectDefinition.
 */
class VS_GUI_EXPORT ProjectDefinition : public QObject {
  Q_OBJECT

 public:
  ProjectDefinition();
  virtual ~ProjectDefinition();

  /**
   * @brief Change the state of the project to @modified and notify using the hasBeenModified(bool) signal.
   */
  void setModified(const bool modified = true);

  /**
   * @brief Validates the panorama of the project.
   * @param errsink Stream which contains the error.
   * @returns True = the panorama is valid / false the panorama is invalid.
   */
  bool validatePanorama(std::stringstream &errsink);

  /**
   * @brief Adds a project from a Ptv::Value. If there is an existing project, it will be deleted.
   * @param value Input value.
   * @returns Returns true on success. false on error, back in a clean state.
   */
  bool load(const VideoStitch::Ptv::Value &value);

  /**
   * @brief Close the current project.
   */
  void close();

  /**
   * @brief isInit indicates if a project is successfully loaded.
   * @returns True if is initialized.
   */
  bool isInit() const;

  /**
   * @brief Serialize a GUI project definition.
   * @returns The serialized PTV.
   */
  VideoStitch::Ptv::Value *serialize() const;

  /**
   * Returns the pano definition for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  PanoDefinitionLocked getPano() const;

  /**
   * @brief Returns a const pano definition pointer for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  const PanoDefinitionLocked getPanoConst() const;

  /**
   * @brief Returns a const audio pipe definition pointer for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  AudioPipeDefinitionLocked getAudioPipe() const;

  /**
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  const AudioPipeDefinitionLocked getAudioPipeConst() const;

  /**
   * @brief Returns the stereo rig definition for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  StereoRigDefinitionLocked getStereoRig() const;

  /**
   * @brief Returns a const stereo rig definition pointer for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  const StereoRigDefinitionLocked getStereoRigConst() const;

  /**
   * @brief Returns the merger factory for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  ImageMergerFactoryLocked getImageMergerFactory() const;

  /**
   * @brief Gets the image blender type.
   * @returns Blender type name, if any. Otherwise the default one.
   */
  QString getBlender() const;

  /**
   * @brief Gets the image flow type.
   * @returns Flow type name, if any. Otherwise the default one.
   */
  QString getFlow() const;

  /**
   * @brief Gets the image warper type.
   * @returns Warper type name, if any. Otherwise the default one.
   */
  QString getWarper() const;

  /**
   * @brief Gets the feather values
   * @returns Returns the feather values if any. Default value, otherwise.
   */
  int getFeather() const;

  /**
   * @brief Gets the sphere scale value
   * @returns Returns the sphere scale value if any. Default value, otherwise.
   */
  double getSphereScale() const;

  /**
   * Lower limit to sphereScale parameter
   */
  double computeMinimumSphereScale() const;

  /**
   * Returns the flow factory for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  ImageFlowFactoryLocked getImageFlowFactory() const;

  /**
   * Returns the flow factory for the project.
   * @returns Non-NULL when isInit() returns true, NULL when isInit() returns false.
   */
  ImageWarperFactoryLocked getImageWarperFactory() const;

  /**
   * @brief Get the number of inputs.
   * @returns The number of inputs.
   */
  readerid_t getNumInputs() const;

  /**
   * @brief Get the list of input names.
   * @note Audio-only input is NOT included.
   * @returns The list of input names.
   */
  QStringList getInputNames() const;

  /**
   * @brief Get input type for video (we ignore the audio only inputs)
   * @returns The video input type.
   */
  VideoStitch::InputFormat::InputFormatEnum getVideoInputType() const;

  /**
   * @brief Checks if the inputs provides audio only
   * @returns True if has audio input
   */
  bool hasAnInputWithAudioOnly() const;

  /**
   * @brief Checks if the projects contains audio
   * @returns True if there is one audio pipe configured
   */
  bool hasAudio() const;

  /**
   * @brief Checks that the inputs are images or procedurals only (and not videos)
   */
  bool hasImagesOrProceduralsOnly() const;

  /**
   * @brief Checks that the inputs are several videos (at least 2 videos, no images and no procedurals)
   */
  bool hasSeveralVideos() const;

  /**
   * @brief Checks that there is at least 2 visual inputs (videos, images or procedurals)
   */
  bool hasSeveralVisualInputs() const;

  /**
   * @brief Get the project lens type for the UI
   * @return The lens type (Rectilinear, FullFrameFisheye, CircularFisheye, Equirectangular)
   */
  InputLensClass::LensType getProjectLensType() const;

  /**
   * @brief Checks if at least one input has a valid crop area
   */
  bool hasCroppedArea() const;

  /**
   * @brief Get the current projection name.
   * @returns The projection name.
   */
  QString getProjection() const;

  /**
   * @brief Get the panorama (uncropped) dimensions.
   */
  void getImageSize(unsigned &width, unsigned &height) const;

  /**
   * @brief Get the panorama cropped dimensions.
   **/
  void getCroppedImageSize(unsigned &width, unsigned &height) const;

  /**
   * @brief Get the horizontal field of view in arc degrees.
   * @return The FOV value.
   */
  double getHFOV() const;

  /**
   * @brief The project is compatible with the lib version.
   * @return True is it's compatible.
   */
  bool hasFileFormatChanged() const;

  /**
   * @brief Updates the project according to the lib version.
   */
  void updateFileFormat();

  /**
   * @brief isDrawingInputNumbers
   * @return The input numbers are shown.
   */
  bool isDrawingInputNumbers() const;

  /**
   * @brief setAudioPipe writes the @audio pipe definition in the project. Thread-safe.
   */
  void setAudioPipe(VideoStitch::Core::AudioPipeDefinition *audioPipeDefinition);

  /**
   * @brief setPano writes the @pano definition in the project. Thread-safe.
   */
  void setPano(VideoStitch::Core::PanoDefinition *pano);

  /**
   * @brief setMergerFactory writes the @mergerFactory definition in the project. Thread-safe.
   */
  void setMergerFactory(VideoStitch::Core::ImageMergerFactory *mergerFactory);

  /**
   * @brief setFlowFactory writes the @flowFactory definition in the project. Thread-safe.
   */
  void setFlowFactory(VideoStitch::Core::ImageFlowFactory *flowFactory);

  /**
   * @brief setWarperFactory writes the @warperFactory definition in the project. Thread-safe.
   */
  void setWarperFactory(VideoStitch::Core::ImageWarperFactory *warperFactory);

  /**
   * @brief setStereoRigDefinition writes the @rigDefinition definition in the project. Thread-safe.
   */
  void setStereoRigDefinition(VideoStitch::Core::StereoRigDefinition *rigDefinition);

  /**
   * @brief Sets the panorama width and height. Updates project data if needed.
   * @param width New panorama width.
   * @param height New panorama height.
   */
  virtual void updateSize(int width, int height);

  /**
   * @brief Sets the Horizontal field of view.
   * @param fov New horizontal field of view.
   */
  void setHFov(double fov);

  /**
   * @brief Sets the sphere scale value.
   * @param sphereScale New sphere scale.
   */
  void setSphereScale(double sphereScale);

  /**
   * @brief Draws the input numbers on the input image
   * @param draw True
   */
  void setDrawInputNumbers(bool draw);

  /**
   * @brief Sets the nex projection of the panorama.
   * @param projection New projection.
   */
  void setProjection(QString projection);

  /**
   * @brief markAsSaved resets the isModified flag of the class to indicate there is no
   * discrepancy between the current project and what you have saved last.
   */
  void markAsSaved();

  /**
   * @brief hasLocalModifications indicates if the current project has modifications since the last
   * markAsSaved() call.
   */
  bool hasLocalModifications() const;

  /**
   * @brief The orientation grid is displayed.
   * @return True if the orientation grid must be displayed
   */
  bool getDisplayOrientationGrid() const;

  /**
   * @brief The project has a rig configuration (Stereo)
   * @return True if has a rig configuration
   */
  bool hasRigConfiguration() const;

  /**
   * @brief Gets the audio delay from the first active audio input
   * @return The delay value
   */
  int getAudioDelay() const;

  /**
   * @brief Sets the crop values for an input.
   * @param input The input number
   * @param crop Crop values
   * @param lensType Project lens type
   * @param applyToAll Apply the same crop to all inputs.
   */
  void setInputCrop(const unsigned int input, const Crop &crop, const InputLensClass::LensType lensType,
                    const bool applyToAll);

  /**
   * @brief Sets a rig configuration to the project.
   * @param orientation The orientation
   * @param geometry The rig geometry
   * @param diameter The rig diamater
   * @param ipd The rig IPD
   * @param leftInputs List of left inputs
   * @param rightInputs List of right inputs
   */
  void setRigConfiguration(const VideoStitch::Core::StereoRigDefinition::Orientation orientation,
                           const VideoStitch::Core::StereoRigDefinition::Geometry geometry, const double diameter,
                           const double ipd, const QVector<int> leftInputs, const QVector<int> rightInputs);

  /**
   * @brief Sets the image blender type.
   * @param merger Merger type
   * @param feather The feather value
   */
  void changeBlendingParameters(QString merger, int feather);

  /**
   * @brief Sets the advanced blender type.
   * @param flow Image flow type
   * @param warper Image warper type
   */
  void changeAdvancedBlendingParameters(const QString &flow, const QString &warper);

  /**
   * @brief Get a list of audio input names.
   * @return List of audio input names.
   */
  QStringList getAudioInputNames() const;

  /**
   * @brief Get a list of video input names.
   * @return List of video input names.
   */
  QStringList getVideoInputNames() const;

 public slots:
  void changeProjection(const QString &projName, const double HFOV);
  void updateMasks();
  void setDisplayOrientationGrid(bool display, bool reqRestitch = true);
  void toggleInputNumberDrawing();
  void setInterPupillaryDistance(double ipd);
  void setAudioDelay(int delay_ms);

 signals:
  void reqReset(SignalCompressionCaps *);
  void reqResetRig(SignalCompressionCaps *);
  void reqUpdateMask(int index, unsigned char *maskData, int width, int height);
  void reqUpdateMask(int index, QImage *mask);
  void maskUpdateDone();
  void reqDisplayWarning(QString);
  void hasBeenModified(bool modified);
  void imagesOrProceduralsOnlyHasChanged(bool hasImagesOrProceduralsOnly);
  void severalVideosOnlyHasChanged(bool hasSeveralVideos);
  void severalVisualInputsHasChanged(bool hasSeveralVisualInputs);
  void reqToggleInputNumbers(bool draw);
  void reqDisplayOrientationGrid(bool display);
  void reqToggleControlPoints(bool display);
  void reqSetAudioDelay(int value);

 protected:
  /**
   * @brief mutex A mutex to protect access to this object.
   * The mutex is recursive in case the user accesses different objects at the same time.
   */
  mutable QMutex mutex;
  SignalCompressionCaps *signalCompressor;

  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::PanoDefinition,
                                                 const ProjectDefinition>;  // PanoDefinitionLocked;
  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::AudioPipeDefinition,
                                                 const ProjectDefinition>;  // AudioDefinitionLocked;
  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::StereoRigDefinition,
                                                 const ProjectDefinition>;  // StereoRigDefinitionLocked;
  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageMergerFactory,
                                                 const ProjectDefinition>;  // ImageMergerFactoryLocked;
  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageFlowFactory,
                                                 const ProjectDefinition>;  // ImageFlowFactoryLocked;
  friend class VideoStitch::Helper::LockingProxy<VideoStitch::Core::ImageWarperFactory,
                                                 const ProjectDefinition>;  // ImageWarperFactoryLocked;

  /**
   * @brief lock Grab the local instance mutex.
   */
  void lock() const;

  /**
   * @brief lock Release the local instance mutex.
   */
  void unlock() const;

  virtual MutableProjectDefinition *getDelegate() const = 0;

  virtual void createDelegate(const VideoStitch::Ptv::Value &value) = 0;

  virtual void destroyDelegate() = 0;

  void setDefaultPtvValues(const VideoStitch::Ptv::Value &value);

 private:
  QString getInputName(int index) const;
  void copyValuesIntoVector(std::vector<VideoStitch::Ptv::Value *> &vector, const QVector<int> inputList);

 private:
  /**
   * @brief Tells if the project definition has been modified since it was saved (VideoStitcher:::savePTV()).
   */
  bool isModified;
  bool drawInputNumbers;
  bool displayOrientationGrid;
};