/*
 * 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 "sliderladder.h"

#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QScreen>
#include <QtMath>
#include <QVBoxLayout>

#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif

#include "common/lerp.h"
#include "common/qtutils.h"
#include "config/config.h"

namespace olive
{

SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values,
						   QString width_hint, QWidget *parent)
	: QFrame(parent, Qt::Popup)
{
	QVBoxLayout *layout = new QVBoxLayout(this);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->setSpacing(0);

	setFrameShape(QFrame::Box);
	setLineWidth(1);

	if (!OLIVE_CONFIG("UseSliderLadders").toBool()) {
		nb_outer_values = 0;
	}

	for (int i = nb_outer_values - 1; i >= 0; i--) {
		elements_.append(new SliderLadderElement(
			qPow(10, i + 1) * drag_multiplier, width_hint));
	}

	// Create center entry
	SliderLadderElement *start_element =
		new SliderLadderElement(drag_multiplier, width_hint);
	active_element_ = elements_.size();
	start_element->SetHighlighted(true);
	elements_.append(start_element);

	for (int i = 0; i < nb_outer_values; i++) {
		elements_.append(new SliderLadderElement(
			qPow(10, -i - 1) * drag_multiplier, width_hint));
	}

	foreach (SliderLadderElement *e, elements_) {
		layout->addWidget(e);
	}

	if (elements_.size() == 1) {
		elements_.first()->SetMultiplierVisible(false);
	}

	drag_timer_.setInterval(10);
	connect(&drag_timer_, &QTimer::timeout, this, &SliderLadder::TimerUpdate);

	screen_ = nullptr;
	foreach (QScreen *screen, qApp->screens()) {
		if (screen->geometry().contains(QCursor::pos())) {
			screen_ = screen;
			break;
		}
	}

	if (UsingLadders()) {
		drag_start_x_ = -1;
		wrap_count_ = 0;
	} else {
#if defined(Q_OS_MAC)
		CGAssociateMouseAndMouseCursorPosition(false);
		CGDisplayHideCursor(kCGDirectMainDisplay);
		CGGetLastMouseDelta(nullptr, nullptr);
#else
		drag_start_x_ = QCursor::pos().x();
		drag_start_y_ = QCursor::pos().y();

		static_cast<QGuiApplication *>(QApplication::instance())
			->setOverrideCursor(Qt::BlankCursor);
#endif
	}
}

SliderLadder::~SliderLadder()
{
	if (UsingLadders()) {
		if (wrap_count_ != 0) {
			// If wrapped, restore cursor to ladder
			QCursor::setPos(pos() + rect().center());
		}
	} else {
#if defined(Q_OS_MAC)
		CGAssociateMouseAndMouseCursorPosition(true);
		CGDisplayShowCursor(kCGDirectMainDisplay);
#else
		static_cast<QGuiApplication *>(QApplication::instance())
			->restoreOverrideCursor();
#endif
	}
}

void SliderLadder::SetValue(const QString &s)
{
	foreach (SliderLadderElement *e, elements_) {
		e->SetValue(s);
	}
}

void SliderLadder::StartListeningToMouseInput()
{
	QMetaObject::invokeMethod(&drag_timer_, "start", Qt::QueuedConnection);
}

void SliderLadder::mouseReleaseEvent(QMouseEvent *event)
{
	Q_UNUSED(event)

	this->close();
}

void SliderLadder::closeEvent(QCloseEvent *event)
{
	Q_UNUSED(event)

	drag_timer_.stop();

	emit Released();

	QFrame::closeEvent(event);
}

void SliderLadder::TimerUpdate()
{
	int ladder_left = this->x();
	int ladder_right = this->x() + this->width() - 1;
	int now_pos = QCursor::pos().x();

	if (UsingLadders()) {
		bool is_under_mouse = (now_pos >= ladder_left &&
							   now_pos <= ladder_right && wrap_count_ == 0);

		if (drag_start_x_ != -1 &&
			(is_under_mouse ||
			 (drag_start_x_ < ladder_left && now_pos > ladder_right) ||
			 (drag_start_x_ > ladder_right && now_pos < ladder_left))) {
			// We're ending a drag, try to return the value back to its beginning
			int anchor;

			if (drag_start_x_ < ladder_left) {
				anchor = ladder_left;
			} else {
				anchor = ladder_right;
			}

			int makeup_value = anchor - drag_start_x_;
			emit DraggedByValue(makeup_value,
								elements_.at(active_element_)->GetMultiplier());

			drag_start_x_ = -1;
		}

		if (is_under_mouse) {
			// Determine which element is currently active
			for (int i = 0; i < elements_.size(); i++) {
				if (elements_.at(i)->underMouse()) {
					if (i != active_element_) {
						elements_.at(active_element_)->SetHighlighted(false);
						active_element_ = i;
						elements_.at(active_element_)->SetHighlighted(true);
					}

					break;
				}
			}

		} else {
			if (drag_start_x_ == -1) {
				// Drag is a new leave from the ladder, calculate origin
				if (now_pos < ladder_left) {
					drag_start_x_ = ladder_left;
				} else {
					drag_start_x_ = ladder_right;
				}
			}

			emit DraggedByValue(now_pos - drag_start_x_,
								elements_.at(active_element_)->GetMultiplier());

			// Determine if cursor is at desktop edge, if so wrap around to other side
			if (screen_) {
				int left = screen_->geometry().left();
				int right = screen_->geometry().right();
				int width = right - left;
				if (now_pos <= left || now_pos >= right) {
					int orig_now_pos = now_pos;
					int orig_wrap = wrap_count_;

					if (now_pos <= left) {
						wrap_count_--;
						now_pos += width;
					} else {
						wrap_count_++;
						now_pos -= width;
					}

					QPoint p(now_pos, QCursor::pos().y());
					QCursor::setPos(p);
					if (QCursor::pos() != p) {
						wrap_count_ = orig_wrap;
						now_pos = orig_now_pos;
					}
				}
			}

			drag_start_x_ = now_pos;
		}

	} else {
		int32_t x_mvmt, y_mvmt;

		// Keep cursor in the same position
#if defined(Q_OS_MAC)
		CGGetLastMouseDelta(&x_mvmt, &y_mvmt);
#else
		QPoint current_pos = QCursor::pos();

		x_mvmt = current_pos.x() - drag_start_x_;
		y_mvmt = current_pos.y() - drag_start_y_;

		QCursor::setPos(QPoint(drag_start_x_, drag_start_y_));
#endif

		if (x_mvmt || y_mvmt) {
			double multiplier = 1.0;

			if (qApp->keyboardModifiers() & Qt::ControlModifier) {
				multiplier *= 0.01;
			}

			if (qApp->keyboardModifiers() & Qt::ShiftModifier) {
				multiplier *= 100.0;
			}

			emit DraggedByValue(x_mvmt + y_mvmt, multiplier);
		}
	}
}

bool SliderLadder::UsingLadders() const
{
	return elements_.size() > 1;
}

SliderLadderElement::SliderLadderElement(const double &multiplier,
										 QString width_hint, QWidget *parent)
	: QWidget(parent)
	, multiplier_(multiplier)
	, highlighted_(false)
	, multiplier_visible_(true)
{
	QVBoxLayout *layout = new QVBoxLayout(this);

	label_ = new QLabel();
	label_->setAlignment(Qt::AlignCenter);
	label_->setFixedWidth(
		QtUtils::QFontMetricsWidth(label_->fontMetrics(), width_hint));
	layout->addWidget(label_);

	QPalette p = palette();
	QColor highlight_color = palette().text().color();
	highlight_color.setAlpha(64);
	p.setColor(QPalette::Highlight, highlight_color);
	setPalette(p);

	setAutoFillBackground(true);

	UpdateLabel();
}

void SliderLadderElement::SetHighlighted(bool e)
{
	highlighted_ = e;

	if (highlighted_) {
		setBackgroundRole(QPalette::Highlight);
	} else {
		setBackgroundRole(QPalette::Window);
	}

	UpdateLabel();
}

void SliderLadderElement::SetValue(const QString &value)
{
	value_ = value;

	UpdateLabel();
}

void SliderLadderElement::SetMultiplierVisible(bool e)
{
	multiplier_visible_ = e;

	UpdateLabel();
}

void SliderLadderElement::UpdateLabel()
{
	if (multiplier_visible_) {
		QString val_text;

		if (highlighted_) {
			val_text = value_;
		}

		label_->setText(QStringLiteral("%1\n%2").arg(
			QString::number(multiplier_), val_text));
	} else {
		label_->setText(value_);
	}
}

}
