#pragma once

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

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

#define px(val) UIValue(val, UIUnit::PIXELS)
#define pw(val) UIValue(val, UIUnit::PARENT_WIDTH)
#define ph(val) UIValue(val, UIUnit::PARENT_HEIGHT)
#define sw(val) UIValue(val, UIUnit::SCREEN_WIDTH)
#define sh(val) UIValue(val, UIUnit::SCREEN_HEIGHT)

namespace kek {

struct UIBounds {
	int x, y, w, h;
	UIBounds(int x, int y, int w, int h) {
		this->x = x;
		this->y = y;
		this->w = w;
		this->h = h;
	}
};

struct UIPoint {
	int x, y;
	UIPoint(int x, int y) {
		this->x = x;
		this->y = y;
	}
};

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

enum class TextMode {
	// Draw with y as baseline, disables vertical offset from the origin position specified using setOrigin()
	BASELINE,
	// Draw from the set origin
	ORIGIN
};

enum class TextBounds {
	SMALLEST,
	LINE
};

enum class SliderDirection {
	HORIZONTAL,
	VERTICAL
};

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:
	static Font *defaultFont;

	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();

	void addChild(UIElement *child);

	void removeChild(UIElement *child);

protected:
	inline int uiToScreen(UIValue val);

public:
	// Returns the bounds of the element relative to its origin (as returned by getX() and getY())
	virtual UIBounds getBounds() = 0;

	void drawAll(UIPoint screenPos, glm::mat4 projection);

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

	void hoverAll(UIPoint pos, UIPoint screenPos);

	void hoverExitAll();

	virtual void hoverEnter(UIPoint pos, UIPoint screenPos) {};

	virtual void hover(UIPoint pos, UIPoint screenPos) {};

	virtual void hoverExit() {};

	void clickAll(UIPoint pos, UIPoint screenPos, GLFWMouseButton button);

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

	bool focusEnterAll(UIPoint pos);

	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) {};

	static void init();

};

}
