/***

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

  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 SynchronizeSelectedClipsBySourceTime();

	void SynchronizeSelectedClipsByWaveform();

	void GenerateProxiesForSelectedClips();

	void SetSelectedClipsProxyEnabled(bool enabled);

	void RevealProxyForSelectedClips();

	void DeleteProxiesForSelectedClips();

	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
