#include <app/RackScrollWidget.hpp>
#include <app/Scene.hpp>
#include <window.hpp>
#include <app.hpp>
#include <settings.hpp>


namespace rack {
namespace app {


RackScrollWidget::RackScrollWidget() {
	zoomWidget = new widget::ZoomWidget;
	container->addChild(zoomWidget);

	rackWidget = new RackWidget;
	rackWidget->box.size = RACK_OFFSET.mult(2);
	zoomWidget->addChild(rackWidget);

	reset();
}

void RackScrollWidget::step() {
	// Clamp zoom
	settings::zoom = math::clamp(settings::zoom, -2.f, 2.f);
	// Compute zoom from exponential zoom
	float zoom = std::pow(2.f, settings::zoom);
	if (zoom != zoomWidget->zoom) {
		// Set offset based on zoomPos
		offset = offset.plus(zoomPos).div(zoomWidget->zoom).mult(zoom).minus(zoomPos);
		// Set zoom
		zoomWidget->setZoom(zoom);
	}

	zoomPos = box.size.div(2);

	// Compute module bounding box
	math::Rect moduleBox = rackWidget->moduleContainer->getChildrenBoundingBox();
	if (!moduleBox.size.isFinite())
		moduleBox = math::Rect(RACK_OFFSET, math::Vec(0, 0));

	// Expand moduleBox by half a screen size
	math::Rect scrollBox = moduleBox;
	scrollBox.pos = scrollBox.pos.mult(zoom);
	scrollBox.size = scrollBox.size.mult(zoom);
	scrollBox = scrollBox.grow(box.size.mult(0.6666));

	// Expand to the current viewport box so that moving modules (and thus changing the module bounding box) doesn't clamp the scroll offset.
	math::Rect viewportBox;
	viewportBox.pos = oldOffset;
	viewportBox.size = box.size;
	scrollBox = scrollBox.expand(viewportBox);

	// Reposition widgets
	zoomWidget->box = scrollBox;
	rackWidget->box.pos = scrollBox.pos.div(zoom).neg();

	// Scroll rack if dragging cable near the edge of the screen
	math::Vec pos = APP->window->mousePos;
	math::Rect viewport = getViewport(box.zeroPos());
	if (rackWidget->incompleteCable) {
		float margin = 20.0;
		float speed = 15.0;
		if (pos.x <= viewport.pos.x + margin)
			offset.x -= speed;
		if (pos.x >= viewport.pos.x + viewport.size.x - margin)
			offset.x += speed;
		if (pos.y <= viewport.pos.y + margin)
			offset.y -= speed;
		if (pos.y >= viewport.pos.y + viewport.size.y - margin)
			offset.y += speed;
	}

	ScrollWidget::step();
	oldOffset = offset;
}


void RackScrollWidget::draw(const DrawArgs& args) {
	// Hide scrollbars if full screen
	bool fullscreen = APP->window->isFullScreen();
	bool horizontalVisible;
	bool verticalVisible;
	if (fullscreen) {
		horizontalVisible = horizontalScrollBar->visible;
		verticalVisible = verticalScrollBar->visible;
		horizontalScrollBar->visible = false;
		verticalScrollBar->visible = false;
	}
	ScrollWidget::draw(args);
	if (fullscreen) {
		horizontalScrollBar->visible = horizontalVisible;
		verticalScrollBar->visible = verticalVisible;
	}
}

void RackScrollWidget::onHoverKey(const event::HoverKey& e) {
	ScrollWidget::onHoverKey(e);
	if (e.isConsumed())
		return;

	// Scroll with arrow keys
	float arrowSpeed = 30.0;
	if ((e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT))
		arrowSpeed /= 16.0;
	else if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL)
		arrowSpeed *= 4.0;
	else if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT)
		arrowSpeed /= 4.0;

	if (e.action == RACK_HELD) {
		switch (e.key) {
			case GLFW_KEY_LEFT: {
				offset.x -= arrowSpeed;
				e.consume(this);
			} break;
			case GLFW_KEY_RIGHT: {
				offset.x += arrowSpeed;
				e.consume(this);
			} break;
			case GLFW_KEY_UP: {
				offset.y -= arrowSpeed;
				e.consume(this);
			} break;
			case GLFW_KEY_DOWN: {
				offset.y += arrowSpeed;
				e.consume(this);
			} break;
		}
	}
}

void RackScrollWidget::onHoverScroll(const event::HoverScroll& e) {
	if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
		// Increase zoom
		float zoomDelta = e.scrollDelta.y / 50 / 4;
		if (settings::invertZoom)
			zoomDelta *= -1;
		settings::zoom += zoomDelta;
		zoomPos = e.pos;
		e.consume(this);
		return;
	}

	ScrollWidget::onHoverScroll(e);
}

void RackScrollWidget::reset() {
	offset = RACK_OFFSET.mult(zoomWidget->zoom);
	offset = offset.minus(math::Vec(30, 30));
}


} // namespace app
} // namespace rack
