#pragma once

#ifndef FUNCTION_SEGMENT_VIEWER_H
#define FUNCTION_SEGMENT_VIEWER_H

#include <array>
#include <QLabel>
#include <QComboBox>

#include "tdoubleparam.h"
#include "tdoublekeyframe.h"
#include "toonzqt/lineedit.h"

#undef DVAPI
#undef DVVAR
#ifdef TOONZQT_EXPORTS
#define DVAPI DV_EXPORT_API
#define DVVAR DV_EXPORT_VAR
#else
#define DVAPI DV_IMPORT_API
#define DVVAR DV_IMPORT_VAR
#endif

class FunctionSegmentPage;
class FunctionSheet;
class TXsheetHandle;
class KeyframeSetter;
class FunctionPanel;
class QPushButton;
class QStackedWidget;
namespace DVGui {
class MeasuredDoubleLineEdit;
class ExpressionField;
class FileField;
}  // namespace DVGui

//-----------------------------------------------------------------------------

class FunctionSegmentViewer final : public QFrame, public TParamObserver {
  Q_OBJECT

  TDoubleParam *m_curve;
  int m_segmentIndex;
  int m_r0, m_r1;
  QWidget *m_topbar;

  QLineEdit *m_fromFld;
  QLineEdit *m_toFld;
  QLabel *m_paramNameLabel;

  QComboBox *m_typeCombo;
  DVGui::LineEdit *m_stepFld;
  QStackedWidget *m_parametersPanel;

  std::array<FunctionSegmentPage *, 9> m_pages;
  std::array<int, 9> m_typeId;

  FunctionSheet *m_sheet;
  TXsheetHandle *m_xshHandle;
  FunctionPanel *m_panel;

  // buttons for move segments
  QPushButton *m_prevCurveButton;
  QPushButton *m_nextCurveButton;
  QPushButton *m_prevLinkButton;
  QPushButton *m_nextLinkButton;

public:
  FunctionSegmentViewer(QWidget *parent, FunctionSheet *sheet = 0,
                        FunctionPanel *panel = 0);
  ~FunctionSegmentViewer();

  TDoubleParam *getCurve() const { return m_curve; }
  int getR0() const { return m_r0; }
  int getR1() const { return m_r1; }
  int getSegmentIndex() const { return m_segmentIndex; }

  void refresh();

  // overridden from TDoubleParamObserver
  void onChange(const TParamChange &) override { refresh(); }

  void setXsheetHandle(TXsheetHandle *xshHandle) { m_xshHandle = xshHandle; }

  bool anyWidgetHasFocus();
  // in order to avoid FunctionViewer to get focus while editing the expression
  bool isExpressionPageActive();

private:
  int typeToIndex(int type) const;
  int indexToType(int typeIndex) const { return m_typeId[typeIndex]; }
  bool segmentIsValid() const;

  // for displaying the types of neighbor segments
  QString typeToString(int type) const;

private slots:
  void onSegmentTypeChanged(int type);
  void onCurveChanged();
  void onStepFieldChanged(const QString &);

  void onApplyButtonPressed();
  void onPrevCurveButtonPressed();
  void onNextCurveButtonPressed();
  void onPrevLinkButtonPressed();
  void onNextLinkButtonPressed();

public slots:
  void setSegment(TDoubleParam *curve, int segmentIndex);
  void setSegmentByFrame(TDoubleParam *curve, int frame);
};

//-----------------------------------------------------------------------------

class FunctionSegmentPage : public QWidget {
  Q_OBJECT
  FunctionSegmentViewer *m_viewer;

public:
  FunctionSegmentPage(FunctionSegmentViewer *parent);
  ~FunctionSegmentPage();

  FunctionSegmentViewer *getViewer() const { return m_viewer; }
  TDoubleParam *getCurve() const { return m_viewer->getCurve(); }

  int getR0() const { return m_viewer->getR0(); }
  int getR1() const { return m_viewer->getR1(); }

  virtual void refresh() = 0;
  virtual void apply()   = 0;

  virtual void init(int segmentLength) = 0;

public slots:
  void onFieldChanged() { apply(); }
};

//-----------------------------------------------------------------------------

class SpeedInOutSegmentPage final : public FunctionSegmentPage {
  Q_OBJECT

  DVGui::LineEdit *m_speed0xFld;
  DVGui::MeasuredDoubleLineEdit *m_speed0yFld;
  DVGui::LineEdit *m_speed1xFld;
  DVGui::MeasuredDoubleLineEdit *m_speed1yFld;

  DVGui::MeasuredDoubleLineEdit *m_firstSpeedFld;
  DVGui::MeasuredDoubleLineEdit *m_lastSpeedFld;

public:
  SpeedInOutSegmentPage(FunctionSegmentViewer *parent = 0);
  void refresh() override;
  void apply() override{};

  void getGuiValues(TPointD &speedIn, TPointD &speedOut);
  void init(int segmentLength) override;

public slots:
  void onFirstHandleXChanged();
  void onFirstHandleYChanged();
  void onLastHandleXChanged();
  void onLastHandleYChanged();
  void onFirstSpeedChanged();
  void onLastSpeedChanged();
};

//-----------------------------------------------------------------------------

class EaseInOutSegmentPage final : public FunctionSegmentPage {
  Q_OBJECT
  DVGui::MeasuredDoubleLineEdit *m_ease0Fld, *m_ease1Fld;
  double m_fieldScale;

  bool m_isPercentage;

public:
  EaseInOutSegmentPage(bool percentage, FunctionSegmentViewer *parent = 0);
  void refresh() override;
  void apply() override {}

  void getGuiValues(TPointD &easeIn, TPointD &easeOut);
  void init(int segmentLength) override;

public slots:
  void onEase0Changed();
  void onEase1Changed();
};

//-----------------------------------------------------------------------------

class FunctionExpressionSegmentPage final : public FunctionSegmentPage {
  Q_OBJECT

  DVGui::ExpressionField *m_expressionFld;
  DVGui::LineEdit *m_unitFld;

public:
  FunctionExpressionSegmentPage(FunctionSegmentViewer *parent = 0);
  void refresh() override;
  void apply() override;

  // return false if a circular reference is occurred
  bool getGuiValues(std::string &expressionText, std::string &unitName);

  void init(int segmentLength) override;
};

//-----------------------------------------------------------------------------

class SimilarShapeSegmentPage final : public FunctionSegmentPage {
  Q_OBJECT

  DVGui::ExpressionField *m_expressionFld;
  DVGui::LineEdit *m_offsetFld;

public:
  SimilarShapeSegmentPage(FunctionSegmentViewer *parent = 0);

  void refresh() override;
  void apply() override;

  void init(int segmentLength) override;

  void getGuiValues(std::string &expressionText, double &similarShapeOffset);
};

//-----------------------------------------------------------------------------

class FileSegmentPage final : public FunctionSegmentPage {
  Q_OBJECT

  DVGui::FileField *m_fileFld;
  DVGui::LineEdit *m_fieldIndexFld;
  DVGui::LineEdit *m_measureFld;

public:
  FileSegmentPage(FunctionSegmentViewer *parent = 0);
  void refresh() override;
  void init(int segmentLength) override;
  void apply() override;
  void getGuiValues(TDoubleKeyframe::FileParams &fileParam,
                    std::string &unitName);
};

#endif
