#pragma once

#ifndef FXSCHEMATIC_H
#define FXSCHEMATIC_H

#include "toonzqt/addfxcontextmenu.h"
#include "schematicviewer.h"
#include "tgeometry.h"
#include <QMap>
#include <tfx.h>
#include <set>

// forward declaration
class FxSchematicNode;
class TFxHandle;
class FxSelection;
class FxSchematicNode;
class TXsheet;
class TSceneHandle;
class QMenu;
class SchematicLink;
class FxGroupNode;
class FxSchematicGroupEditor;
class FxSchematicMacroEditor;
class TMacroFx;

enum SearchDirection { Both = 0, Input, Output };
//==================================================================
//
// FXSchematic
//
//==================================================================

class FxSchematicScene final : public SchematicScene {
  Q_OBJECT

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

  class SupportLinks {
    QList<SchematicLink *> m_bridges;
    QList<SchematicLink *> m_inputs;
    QList<SchematicLink *> m_outputs;

  public:
    SupportLinks() {}

    void addBridgeLink(SchematicLink *link);
    void addInputLink(SchematicLink *link);
    void addOutputLink(SchematicLink *link);

    QList<SchematicLink *> getBridgeLinks() { return m_bridges; }
    QList<SchematicLink *> getInputLinks() { return m_inputs; }
    QList<SchematicLink *> getOutputLinks() { return m_outputs; }

    void hideBridgeLinks();
    void hideInputLinks();
    void hideOutputLinks();

    void showBridgeLinks();
    void showInputLinks();
    void showOutputLinks();

    void removeBridgeLinks(bool deleteLink = false);
    void removeInputLinks(bool deleteLink = false);
    void removeOutputLinks(bool deleteLink = false);

    bool isABridgeLink(SchematicLink *link) { return m_bridges.contains(link); }
    bool isAnInputLink(SchematicLink *link) { return m_inputs.contains(link); }
    bool isAnOutputLink(SchematicLink *link) {
      return m_outputs.contains(link);
    }

    void clearAll();
    int size();
  };

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

  TApplication *m_app;
  TXsheetHandle *m_xshHandle;
  TFxHandle *m_fxHandle;
  TFrameHandle *m_frameHandle;
  TColumnHandle *m_columnHandle;

  QPointF m_firstPoint;
  QMap<TFx *, FxSchematicNode *> m_table;
  QMap<int, FxGroupNode *> m_groupedTable;
  QMap<int, FxSchematicGroupEditor *> m_groupEditorTable;
  QMap<TMacroFx *, FxSchematicMacroEditor *> m_macroEditorTable;
  FxSelection *m_selection;
  AddFxContextMenu m_addFxContextMenu;
  SupportLinks m_disconnectionLinks, m_connectionLinks;
  bool m_isConnected;
  bool m_linkUnlinkSimulation;
  bool m_altPressed;
  QPointF m_lastPos;
  QList<QPair<TFxP, TPointD>> m_selectionOldPos;
  QList<TFx *> m_placedFxs;
  FxSchematicNode *m_currentFxNode;
  int m_gridDimension;

  bool m_isNormalIconView;

  QMap<TFx *, QList<FxSchematicNode *>> m_nodesToPlace;

  SchematicViewer *m_viewer;

public:
  FxSchematicScene(QWidget *parent);
  ~FxSchematicScene();

  void updateScene() override;
  QGraphicsItem *getCurrentNode() override;
  void reorderScene() override;
  TXsheet *getXsheet();

  void setApplication(TApplication *app);

  TApplication *getApplication() const { return m_app; }
  TXsheetHandle *getXsheetHandle() const { return m_xshHandle; }
  TFxHandle *getFxHandle() const { return m_fxHandle; }
  TFrameHandle *getFrameHandle() const { return m_frameHandle; }
  TColumnHandle *getColumnHandle() const { return m_columnHandle; }
  TFx *getCurrentFx();

  QMenu *getInsertFxMenu() { return m_addFxContextMenu.getInsertMenu(); }
  QMenu *getAddFxMenu() { return m_addFxContextMenu.getAddMenu(); }
  QMenu *getReplaceFxMenu() { return m_addFxContextMenu.getReplaceMenu(); }

  QAction *getAgainAction(int commands) {
    return m_addFxContextMenu.getAgainCommand(commands);
  }

  FxSelection *getFxSelection() const { return m_selection; }
  //! Disconnects or connects selected item from the rest of the graph.
  //! Selection must be a connected subgraph. If \b disconnect is true, the
  //! selection is disconnected; connected otherwise.
  void simulateDisconnectSelection(bool disconnect);

  //! Updates all Group Editors containing fx.
  //! Each fx is only in one group, but each group can contains other groups.
  //! All nested Groups must be updated.
  void updateNestedGroupEditors(FxSchematicNode *node, const QPointF &newPos);
  void closeInnerMacroEditor(int groupId);
  void resizeNodes(bool maximizedNode);

  void initCursorScenePos() {
    m_addFxContextMenu.setCurrentCursorScenePos(QPointF(0, 0));
  }
  void selectNodes(QList<TFxP> &fxs);
  void toggleNormalIconView() { m_isNormalIconView = !m_isNormalIconView; }
  bool isNormalIconView() { return m_isNormalIconView; }

  SchematicViewer *getSchematicViewer() { return m_viewer; }

protected:
  void contextMenuEvent(QGraphicsSceneContextMenuEvent *cme) override;
  void mousePressEvent(QGraphicsSceneMouseEvent *me) override;
  void mouseMoveEvent(QGraphicsSceneMouseEvent *me) override;
  void mouseReleaseEvent(QGraphicsSceneMouseEvent *me) override;
  bool event(QEvent *e) override;

private:
  FxSchematicNode *addFxSchematicNode(TFx *fx);
  FxSchematicNode *addGroupedFxSchematicNode(int groupId,
                                             const QList<TFxP> &groupedFxs);
  FxSchematicGroupEditor *addEditedGroupedFxSchematicNode(
      int groupId, const QList<SchematicNode *> &groupedFxs);
  FxSchematicMacroEditor *addEditedMacroFxSchematicNode(
      TMacroFx *macro, const QList<SchematicNode *> &groupedFxs);
  FxSchematicNode *createFxSchematicNode(TFx *fx);
  void placeNode(FxSchematicNode *node);
  void updateLink();
  void updateDuplcatedNodesLink();
  void updateEditedGroups(const QMap<int, QList<SchematicNode *>> &editedGroup);
  void updateEditedMacros(
      const QMap<TMacroFx *, QList<SchematicNode *>> &editedMacro);
  FxSchematicNode *getFxNodeFromPosition(const QPointF &pos);
  void placeNodeAndParents(TFx *fx, double x, double &maxX, double &maxY);

  QPointF nearestPoint(const QPointF &point);

  void highlightLinks(FxSchematicNode *node, bool value,
                      SearchDirection direction = Both);

  void updatePosition(FxSchematicNode *node, const TPointD &pos);
  void simulateInsertSelection(SchematicLink *link, bool connect);
  void updatePositionOnResize(TFx *fx, bool maximizedNode);
  void removeRetroLinks(TFx *fx, double &maxX);

  bool isAnEmptyZone_withParentFx(const QRectF &rect, const TFx *parent);

  // snap to neighbor nodes on dragging
  void updateSnapTarget(QGraphicsItem *item) override;
signals:
  void showPreview(TFxP);
  void cacheFx(TFxP);
  void doCollapse(const QList<TFxP> &);
  void doExplodeChild(const QList<TFxP> &);
  void editObject();

protected slots:
  void onSelectionSwitched(TSelection *oldSel, TSelection *newSel) override;
  void onSelectionChanged();

  // void onOutputFxAdded();
  void onDisconnectFromXSheet();
  void onConnectToXSheet();
  void onDeleteFx();
  void onDuplicateFx();
  void onUnlinkFx();
  void onMacroFx();
  void onExplodeMacroFx();
  void onOpenMacroFx();
  void onSavePresetFx();
  void onActivateOutput();
  void onPreview();
  void onCacheFx();
  void onUncacheFx();
  void onCollapse(const QList<TFxP> &);

  void onXsheetChanged();
  void onSceneChanged();
  void onSwitchCurrentFx(TFx *);
  void onCurrentFxSwitched();
  void onCurrentColumnChanged(int);

  void onFxNodeDoubleClicked();

  void onInsertPaste();
  void onAddPaste();
  void onReplacePaste();
  void onAltModifierChanged(bool);
  void onEditGroup();

  void onIconifyNodesToggled(bool iconified);

  void onNodeChangedSize();

private:
  void setEnableCache(bool toggle);

  // not implemented
  FxSchematicScene(const FxSchematicScene &);
  const FxSchematicScene &operator=(const FxSchematicScene &);
};

#endif  // FXSCHEMATIC_H
