#pragma once

#include <glm/glm.hpp>
#include <string>

#include "fonts.h"
#include "input.h"

#define uiPx(val) UIValue(val, UIUnit::PIXELS)
#define uiPw(val) UIValue(val, UIUnit::PARENT_WIDTH)
#define uiPh(val) UIValue(val, UIUnit::PARENT_HEIGHT)
#define uiSw(val) UIValue(val, UIUnit::SCREEN_WIDTH)
#define uiSh(val) UIValue(val, UIUnit::SCREEN_HEIGHT)

namespace kek {

struct UIPoint {
	int x, y;
	UIPoint(int x, int y): x(x), y(y) {}
};

struct UIBounds {
	int x, y, w, h;

	UIBounds(int x, int y, int w, int h): x(x), y(y), w(w), h(h) {}

	inline bool contains(UIPoint pos) {
		return pos.x > x && pos.y > y && pos.x < x + w && pos.y < y + h;
	}
};

enum class UIElementType {
	WINDOW,
	RECTANGLE,
	TEXT,
	BUTTON,
	TEXTURE,
	TEXT_FIELD,
	SLIDER,
	CHECKBOX,
	GROUP
};

enum class Origin {
	TOP_LEFT,
	TOP_CENTER,
	TOP_RIGHT,
	BOTTOM_LEFT,
	BOTTOM_CENTER,
	BOTTOM_RIGHT,
	LEFT_CENTER,
	RIGHT_CENTER,
	CENTER
};

enum class UIUnit {
	PIXELS,
	PARENT_WIDTH,
	PARENT_HEIGHT,
	SCREEN_WIDTH,
	SCREEN_HEIGHT
};

struct UIValue {
	float pixels = 0;
	float parentWidth = 0;
	float parentHeight = 0;
	float screenWidth = 0;
	float screenHeight = 0;

	constexpr UIValue(float value, UIUnit unit) {
		switch(unit) {
			case UIUnit::PIXELS:
				this->pixels = value;
				break;
			case UIUnit::PARENT_WIDTH:
				this->parentWidth = value;
				break;
			case UIUnit::PARENT_HEIGHT:
				this->parentHeight = value;
				break;
			case UIUnit::SCREEN_WIDTH:
				this->screenWidth = value;
				break;
			case UIUnit::SCREEN_HEIGHT:
				this->screenHeight = value;
				break;
		}
	}

	constexpr UIValue() = default;

	constexpr UIValue &operator+=(const UIValue &rhs);

	constexpr UIValue &operator-=(const UIValue &rhs);

	constexpr friend UIValue operator+(const UIValue &lhs, const UIValue &rhs);

	constexpr friend UIValue operator-(const UIValue &lhs, const UIValue &rhs);

};

class UIElement {

protected:
	UIElement *parent = nullptr;
	std::vector<UIElement *> children;

public:
	UIValue x, y;
	Origin origin = Origin::TOP_LEFT;
	bool clickable = false;
	bool hovering = false;
	bool visible = true;
	bool focusable = false;
	bool draggable = false;

	UIElement(UIValue x, UIValue y);

	virtual ~UIElement();

	virtual UIElementType getType() = 0;

	// Returns the element's position relative to its parent in pixels
	UIPoint getPosition();

	// Returns the element's position on the screen in pixels
	UIPoint getScreenPosition();

	std::vector<UIElement *> getChildren();
	void addChild(UIElement *child);
	void removeChild(UIElement *child);

protected:
	int uiToScreen(UIValue val);
	static int offsetX(int w, Origin origin);
	static int offsetY(int h, Origin origin);
	static UIBounds offsetUIBounds(int w, int h, Origin origin);

public:
	// Returns the bounds of the element relative to its origin
	virtual UIBounds getBounds() = 0;

	void drawAll(UIPoint screenPos, glm::mat4 projection);
	virtual void draw(UIPoint screenPos, glm::mat4 projection) = 0;

	bool hoverAll(UIPoint pos, UIPoint screenPos);
	void hoverExitAll();
	virtual void hoverEnter(UIPoint pos, UIPoint screenPos) {};
	virtual void hover(UIPoint pos, UIPoint screenPos) {};
	virtual void hoverExit() {};

	bool clickAll(UIPoint pos, UIPoint screenPos, GLFWMouseButton button);
	virtual void click(UIPoint pos, UIPoint screenPos, GLFWMouseButton button) {};

	bool focusEnterAll(UIPoint pos, UIPoint screenPos);
	virtual void focusEnter(UIPoint pos, UIPoint screenPos) {};
	virtual void focusExit() {};

	UIElement *dragEnterAll(UIPoint pos, UIPoint screenPos);
	virtual void dragEnter(UIPoint pos, UIPoint screenPos) {};
	virtual void drag(UIPoint pos, UIPoint screenPos) {};
	virtual void dragExit(UIPoint pos, UIPoint screenPos) {};

};

namespace UI {

void init();
void destroy();

std::vector<UIElement *> getElements();
void addElement(UIElement *element);
void removeElement(UIElement *element);

};

}

#include "uielements.h"
