/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2022 Olive Team

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#ifndef TIMELINEWIDGET_H
#define TIMELINEWIDGET_H

#include <QScrollBar>
#include <QRubberBand>
#include <QWidget>

#include "core.h"
#include "node/block/transition/transition.h"
#include "node/output/viewer/viewer.h"
#include "node/project/serializer/serializer.h"
#include "timeline/timelinecommon.h"
#include "timelineandtrackview.h"
#include "widget/slider/rationalslider.h"
#include "widget/timebased/timebasedwidget.h"
#include "widget/timelinewidget/timelinewidgetselections.h"
#include "widget/timelinewidget/tool/import.h"
#include "widget/timelinewidget/tool/tool.h"

namespace olive {

/**
 * @brief Full widget for working with TimelineOutput nodes
 *
 * Encapsulates TimelineViews, TimeRulers, and scrollbars for a complete widget to manipulate Timelines
 */
class TimelineWidget : public TimeBasedWidget
{
  Q_OBJECT
public:
  TimelineWidget(QWidget* parent = nullptr);

  virtual ~TimelineWidget() override;

  void Clear();

  void SelectAll();

  void DeselectAll();

  void RippleToIn();

  void RippleToOut();

  void EditToIn();

  void EditToOut();

  void SplitAtPlayhead();

  void DeleteSelected(bool ripple = false);

  void IncreaseTrackHeight();

  void DecreaseTrackHeight();

  void InsertFootageAtPlayhead(const QVector<ViewerOutput *> &footage);

  void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);

  void ToggleLinksOnSelected();

  void AddDefaultTransitionsToSelected();

  virtual bool CopySelected(bool cut) override;

  virtual bool Paste() override;

  void PasteInsert();

  void DeleteInToOut(bool ripple);

  void ToggleSelectedEnabled();

  void SetColorLabel(int index);

  void NudgeLeft();

  void NudgeRight();

  void MoveInToPlayhead();

  void MoveOutToPlayhead();

  void ShowSpeedDurationDialogForSelectedClips();

  void RecordingCallback(const QString &filename, const TimeRange &time, const Track::Reference &track);

  void EnableRecordingOverlay(const TimelineCoordinate &coord);

  void DisableRecordingOverlay();

  void AddTentativeSubtitleTrack();

  void NestSelectedClips();

  /**
   * @brief Timelines should always be connected to sequences
   */
  Sequence* sequence() const
  {
    return static_cast<Sequence*>(GetConnectedNode());
  }

  const QVector<Block*>& GetSelectedBlocks() const
  {
    return selected_blocks_;
  }

  QByteArray SaveSplitterState() const;

  void RestoreSplitterState(const QByteArray& state);

  static void ReplaceBlocksWithGaps(const QVector<Block *> &blocks, bool remove_from_graph, MultiUndoCommand *command, bool handle_transitions = true);

  /**
   * @brief Retrieve the QGraphicsItem at a particular scene position
   *
   * Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
   * scene position
   */
  Block* GetItemAtScenePos(const TimelineCoordinate &coord);

  void AddSelection(const TimeRange& time, const Track::Reference& track);
  void AddSelection(Block* item);

  void RemoveSelection(const TimeRange& time, const Track::Reference& track);
  void RemoveSelection(Block* item);

  const TimelineWidgetSelections& GetSelections() const
  {
    return selections_;
  }

  void SetSelections(const TimelineWidgetSelections &s, bool process_block_changes);

  Track* GetTrackFromReference(const Track::Reference& ref) const;

  void SetViewBeamCursor(const TimelineCoordinate& coord);
  void SetViewTransitionOverlay(ClipBlock *out, ClipBlock *in);

  const QVector<TimelineViewGhostItem*>& GetGhostItems() const
  {
    return ghost_items_;
  }

  void InsertGapsAt(const rational& time, const rational& length, MultiUndoCommand *command);

  void StartRubberBandSelect(const QPoint& global_cursor_start);
  void MoveRubberBandSelect(bool enable_selecting, bool select_links);
  void EndRubberBandSelect();

  int GetTrackY(const Track::Reference& ref);
  int GetTrackHeight(const Track::Reference& ref);

  void AddGhost(TimelineViewGhostItem* ghost);

  void ClearGhosts();

  bool HasGhosts() const
  {
    return !ghost_items_.isEmpty();
  }

  bool IsBlockSelected(Block* b) const
  {
    return selected_blocks_.contains(b);
  }

  void SetBlockLinksSelected(ClipBlock *block, bool selected);

  void QueueScroll(int value);

  TimelineView* GetFirstTimelineView();

  rational GetTimebaseForTrackType(Track::Type type);

  const QRect &GetRubberBandGeometry() const;

  /**
   * @brief Track blocks that have newly been selected (this is preferred over emitting BlocksSelected directly)
   *
   * TimelineWidget keeps track of which blocks are selected internally. Calling this function will
   * add to that list and emit a signal to other widgets that said blocks have been selected.
   *
   * @param selected_blocks
   *
   * The list of blocks to add to the internal selection list and signal.
   *
   * @param filter
   *
   * TRUE to automatically filter blocks that are already selected from the list. In most cases,
   * this is preferable and should only be set to FALSE if the list is guaranteed not to contain
   * already selected blocks (and therefore filtering can be skipped to save time).
   */
  void SignalSelectedBlocks(QVector<Block *> selected_blocks, bool filter = true);

  /**
   * @brief Track blocks that have been newly deselected
   */
  void SignalDeselectedBlocks(const QVector<Block *> &deselected_blocks);

  /**
   * @brief Convenience function to deselect all blocks and signal them
   */
  void SignalDeselectedAllBlocks();

  void Refresh()
  {
    UpdateViewports();
  }

  MultiUndoCommand *TakeSubtitleSectionCommand()
  {
    // Copy pointer
    MultiUndoCommand *c = subtitle_show_command_;

    // Set to null
    subtitle_show_command_ = nullptr;
    subtitle_tentative_track_ = nullptr;

    // Return command
    return c;
  }

  class SetSelectionsCommand : public UndoCommand {
  public:
    SetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, bool process_block_changes = true) :
      timeline_(timeline),
      old_(old),
      now_(now),
      process_block_changes_(process_block_changes)
    {
    }

    virtual Project* GetRelevantProject() const override {return nullptr;}

  protected:
    virtual void redo() override
    {
      timeline_->SetSelections(now_, process_block_changes_);
    }

    virtual void undo() override
    {
      timeline_->SetSelections(old_, process_block_changes_);
    }

  private:
    TimelineWidget* timeline_;
    TimelineWidgetSelections old_;
    TimelineWidgetSelections now_;
    bool process_block_changes_;

  };

public slots:
  void ClearTentativeSubtitleTrack();

  void RenameSelectedBlocks();

signals:
  void BlockSelectionChanged(const QVector<Block*>& selected_blocks);

  void RequestCaptureStart(const TimeRange &time, const Track::Reference &track);

  void RevealViewerInFootageViewer(ViewerOutput *r, const TimeRange &range);
  void RevealViewerInProject(ViewerOutput *r);

protected:
  virtual void resizeEvent(QResizeEvent *event) override;

  virtual void TimeChangedEvent(const rational &) override;
  virtual void TimebaseChangedEvent(const rational &) override;
  virtual void ScaleChangedEvent(const double &) override;

  virtual void ConnectNodeEvent(ViewerOutput* n) override;
  virtual void DisconnectNodeEvent(ViewerOutput* n) override;

  virtual const QVector<Block*> *GetSnapBlocks() const override { return &added_blocks_; }

protected slots:
  virtual void SendCatchUpScrollEvent() override;

private:
  QVector<Timeline::EditToInfo> GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);

  void RippleTo(Timeline::MovementMode mode);

  void EditTo(Timeline::MovementMode mode);

  void UpdateViewports(const Track::Type& type = Track::kNone);

  bool PasteInternal(bool insert);

  TimelineAndTrackView *AddTimelineAndTrackView(Qt::Alignment alignment);

  QHash<Node*, Node*> GenerateExistingPasteMap(const ProjectSerializer::Result &r);

  QRubberBand rubberband_;
  QVector<QPointF> rubberband_scene_pos_;
  TimelineWidgetSelections rubberband_old_selections_;
  QVector<Block*> rubberband_now_selected_;
  bool rubberband_enable_selecting_;
  bool rubberband_select_links_;

  TimelineWidgetSelections selections_;

  TimelineTool* GetActiveTool();

  QVector<TimelineTool*> tools_;

  ImportTool* import_tool_;

  TimelineTool* active_tool_;

  QVector<TimelineViewGhostItem*> ghost_items_;

  QVector<TimelineAndTrackView*> views_;

  RationalSlider* timecode_label_;

  QVector<Block*> selected_blocks_;

  QVector<Block*> added_blocks_;

  int deferred_scroll_value_;

  bool use_audio_time_units_;

  QSplitter* view_splitter_;

  MultiUndoCommand *subtitle_show_command_;
  Track *subtitle_tentative_track_;

  QTimer *signal_block_change_timer_;

  class SetSplitterSizesCommand : public UndoCommand
  {
  public:
    SetSplitterSizesCommand(QSplitter *splitter, const QList<int> &sizes) :
      splitter_(splitter),
      new_sizes_(sizes)
    {}

    virtual Project* GetRelevantProject() const override
    {
      return nullptr;
    }

  protected:
    virtual void redo() override;
    virtual void undo() override;

  private:
    QSplitter *splitter_;
    QList<int> new_sizes_;
    QList<int> old_sizes_;

  };

  void CenterOn(qreal scene_pos);

  void UpdateViewTimebases();

  void NudgeInternal(rational amount);

  void MoveToPlayheadInternal(bool out);

private slots:
  void ViewMousePressed(TimelineViewMouseEvent* event);
  void ViewMouseMoved(TimelineViewMouseEvent* event);
  void ViewMouseReleased(TimelineViewMouseEvent* event);
  void ViewMouseDoubleClicked(TimelineViewMouseEvent* event);

  void ViewDragEntered(TimelineViewMouseEvent* event);
  void ViewDragMoved(TimelineViewMouseEvent* event);
  void ViewDragLeft(QDragLeaveEvent* event);
  void ViewDragDropped(TimelineViewMouseEvent* event);

  void AddBlock(Block* block);
  void RemoveBlock(Block *blocks);

  void AddTrack(Track* track);
  void RemoveTrack(Track* track);
  void TrackUpdated();

  void BlockUpdated();

  void UpdateHorizontalSplitters();

  void UpdateTimecodeWidthFromSplitters(QSplitter *s);

  void ShowContextMenu();

  void DeferredScrollAction();

  void ShowSequenceDialog();

  void SetUseAudioTimeUnits(bool use);

  void ToolChanged();

  void AddableObjectChanged();

  void SetViewWaveformsEnabled(bool e);

  void SetViewThumbnailsEnabled(QAction *action);

  void FrameRateChanged();

  void SampleRateChanged();

  void TrackIndexChanged(int old, int now);

  void SignalBlockSelectionChange();

  void RevealInFootageViewer();
  void RevealInProject();

  void TrackAboutToBeDeleted(Track *track);

  void SetSelectedClipsAutocaching(bool e);

  void CacheClips();
  void CacheClipsInOut();
  void CacheDiscard();

  void MulticamEnabledTriggered(bool e);

  void ForceUpdateRubberBand();

};

}

#endif // TIMELINEWIDGET_H
