/*
 * Oak Video Editor - Non-Linear Video Editor
 * Copyright (C) 2025 Olive CE 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/>.
 */

#include "timelineview.h"

#include <QDebug>
#include <QMimeData>
#include <QMouseEvent>
#include <QScrollBar>
#include <QtMath>
#include <QPen>

#include "config/config.h"
#include "common/qtutils.h"
#include "node/project/footage/footage.h"
#include "panel/panelmanager.h"
#include "panel/timeline/timeline.h"
#include "ui/colorcoding.h"
#include "widget/timelinewidget/timelinewidget.h"

namespace olive
{

#define super TimeBasedView

TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent)
	: super(parent)
	, selections_(nullptr)
	, ghosts_(nullptr)
	, show_beam_cursor_(false)
	, connected_track_list_(nullptr)
	, transition_overlay_out_(nullptr)
	, transition_overlay_in_(nullptr)
{
	Q_ASSERT(vertical_alignment == Qt::AlignTop ||
			 vertical_alignment == Qt::AlignBottom);
	setAlignment(Qt::AlignLeft | vertical_alignment);

	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
	setBackgroundRole(QPalette::Window);
	setContextMenuPolicy(Qt::CustomContextMenu);
	viewport()->setMouseTracking(true);

	SetIsTimelineAxes(true);
}

void TimelineView::mousePressEvent(QMouseEvent *event)
{
	// If we click on marker, jump to that point in the timeline
	QPointF scene_pos = mapToScene(event->pos());
	for (auto it = clip_marker_rects_.cbegin(); it != clip_marker_rects_.cend();
		 it++) {
		if (it.value().contains(scene_pos)) {
			GetViewerNode()->SetPlayhead(it.key()->time().in());
			break;
		}
	}

	TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);

	if (HandPress(event) ||
		(!GetItemAtScenePos(timeline_event.GetFrame(),
							timeline_event.GetTrack().index()) &&
		 Core::instance()->tool() != Tool::kAdd && PlayheadPress(event))) {
		// Let the parent handle this
		return;
	}

	if (dragMode() != GetDefaultDragMode()) {
		// Use default behavior when hand dragging for instance
		super::mousePressEvent(event);
		return;
	}

	emit MousePressed(&timeline_event);
}

void TimelineView::mouseMoveEvent(QMouseEvent *event)
{
	TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);

	if (HandMove(event) || PlayheadMove(event)) {
		// Let the parent handle this
		return;
	}

	if (dragMode() != GetDefaultDragMode()) {
		super::mouseMoveEvent(event);
		return;
	}

	if (event->buttons() == Qt::NoButton) {
		Block *b = GetItemAtScenePos(timeline_event.GetFrame(),
									 timeline_event.GetTrack().index());
		if (b) {
			setToolTip(
				tr("In: %1\nOut: %2\nDuration: %3")
					.arg(QString::fromStdString(Timecode::time_to_timecode(
							 b->in(), timebase(),
							 Core::instance()->GetTimecodeDisplay())),
						 QString::fromStdString(Timecode::time_to_timecode(
							 b->out(), timebase(),
							 Core::instance()->GetTimecodeDisplay())),
						 QString::fromStdString(Timecode::time_to_timecode(
							 b->length(), timebase(),
							 Core::instance()->GetTimecodeDisplay()))));
		} else {
			setToolTip(QString());
		}
	}

	emit MouseMoved(&timeline_event);
}

void TimelineView::mouseReleaseEvent(QMouseEvent *event)
{
	if (HandRelease(event) || PlayheadRelease(event)) {
		// Let the parent handle this
		return;
	}

	if (dragMode() != GetDefaultDragMode()) {
		super::mouseReleaseEvent(event);
		return;
	}

	TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);

	emit MouseReleased(&timeline_event);
}

void TimelineView::mouseDoubleClickEvent(QMouseEvent *event)
{
	TimelineViewMouseEvent timeline_event = CreateMouseEvent(event);

	emit MouseDoubleClicked(&timeline_event);
}

void TimelineView::dragEnterEvent(QDragEnterEvent *event)
{
	TimelineViewMouseEvent timeline_event = CreateMouseEvent(
		event->pos(), Qt::NoButton, event->keyboardModifiers());

	timeline_event.SetMimeData(event->mimeData());
	timeline_event.SetEvent(event);

	emit DragEntered(&timeline_event);
}

void TimelineView::dragMoveEvent(QDragMoveEvent *event)
{
	TimelineViewMouseEvent timeline_event = CreateMouseEvent(
		event->pos(), Qt::NoButton, event->keyboardModifiers());

	timeline_event.SetMimeData(event->mimeData());
	timeline_event.SetEvent(event);

	emit DragMoved(&timeline_event);
}

void TimelineView::dragLeaveEvent(QDragLeaveEvent *event)
{
	emit DragLeft(event);
}

void TimelineView::dropEvent(QDropEvent *event)
{
	TimelineViewMouseEvent timeline_event = CreateMouseEvent(
		event->pos(), Qt::NoButton, event->keyboardModifiers());

	timeline_event.SetMimeData(event->mimeData());
	timeline_event.SetEvent(event);

	emit DragDropped(&timeline_event);
}

void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
{
	if (!connected_track_list_) {
		return;
	}

	painter->setPen(palette().base().color());

	int line_y = 0;

	foreach (Track *track, connected_track_list_->GetTracks()) {
		line_y += track->GetTrackHeightInPixels();

		// One px gap between tracks
		line_y++;

		int this_line_y;

		if (alignment() & Qt::AlignTop) {
			this_line_y = line_y;
		} else {
			this_line_y = -line_y;
		}

		painter->drawLine(qRound(rect.left()), this_line_y,
						  qRound(rect.right()), this_line_y);
	}
}

void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
{
	if (!connected_track_list_) {
		return;
	}

	// Draw block backgrounds
	DrawBlocks(painter, false);

	// Draw selections
	if (selections_ && !selections_->isEmpty()) {
		painter->setPen(Qt::NoPen);
		painter->setBrush(QColor(0, 0, 0, 64));

		for (auto it = selections_->cbegin(); it != selections_->cend(); it++) {
			if (it.key().type() == connected_track_list_->type()) {
				int track_index = it.key().index();

				foreach (const TimeRange &range, it.value()) {
					painter->drawRect(TimeToScene(range.in()),
									  GetTrackY(track_index),
									  TimeToScene(range.length()),
									  GetTrackHeight(track_index));
				}
			}
		}
	}

	// Draw block foregrounds
	DrawBlocks(painter, true);

	// Draw ghosts
	if (ghosts_ && !ghosts_->isEmpty()) {
		foreach (TimelineViewGhostItem *ghost, (*ghosts_)) {
			if (ghost->GetTrack().type() == connected_track_list_->type() &&
				!ghost->IsInvisible()) {
				int track_index = ghost->GetAdjustedTrack().index();

				Block *attached = QtUtils::ValueToPtr<Block>(
					ghost->GetData(TimelineViewGhostItem::kAttachedBlock));

				if (attached &&
					OLIVE_CONFIG("ShowClipWhileDragging").toBool()) {
					int adj_track = ghost->GetAdjustedTrack().index();
					qreal track_top = GetTrackY(adj_track);
					qreal track_height = GetTrackHeight(adj_track);

					qreal old_opacity = painter->opacity();
					painter->setOpacity(0.5);

					rational in = ghost->GetAdjustedIn(),
							 out = ghost->GetAdjustedOut(),
							 media_in = ghost->GetAdjustedMediaIn();
					DrawBlock(painter, false, attached, track_top, track_height,
							  in, out, media_in);
					DrawBlock(painter, true, attached, track_top, track_height,
							  in, out, media_in);

					painter->setOpacity(old_opacity);
				}

				painter->setPen(QPen(Qt::yellow, 2));
				painter->setBrush(Qt::NoBrush);

				painter->drawRect(TimeToScene(ghost->GetAdjustedIn()),
								  GetTrackY(track_index),
								  TimeToScene(ghost->GetAdjustedLength()),
								  GetTrackHeight(track_index));
			}
		}
	}

	// Draw beam cursor
	if (show_beam_cursor_ &&
		cursor_coord_.GetTrack().type() == connected_track_list_->type()) {
		painter->setPen(Qt::gray);

		double cursor_x = TimeToScene(cursor_coord_.GetFrame());
		int track_index = cursor_coord_.GetTrack().index();
		int track_y = GetTrackY(track_index);

		painter->drawLine(cursor_x, track_y, cursor_x,
						  track_y + GetTrackHeight(track_index));
	}

	// Draw recording overlay
	if (recording_overlay_ &&
		recording_coord_.GetTrack().type() == connected_track_list_->type()) {
		painter->setPen(QPen(Qt::red, 2));
		painter->setBrush(QColor(255, 128, 128));

		int x = TimeToScene(recording_coord_.GetFrame());
		painter->drawRect(x, GetTrackY(recording_coord_.GetTrack().index()),
						  TimeToScene(GetViewerNode()->GetPlayhead()) - x,
						  GetTrackHeight(recording_coord_.GetTrack().index()));
	}

	// Draw standard TimelineViewBase things (such as playhead)
	super::drawForeground(painter, rect);
}

void TimelineView::ToolChangedEvent(Tool::Item tool)
{
	switch (tool) {
	case Tool::kRazor:
		setCursor(Qt::SplitHCursor);
		break;
	case Tool::kEdit:
		setCursor(Qt::IBeamCursor);
		break;
	case Tool::kAdd:
	case Tool::kTransition:
	case Tool::kZoom:
	case Tool::kRecord:
		setCursor(Qt::CrossCursor);
		break;
	case Tool::kTrackSelect:
		setCursor(Qt::SizeHorCursor); // FIXME: Not the ideal cursor
		break;
	default:
		unsetCursor();
	}

	// Hide/show cursor if necessary
	if (show_beam_cursor_) {
		show_beam_cursor_ = false;
		viewport()->update();
	}
}

void TimelineView::SceneRectUpdateEvent(QRectF &rect)
{
	if (alignment() & Qt::AlignTop) {
		rect.setTop(0);
		rect.setBottom(GetHeightOfAllTracks() + height() / 2);
	} else if (alignment() & Qt::AlignBottom) {
		rect.setBottom(0);
		rect.setTop(GetHeightOfAllTracks() - height() / 2);
	}
}

Track::Type TimelineView::ConnectedTrackType()
{
	if (connected_track_list_) {
		return connected_track_list_->type();
	}

	return Track::kNone;
}

TimelineCoordinate TimelineView::ScreenToCoordinate(const QPoint &pt)
{
	return SceneToCoordinate(mapToScene(pt));
}

TimelineCoordinate TimelineView::SceneToCoordinate(const QPointF &pt)
{
	return TimelineCoordinate(SceneToTime(pt.x()),
							  Track::Reference(ConnectedTrackType(),
											   SceneToTrack(pt.y())));
}

TimelineViewMouseEvent TimelineView::CreateMouseEvent(QMouseEvent *event)
{
	return CreateMouseEvent(event->pos(), event->button(), event->modifiers());
}

TimelineViewMouseEvent
TimelineView::CreateMouseEvent(const QPoint &pos, Qt::MouseButton button,
							   Qt::KeyboardModifiers modifiers)
{
	QPointF scene_pt = mapToScene(pos);

	return TimelineViewMouseEvent(scene_pt, pos, GetScale(), timebase(),
								  Track::Reference(ConnectedTrackType(),
												   SceneToTrack(scene_pt.y())),
								  button, modifiers);
}

void TimelineView::DrawBlocks(QPainter *painter, bool foreground)
{
	rational start_time = SceneToTime(GetTimelineLeftBound());
	rational end_time = SceneToTime(GetTimelineRightBound());

	foreach (Track *track, connected_track_list_->GetTracks()) {
		// Get first visible block in this track
		Block *block = track->NearestBlockBeforeOrAt(start_time);

		qreal track_top = GetTrackY(track->Index());
		qreal track_height = GetTrackHeight(track->Index());

		while (block) {
			DrawBlock(painter, foreground, block, track_top, track_height);

			if (block->out() >= end_time) {
				// Rest of the clips are offscreen, can break loop now
				break;
			}

			block = block->next();
		}
	}
}

void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block,
							 qreal block_top, qreal block_height,
							 const rational &in, const rational &out,
							 const rational &media_in)
{
	if (dynamic_cast<ClipBlock *>(block) ||
		dynamic_cast<TransitionBlock *>(block)) {
		qreal block_in = TimeToScene(in);

		qreal block_left = qMax(GetTimelineLeftBound(), block_in);
		qreal block_right = qMin(GetTimelineRightBound(), TimeToScene(out)) - 1;

		QRectF r(block_left, block_top, block_right - block_left, block_height);

		QColor shadow_color = block->is_enabled() ?
								  QtUtils::toQColor(block->color()).darker() :
								  QColor(Qt::darkGray).darker();

		const qreal MINIMUM_RECT_WIDTH = 2;
		const qreal MINIMUM_DETAIL_WIDTH = 8;

		if (r.width() <= MINIMUM_RECT_WIDTH) {
			if (!foreground) {
				// Just draw a green background
				// Width is likely fractional, so we ceil it and add 1 to ensure the entire width of the
				// rect is painted
				r.setWidth(std::ceil(r.width()) + 1);
				painter->fillRect(r, shadow_color);
			}
		} else {
			QFontMetrics fm = fontMetrics();
			int text_height = fm.height();
			int text_padding =
				text_height /
				4; // This ties into the track minimum height being 1.5
			int text_total_height = text_height + text_padding + text_padding;

			if (foreground) {
				painter->setBrush(Qt::NoBrush);

				if (r.width() > MINIMUM_DETAIL_WIDTH) {
					QString using_label = block->GetLabelOrName();

					QRectF text_rect = r.adjusted(text_padding, text_padding,
												  -text_padding, -text_padding);
					painter->setPen(
						block->is_enabled() ?
							ColorCoding::GetUISelectorColor(block->color()) :
							Qt::lightGray);
					painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignTop,
									  using_label);

					if (block->HasLinks()) {
						int text_width =
							qMin(qRound(text_rect.width()),
								 QtUtils::QFontMetricsWidth(fm, using_label));

						int underline_y = text_rect.y() + text_height;

						painter->drawLine(text_rect.x(), underline_y,
										  text_width + text_rect.x(),
										  underline_y);
					}
				}

				qreal line_bottom = block_top + block_height - 1;

				painter->setPen(Qt::white);
				painter->drawLine(block_left, block_top, block_right,
								  block_top);
				painter->drawLine(block_left, block_top, block_left,
								  line_bottom);

				painter->setPen(shadow_color);
				painter->drawLine(block_left, line_bottom, block_right,
								  line_bottom);
				painter->drawLine(block_right, line_bottom, block_right,
								  block_top);
			} else {
				painter->setPen(Qt::NoPen);
				painter->setBrush(
					block->is_enabled() ?
						block->brush(block_top, block_top + block_height) :
						Qt::gray);
				painter->drawRect(r);

				if (r.width() > MINIMUM_DETAIL_WIDTH) {
					if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
						QRect preview_rect = r.toRect();

						// Draw clip thumbnails
						if (clip->GetTrackType() == Track::kVideo &&
							OLIVE_CONFIG("TimelineThumbnailMode").toInt() !=
								Timeline::kThumbnailOff) {
							// Start thumbnails underneath clip name
							preview_rect.adjust(0, text_total_height, 0, 0);

							if (preview_rect.height() > r.height() / 3) {
								if (const FrameHashCache *thumbs =
										clip->thumbnails()) {
									QRect thumb_rect;
									painter->setRenderHint(
										QPainter::SmoothPixmapTransform);
									painter->setClipRect(preview_rect);

									if (OLIVE_CONFIG("TimelineThumbnailMode") ==
										Timeline::kThumbnailOn) {
										Sequence *s = clip->track()->sequence();
										int width = s->GetVideoParams().width();
										int height =
											s->GetVideoParams().height();
										int start;
										if (height >
											0) { // Prevent divide by zero/invalid params
											double scale =
												double(preview_rect.height()) /
												double(height);
											thumb_rect.setWidth(width * scale);
											start = (((preview_rect.left() -
													   int(qFloor(block_in))) /
													  thumb_rect.width()) *
													 thumb_rect.width()) +
													qFloor(block_in);
										} else {
											start = preview_rect.left();
										}

										for (int i = start;
											 i < preview_rect.right();
											 i += thumb_rect.width() + 1) {
											rational time_here =
												SceneToTime(
													i - block_in, GetScale(),
													connected_track_list_
														->parent()
														->GetVideoParams()
														.frame_rate_as_time_base()) +
												media_in;
											DrawThumbnail(painter, thumbs,
														  time_here, i,
														  preview_rect,
														  &thumb_rect);
										}

									} else {
										rational time =
											clip->media_range().in();
										time = Timecode::snap_time_to_timebase(
											time, thumbs->GetTimebase(),
											Timecode::kFloor);
										DrawThumbnail(painter, thumbs, time,
													  block_left, preview_rect,
													  &thumb_rect);
									}

									painter->setClipping(false);
								}
							}
						}

						// Draw waveform
						if (clip->GetTrackType() == Track::kAudio &&
							OLIVE_CONFIG("TimelineWaveformMode").toInt() ==
								Timeline::kWaveformsEnabled) {
							if (const AudioWaveformCache *wave =
									clip->waveform()) {
								rational waveform_start =
									SceneToTime(
										block_left - block_in, GetScale(),
										connected_track_list_->parent()
											->GetAudioParams()
											.sample_rate_as_time_base()) +
									media_in;
								painter->setPen(shadow_color);

								wave->Draw(painter, preview_rect,
										   this->GetScale(), waveform_start);
							}
						}

						// Draw zebra stripes and markers
						if (clip->connected_viewer()) {
							if (!clip->connected_viewer()->GetLength().isNull()) {
								painter->setPen(shadow_color);

								if (clip->media_in() < 0) {
									qreal zebra_right = TimeToScene(
										clip->in() - clip->media_in());

									switch (clip->loop_mode()) {
									case LoopMode::kLoopModeOff:
										// Draw stripes for sections of clip < 0
										if (zebra_right >
											GetTimelineLeftBound()) {
											DrawZebraStripes(
												painter,
												QRectF(block_left, block_top,
													   zebra_right - block_left,
													   block_height));
										}
										break;
									case LoopMode::kLoopModeLoop:
										for (qreal i = zebra_right;
											 i > block_left;
											 i -= TimeToScene(
												 clip->connected_viewer()
													 ->GetLength())) {
											painter->drawLine(i, block_top, i,
															  block_top +
																  block_height);
										}
										break;
									case LoopMode::kLoopModeClamp:
										painter->drawLine(
											zebra_right, block_top, zebra_right,
											block_top + block_height);
										break;
									}
								}

								if (clip->length() + clip->media_in() >
									clip->connected_viewer()->GetLength()) {
									qreal zebra_left = TimeToScene(
										clip->out() -
										(clip->media_in() + clip->length() -
										 clip->connected_viewer()->GetLength()));
									switch (clip->loop_mode()) {
									case LoopMode::kLoopModeOff:
										// Draw stripes for sections for clip > clip length
										if (zebra_left <
											GetTimelineRightBound()) {
											DrawZebraStripes(
												painter,
												QRectF(zebra_left, block_top,
													   block_right - zebra_left,
													   block_height));
										}
										break;
									case LoopMode::kLoopModeLoop:
										for (qreal i = zebra_left;
											 i < block_right;
											 i += TimeToScene(
												 clip->connected_viewer()
													 ->GetLength())) {
											painter->drawLine(i, block_top, i,
															  block_top +
																  block_height);
										}
										break;
									case LoopMode::kLoopModeClamp:
										painter->drawLine(
											zebra_left, block_top, zebra_left,
											block_top + block_height);
										break;
									}
								}
							}

							TimelineMarkerList *marker_list =
								clip->connected_viewer()->GetMarkers();
							if (!marker_list->empty()) {
								clip_marker_rects_.clear();

								for (auto it = marker_list->cbegin();
									 it != marker_list->cend(); it++) {
									TimelineMarker *marker = *it;
									// Make sure marker is within In/Out points of the clip
									if (marker->time().in() >=
											clip->media_in() &&
										marker->time().out() <=
											clip->media_in() + clip->length()) {
										QPoint marker_pt(
											TimeToScene(clip->in() -
														clip->media_in() +
														marker->time().in()),
											block_top + block_height);
										painter->setClipRect(r);
										QRect marker_rect =
											marker->Draw(painter, marker_pt, -1,
														 GetScale(), false);
										clip_marker_rects_.insert(marker,
																  marker_rect);
										painter->setClipping(false);
									}
								}
							}
						}

						if (const FrameHashCache *cache =
								clip->connected_video_cache()) {
							if (cache->HasValidatedRanges()) {
								QRect cache_rect =
									r.adjusted(
										 0,
										 r.height() -
											 PlaybackCache::
												 GetCacheIndicatorHeight(),
										 0, 0)
										.toRect();
								cache->Draw(painter, clip->media_in(),
											GetScale(), cache_rect);
							}
						}
					}

					// For transitions, show lines representing a transition
					if (TransitionBlock *transition =
							dynamic_cast<TransitionBlock *>(block)) {
						QVector<QLineF> lines;

						if (transition->connected_in_block()) {
							lines.append(QLineF(r.bottomLeft(), r.topRight()));
						}

						if (transition->connected_out_block()) {
							lines.append(QLineF(r.topLeft(), r.bottomRight()));
						}

						painter->setPen(shadow_color);
						painter->drawLines(lines);
					}

					if (transition_overlay_out_ == block ||
						transition_overlay_in_ == block) {
						QRectF transition_overlay_rect = r;

						qreal transition_overlay_width =
							TimeToScene(block->length()) * 0.5;
						if (transition_overlay_out_ && transition_overlay_in_) {
							// This is a dual transition, use the smallest width
							Block *other_block =
								(transition_overlay_out_ == block) ?
									transition_overlay_in_ :
									transition_overlay_out_;

							qreal other_width =
								TimeToScene(other_block->length()) * 0.5;

							transition_overlay_width =
								qMin(transition_overlay_width, other_width);
						}

						if (transition_overlay_out_ == block) {
							transition_overlay_rect.setLeft(
								transition_overlay_rect.right() -
								transition_overlay_width);
						} else {
							transition_overlay_rect.setRight(
								transition_overlay_rect.left() +
								transition_overlay_width);
						}

						painter->setPen(Qt::NoPen);
						painter->setBrush(QColor(0, 0, 0, 64));

						painter->drawRect(transition_overlay_rect);
					}
				}
			}
		}
	}
}

void TimelineView::DrawZebraStripes(QPainter *painter, const QRectF &r)
{
	int zebra_interval = fontMetrics().height();

	painter->setPen(QPen(QColor(0, 0, 0, 128), zebra_interval / 4));
	painter->setBrush(Qt::NoBrush);

	QVector<QLineF> lines;
	lines.reserve(qCeil(r.width() / zebra_interval));

	qreal left = r.left() - r.height();
	qreal right = r.right() + r.height();

	for (qreal i = left; i < right; i += zebra_interval) {
		lines.append(QLineF(i, r.top(), i - r.height(), r.bottom()));
	}

	painter->setClipRect(r);
	painter->drawLines(lines);
	painter->setClipping(false);
}

int TimelineView::GetHeightOfAllTracks() const
{
	if (connected_track_list_) {
		if (alignment() & Qt::AlignTop) {
			return GetTrackY(connected_track_list_->GetTrackCount());
		} else {
			return GetTrackY(connected_track_list_->GetTrackCount() - 1);
		}
	} else {
		return 0;
	}
}

qreal TimelineView::GetTimelineLeftBound() const
{
	return horizontalScrollBar()->value();
}

qreal TimelineView::GetTimelineRightBound() const
{
	return GetTimelineLeftBound() + viewport()->width();
}

void TimelineView::DrawThumbnail(QPainter *painter,
								 const FrameHashCache *thumbs,
								 const rational &time, int x,
								 const QRect &preview_rect,
								 QRect *thumb_rect) const
{
	QString thumbnail = thumbs->GetValidCacheFilename(time);

	if (!thumbnail.isEmpty()) {
		QImage img;
		if (img.load(thumbnail, "jpg")) {
			double scale = double(preview_rect.height()) / double(img.height());
			*thumb_rect = QRect(x, preview_rect.top(), img.width() * scale,
								preview_rect.height());
			painter->drawImage(*thumb_rect, img);
		}
	}
}

int TimelineView::GetTrackY(int track_index) const
{
	if (!connected_track_list_ || !connected_track_list_->GetTrackCount()) {
		return 0;
	}

	int y = 0;

	if (alignment() & Qt::AlignBottom) {
		track_index++;
	}

	for (int i = 0; i < track_index; i++) {
		y += GetTrackHeight(i);

		// One px line between each track
		y++;
	}

	if (alignment() & Qt::AlignBottom) {
		y = -y + 1;
	}

	return y;
}

int TimelineView::GetTrackHeight(int track_index) const
{
	if (!connected_track_list_ || connected_track_list_->GetTrackCount() == 0) {
		// Handle null or empty track list
		return Track::GetDefaultTrackHeightInPixels();
	}

	if (track_index >= connected_track_list_->GetTrackCount()) {
		// Handle new track at the end of the list
		return connected_track_list_
			->GetTrackAt(connected_track_list_->GetTrackCount() - 1)
			->GetTrackHeightInPixels();
	}

	if (track_index < 0) {
		// Handle new track at the beginning of the list
		return connected_track_list_->GetTrackAt(0)->GetTrackHeightInPixels();
	}

	// Track definitely exists, return its actual height
	return connected_track_list_->GetTrackAt(track_index)
		->GetTrackHeightInPixels();
}

QPoint TimelineView::GetScrollCoordinates() const
{
	return QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value());
}

void TimelineView::SetScrollCoordinates(const QPoint &pt)
{
	horizontalScrollBar()->setValue(pt.x());
	verticalScrollBar()->setValue(pt.y());
}

void TimelineView::ConnectTrackList(TrackList *list)
{
	if (connected_track_list_) {
		disconnect(connected_track_list_, &TrackList::TrackListChanged, this,
				   &TimelineView::TrackListChanged);
		disconnect(connected_track_list_, &TrackList::TrackHeightChanged, this,
				   &TimelineView::TrackListChanged);
	}

	connected_track_list_ = list;

	if (connected_track_list_) {
		connect(connected_track_list_, &TrackList::TrackListChanged, this,
				&TimelineView::TrackListChanged);
		connect(connected_track_list_, &TrackList::TrackHeightChanged, this,
				&TimelineView::TrackListChanged);
	}
}

void TimelineView::SetBeamCursor(const TimelineCoordinate &coord)
{
	if (!connected_track_list_) {
		return;
	}

	bool update_required =
		coord.GetTrack().type() == connected_track_list_->type() ||
		cursor_coord_.GetTrack().type() == connected_track_list_->type();

	show_beam_cursor_ = true;
	cursor_coord_ = coord;

	if (update_required) {
		viewport()->update();
	}
}

void TimelineView::SetTransitionOverlay(ClipBlock *out, ClipBlock *in)
{
	if (transition_overlay_out_ != out || transition_overlay_in_ != in) {
		Track::Type type = Track::kNone;

		if (out) {
			type = out->track()->type();
		} else if (in) {
			type = in->track()->type();
		}

		if (type == this->connected_track_list_->type()) {
			transition_overlay_out_ = out;
			transition_overlay_in_ = in;
		} else {
			transition_overlay_out_ = nullptr;
			transition_overlay_in_ = nullptr;
		}

		viewport()->update();
	}
}

void TimelineView::EnableRecordingOverlay(const TimelineCoordinate &coord)
{
	recording_overlay_ = true;
	recording_coord_ = coord;
	viewport()->update();
}

void TimelineView::DisableRecordingOverlay()
{
	recording_overlay_ = false;
	viewport()->update();
}

int TimelineView::SceneToTrack(double y)
{
	int track = -1;
	int heights = 0;

	if (alignment() & Qt::AlignBottom) {
		y = -y;
	}

	do {
		track++;
		heights += GetTrackHeight(track);
	} while (y > heights);

	return track;
}

Block *TimelineView::GetItemAtScenePos(const rational &time,
									   int track_index) const
{
	if (connected_track_list_) {
		Track *track = connected_track_list_->GetTrackAt(track_index);

		if (track) {
			foreach (Block *b, track->Blocks()) {
				if (b->in() <= time && b->out() > time) {
					return b;
				}
			}
		}
	}

	return nullptr;
}

QVector<Block *> TimelineView::GetItemsAtSceneRect(const QRectF &rect) const
{
	QVector<Block *> list;

	if (connected_track_list_) {
		rational start = this->SceneToTime(rect.left());
		rational end = this->SceneToTime(rect.right());

		for (int i = 0; i < connected_track_list_->GetTrackCount(); i++) {
			Track *track = connected_track_list_->GetTrackAt(i);
			int track_top = GetTrackY(i);
			int track_bottom = track_top + GetTrackHeight(i);

			if (track) {
				if (!(track_bottom < rect.top() || track_top > rect.bottom())) {
					Block *b = track->NearestBlockBeforeOrAt(start);
					while (b && b->in() < end) {
						list.append(b);
						b = b->next();
					}
				}
			}
		}
	}

	return list;
}

void TimelineView::TrackListChanged()
{
	UpdateSceneRect();
	viewport()->update();
}

}
