#include "ui.h"

#include <bits/stdc++.h>

#include "internal.h"
#include "internal/ui.h"

namespace kek {

constexpr UIValue &UIValue::operator+=(const UIValue& rhs) {
	this->pixels += rhs.pixels;
	this->parentWidth += rhs.parentWidth;
	this->parentHeight += rhs.parentHeight;
	this->screenWidth += rhs.screenWidth;
	this->screenHeight += rhs.screenHeight;
	return *this;
}

constexpr UIValue &UIValue::operator-=(const UIValue& rhs) {
	this->pixels -= rhs.pixels;
	this->parentWidth -= rhs.parentWidth;
	this->parentHeight -= rhs.parentHeight;
	this->screenWidth -= rhs.screenWidth;
	this->screenHeight -= rhs.screenHeight;
	return *this;
}

constexpr UIValue operator+(const UIValue& lhs, const UIValue& rhs) {
	return UIValue(lhs) += rhs;
}

constexpr UIValue operator-(const UIValue& lhs, const UIValue& rhs) {
	return UIValue(lhs) -= rhs;
}

UIElement::UIElement(UIValue x, UIValue y): x(x), y(y) {

}

UIElement::~UIElement() {

}

// Returns the element's position relative to its parent in pixels
UIPoint UIElement::getPosition() {
	return UIPoint(uiToScreen(x), uiToScreen(y));
}

// Returns the element's position on the screen in pixels
UIPoint UIElement::getScreenPosition() {
	UIPoint pos = getPosition();
	if(parent) {
		UIPoint parentPos = parent->getPosition();
		pos.x += parentPos.x;
		pos.y += parentPos.y;
	}
	return pos;
}

std::vector<UIElement *> UIElement::getChildren() {
	return children;
}

void UIElement::addChild(UIElement *child) {
	child->parent = this;
	children.push_back(child);
}

void UIElement::removeChild(UIElement *child) {
	child->parent = nullptr;
	std::remove(children.begin(), children.end(), child);
}

int UIElement::uiToScreen(UIValue val) {
	float px = 0;
	px += val.pixels;
	if(parent) px += (int) (val.parentWidth * parent->getBounds().w);
	px += val.screenWidth * kekData.screenWidth;
	if(!parent) px += val.parentWidth * kekData.screenWidth;
	if(parent) px += (int) (val.parentHeight * parent->getBounds().h);
	px += val.screenHeight * kekData.screenHeight;
	if(!parent) px += val.parentHeight * kekData.screenHeight;
	return px;
}

int UIElement::offsetX(int w, Origin origin) {
	switch(origin) {
		case Origin::TOP_LEFT:
		case Origin::BOTTOM_LEFT:
		case Origin::LEFT_CENTER:
			return 0;

		case Origin::TOP_RIGHT:
		case Origin::BOTTOM_RIGHT:
		case Origin::RIGHT_CENTER:
			return -w;

		case Origin::TOP_CENTER:
		case Origin::BOTTOM_CENTER:
		case Origin::CENTER:
			return -w / 2;
	}

	return 0;
}

int UIElement::offsetY(int h, Origin origin) {
	switch(origin) {
		case Origin::TOP_LEFT:
		case Origin::TOP_CENTER:
		case Origin::TOP_RIGHT:
			return 0;

		case Origin::BOTTOM_LEFT:
		case Origin::BOTTOM_CENTER:
		case Origin::BOTTOM_RIGHT:
			return -h;

		case Origin::LEFT_CENTER:
		case Origin::RIGHT_CENTER:
		case Origin::CENTER:
			return -h / 2;
	}

	return 0;
}

UIBounds UIElement::offsetUIBounds(int w, int h, Origin origin) {
	return UIBounds(offsetX(w, origin), offsetY(h, origin), w, h);
}

void UIElement::drawAll(UIPoint screenPos, glm::mat4 projection) {
	if(!visible) return;
	draw(screenPos, projection);

	for(UIElement *child : children) {
		UIPoint pos = child->getPosition();
		child->drawAll(UIPoint(screenPos.x + pos.x, screenPos.y + pos.y), projection);
	}
}

bool UIElement::hoverAll(UIPoint pos, UIPoint screenPos) {
	UIBounds bounds = getBounds();
	if(!bounds.contains(pos)) { // Only used by topmost parent UIElement
		if(hovering) hoverExitAll();
		return false;
	}

	UIElement *hoveredChild = nullptr;
	for(UIElement *child : children) {
		UIPoint childPos = child->getPosition();
		int relX = pos.x - childPos.x;
		int relY = pos.y - childPos.y;
		UIBounds b = child->getBounds();
		if(!b.contains(UIPoint(relX, relY))) continue;
		hoveredChild = child;
	}

	for(UIElement *child : children) {
		if(child != hoveredChild && child->hovering) child->hoverExitAll();
	}

	if(hoveredChild) {
		UIPoint childPos = hoveredChild->getPosition();
		hoveredChild->hoverAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos);
	}

	if(!hovering) hoverEnter(pos, screenPos);
	hovering = true;
	hover(pos, screenPos);
	return true;
}

void UIElement::hoverExitAll() {
	if(!hovering) return;
	hovering = false;
	for(UIElement *child : children) {
		if(child->hovering) child->hoverExitAll();
	}
	hoverExit();
}

bool UIElement::clickAll(UIPoint pos, UIPoint screenPos, GLFWMouseButton button) {
	UIBounds bounds = getBounds();
	if(!bounds.contains(pos)) return false; // Only used by topmost parent UIElement

	UIElement *clickedChild = nullptr;
	for(UIElement *child : children) {
		if(!child->clickable) continue;
		UIPoint childPos = child->getPosition();
		int relX = pos.x - childPos.x;
		int relY = pos.y - childPos.y;
		UIBounds b = child->getBounds();
		if(!b.contains(UIPoint(relX, relY))) continue;
		clickedChild = child;
	}

	if(clickedChild != nullptr) {
		UIPoint childPos = clickedChild->getPosition();
		clickedChild->clickAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos, button);
	}else {
		click(pos, screenPos, button);
	}

	return true;
}

bool UIElement::focusEnterAll(UIPoint pos, UIPoint screenPos) {
	UIBounds bounds = getBounds();
	if(!bounds.contains(pos)) return false;

	UIElement *focusedChild = nullptr;
	for(UIElement *child : children) {
		UIPoint childPos = child->getPosition();
		int relX = pos.x - childPos.x;
		int relY = pos.y - childPos.y;
		UIBounds b = child->getBounds();
		if(!b.contains(UIPoint(relX, relY))) continue;
		focusedChild = child;
	}

	if(focusedChild) {
		UIPoint childPos = focusedChild->getPosition();
		bool focusable = focusedChild->focusEnterAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos);
		if(focusable) return true;
	}

	if(focusable) {
		//UI::focusElement(this); FIXME
		return true;
	}

	return false;
}

UIElement *UIElement::dragEnterAll(UIPoint pos, UIPoint screenPos) {
	UIBounds bounds = getBounds();
	if(!bounds.contains(pos)) return nullptr; // Only used by topmost parent UIElement

	UIElement *draggedChild = nullptr;
	for(UIElement *child : children) {
		UIPoint childPos = child->getPosition();
		int relX = pos.x - childPos.x;
		int relY = pos.y - childPos.y;
		UIBounds b = child->getBounds();
		if(!b.contains(UIPoint(relX, relY))) continue;
		draggedChild = child;
	}

	if(draggedChild) {
		UIPoint childPos = draggedChild->getPosition();
		UIElement *dragged = draggedChild->dragEnterAll(UIPoint(pos.x - childPos.x, pos.y - childPos.y), screenPos);
		if(dragged) return dragged;
	}

	if(draggable) return this;

	return nullptr;
}

void UI::init() {
	kekData.ui = new UIData();
	kekData.ui->defaultFont = new Font(KEK_DEFAULT_FONT);
	kekData.ui->rectangleShader = new Shader("shader/rectangle/vertex.glsl", "shader/rectangle/fragment.glsl");
}

void UI::destroy() {
	delete kekData.ui->defaultFont;
	delete kekData.ui->rectangleShader;
	delete kekData.ui;
}

void UI::addElement(UIElement *element) {
	kekData.ui->elements.push_back(element);
}

void UI::removeElement(UIElement *element) {
	for(auto it = kekData.ui->elements.begin(); it < kekData.ui->elements.end(); it++) {
		if(*it == element) {
			kekData.ui->elements.erase(it);
			delete element;
			break;
		}
	}
}

std::vector<UIElement *> UI::getElements() {
	return kekData.ui->elements;
}

}
