#include "pch.h"

#include "engine/EditorNew.h"
#include "engine/Engine.h"


RampEdit::RampEdit() {
	// curve_points[0][0] = ImVec2(-10.f, 0);
	// curve_points[0][1] = ImVec2(20.f, 0.6f);
	// curve_points[0][2] = ImVec2(25.f, 0.2f);
	// curve_points[0][3] = ImVec2(70.f, 0.4f);
	// curve_points[0][4] = ImVec2(120.f, 1.f);
	// curve_point_counts[0] = 5;
	//
	// curve_points[1][0] = ImVec2(-50.f, 0.2f);
	// curve_points[1][1] = ImVec2(33.f, 0.7f);
	// curve_points[1][2] = ImVec2(80.f, 0.2f);
	// curve_points[1][3] = ImVec2(82.f, 0.8f);
	// curve_point_counts[1] = 4;
	//
	//
	// curve_points[2][0] = ImVec2(40.f, 0);
	// curve_points[2][1] = ImVec2(60.f, 0.1f);
	// curve_points[2][2] = ImVec2(90.f, 0.82f);
	// curve_points[2][3] = ImVec2(150.f, 0.24f);
	// curve_points[2][4] = ImVec2(200.f, 0.34f);
	// curve_points[2][5] = ImVec2(250.f, 0.12f);
	// curve_point_counts[2] = 6;
	// curve_is_visibles[0] = curve_is_visibles[1] = curve_is_visibles[2] = true;
	// mMax = ImVec2(1.f, 1.f);
	// mMin = ImVec2(0.f, 0.f);
}
size_t RampEdit::GetCurveCount() {
	return this->total_curve_count;
}

bool RampEdit::IsVisible(size_t curveIndex) {
	return curve_is_visibles[curveIndex];
}

size_t RampEdit::GetPointCount(size_t curveIndex) {
	return curve_point_counts[curveIndex];
}
#include <iomanip>

// Utility functions
inline double clamp(double x, double minVal, double maxVal) {
	return std::max(minVal, std::min(x, maxVal));
}

inline double labToXyz(double t) {
	return (t > 0.206893034) ? (t * t * t) : (t - 0.137931034) * 0.12841855;
}

inline double xyzToRgb(double t) {
	return (t > 0.0031308) ? (1.055 * pow(t, 1.0 / 2.4) - 0.055) : 12.92 * t;
}

// OKLCH to OKLab
void oklchToOklab(double L, double C, double H, double &labL, double &labA, double &labB) {
	constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
	double hRad = H * pi / 180.0;
	labL = L;
	labA = C * cos(hRad);
	labB = C * sin(hRad);
}

// OKLab to linear sRGB
void oklabToLinearSRGB(double L, double a, double b, double &r, double &g, double &bl) {
	double l_ = L + 0.3963377774 * a + 0.2158037573 * b;
	double m_ = L - 0.1055613458 * a - 0.0638541728 * b;
	double s_ = L - 0.0894841775 * a - 1.2914855480 * b;

	double l = l_ * l_ * l_;
	double m = m_ * m_ * m_;
	double s = s_ * s_ * s_;

	r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
	g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
	bl = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
}

// Linear sRGB to sRGB
void linearSRGBToSRGB(double &r, double &g, double &bl) {
	r = clamp(xyzToRgb(r), 0.0, 1.0);
	g = clamp(xyzToRgb(g), 0.0, 1.0);
	bl = clamp(xyzToRgb(bl), 0.0, 1.0);
}

// Convert sRGB to Hex Number
unsigned int srgbToHexNumber(double r, double g, double bl) {
	int ir = static_cast<int>(round(r * 255.0));
	int ig = static_cast<int>(round(g * 255.0));
	int ib = static_cast<int>(round(bl * 255.0));

	return (0xFF << 24) + (ir << 16) + (ig << 8) + ib;
}

// Main function to convert OKLCH to Hex
unsigned int oklchToHex(double L, double C, double H) {
	double labL, labA, labB;
	double r, g, bl;

	oklchToOklab(L, C, H, labL, labA, labB);
	oklabToLinearSRGB(labL, labA, labB, r, g, bl);
	linearSRGBToSRGB(r, g, bl);

	return srgbToHexNumber(r, g, bl);
}

float seededRNG(int seed) {
	// Wang Hash function
	uint32_t hash = static_cast<uint32_t>(seed);
	hash = (hash ^ 61) ^ (hash >> 16);
	hash = hash + (hash << 3);
	hash = hash ^ (hash >> 4);
	hash = hash * 0x27d4eb2d;
	hash = hash ^ (hash >> 15);
    
	// Convert hashed integer to float in [0, 1)
	return (hash & 0xFFFFFF) / static_cast<float>(0xFFFFFF + 1);
}



uint32_t RampEdit::GetCurveColor(size_t curveIndex) {
	uint32_t curve_cols[] = { 0xFF0000FF, 0xFF00FF00, 0xFFFF0000 };

	return oklchToHex(0.7, 0.5, float(curveIndex)*10000);
	return curve_cols[curveIndex%3];
}

CurvePoint* RampEdit::GetPoints(size_t curveIndex) {
	return curve_points[curveIndex];
}

ImCurveEdit::CurveType RampEdit::GetCurveType(size_t curveIndex) const { return ImCurveEdit::CurveSmooth; }

int RampEdit::EditPoint(size_t curveIndex, int pointIndex, ImVec2 value) {
	value.y = glm::clamp(value.y,0.0f, 1.0f);
	curve_points[curveIndex][pointIndex].point = ImVec2(value.x, value.y);
	SortValues(curveIndex);
	for (size_t i = 0; i < GetPointCount(curveIndex); i++) {
		// if (curve_points[curveIndex][i].point.x == value.x)
		if (glm::abs(curve_points[curveIndex][i].point.x - value.x) < 0.0000001f)
			return (int)i;
	}
	return pointIndex;
}
void RampEdit::AddPoint(size_t curveIndex, ImVec2 value) {
	// if (curve_point_counts[curveIndex] >= 8)
	// 	return;
	
	int idx = curve_point_counts[curveIndex]++;
	curve_points[curveIndex][idx].point = value;
	curve_points[curveIndex][idx].handles = ImVec4(-1,0,1,0);
	SortValues(curveIndex);

	int pt_idx = 0;
	for (size_t i = 0; i < GetPointCount(curveIndex); i++) {
		// if (curve_points[curveIndex][i].point.x == value.x)
		if (
			glm::abs(curve_points[curveIndex][i].point.x - value.x) < 0.0000001f &&
			glm::abs(curve_points[curveIndex][i].point.y - value.y) < 0.0000001f
		){
			pt_idx = i;
			break;
		}
			// return (int)i;
	}
	
	int pt_count = curve_point_counts[curveIndex];
	
	auto next_pt = curve_points[curveIndex][min(pt_idx, pt_count - 1)];
	auto prev_pt = curve_points[curveIndex][max(pt_idx - 1, 0)];

	glm::vec2 next_pt_pos = glm::vec2(next_pt.point.x, next_pt.point.y);
	glm::vec2 prev_pt_pos = glm::vec2(prev_pt.point.x, prev_pt.point.y);

	float handle_scale = 0.25;
	glm::vec2 handle_a = -(next_pt_pos - prev_pt_pos) * handle_scale;
	glm::vec2 handle_b = -(prev_pt_pos - next_pt_pos) * handle_scale;

	curve_points[curveIndex][pt_idx].handles.x = handle_a.x;
	curve_points[curveIndex][pt_idx].handles.y = handle_a.y;
	
	curve_points[curveIndex][pt_idx].handles.z = handle_b.x;
	curve_points[curveIndex][pt_idx].handles.w = handle_b.y;
}

void RampEdit::DeletePoint(size_t curveIndex, int pointIndex) {
	curve_points[curveIndex][pointIndex].point.x = 10000000000;
	SortValues(curveIndex);
	bool done = false;
	for(Lane& lane : WEngine->editor->timeline.lanes) {
		for(Curve& curve : lane.curves) {
			if(curve.curve_idx == curveIndex) {
				(*curve.curve_point_count)--;
				done = true;
				break;
			}
		}
		if(done) {
			break;
		}
	}
	
}
ImVec2& RampEdit::GetMax() { return mMax; }
ImVec2& RampEdit::GetMin() { return mMin; }

unsigned RampEdit::GetBackgroundColor() { return 0; }

void RampEdit::SortValues(size_t curveIndex) {
	CurvePoint* b = std::begin(curve_points[curveIndex]);
	CurvePoint* e = std::begin(curve_points[curveIndex]) + GetPointCount(curveIndex);
	std::sort(b, e, [](CurvePoint a, CurvePoint b) { return a.point.x < b.point.x; });
}

int Timeline::GetFrameMin() const {
	return mFrameMin;
}
int Timeline::GetFrameMax() const {
	return mFrameMax;
}
int Timeline::GetLanesCount() const { return (int)lanes.size(); }

// int Timeline::GetItemTypeCount() const { return sizeof(SequencerItemTypeNames) / sizeof(char*); }
int Timeline::GetItemTypeCount() const { return GetLanesCount(); }

// const char* Timeline::GetItemTypeName(int lane_index) const { return SequencerItemTypeNames[lane_index]; }
const char* Timeline::GetItemTypeName(int lane_index) const { return &lanes[lane_index].name[0]; }

const char* Timeline::GetLaneLabel(int index) const {
	static char tmps[512];
	snprintf(tmps, 512, "[%02d] %s", index, GetItemTypeName(index));
	return tmps;
}

void Timeline::GetSequence(int index, int** start, int** end, int* type, unsigned* color) {
	Lane& item = lanes[index];
	if (color)
		*color = 0xFFAA8080; // same color for everyone, return color based on type
	if (start)
		*start = &item.mFrameStart;
	if (end)
		*end = &item.mFrameEnd;
	if (type)
		*type = item.mType;
}

void Timeline::AddSequence(int type) { lanes.push_back(Lane{ type, 0, 10, false }); }
Lane* Timeline::AddSequence(std::string_view lane_name) {
	this->lanes.emplace_back(
		Lane{
			0,
			0, 
			30,
			false
		}
	);
	Lane* lane = &this->lanes[this->lanes.size() - 1];
	lane->timeline = this;
	lane->name = lane_name;
	return lane;
}

void Timeline::DeleteSequence(int index) { lanes.erase(lanes.begin() + index); }

void Timeline::DuplicateSequence(int index) { lanes.push_back(lanes[index]); }

size_t Timeline::GetCustomHeight(int index) { return lanes[index].mExpanded ? 300 : 0; }

Timeline::Timeline(): mFrameMin(0), mFrameMax(0) {}

void Timeline::deserialize_from_json() {
	using nlohmann::json;

	if(!std::filesystem::exists(this->json_path)) {
		// TODO: Error
		return;
	} else {
		std::ifstream json_file(this->json_path);
		json json_data;
		json_file >> json_data;
		json_file.close();

		this->bpm = json_data["bpm"].get<float>();
		
		json json_lanes = json_data["lanes"];

		for(const auto& json_lane : json_lanes) {
			std::string json_lane_name = json_lane["name"].get<std::string>();
			nlohmann::basic_json<> json_lane_curves = json_lane["curves"];

			for(Lane& lane : this->lanes) {
				if(lane.name == json_lane_name){
					for(Curve& curve : lane.curves) {
						for(const auto& json_curve : json_lane_curves) {
							std::string json_curve_name = json_curve["name"].get<std::string>();
							bool json_curve_visible = json_curve["visible"].get<bool>();
							
							if(*curve.curve_name == json_curve_name) {
								int idx_pt = 0;
								for(const auto& json_curve_key : json_curve["keys"]) {
									float key_point_x = json_curve_key["pos"][0].get<float>();
									float key_point_y = json_curve_key["pos"][1].get<float>();

									float handle_point_a = - 1;
									float handle_point_b = 0;
									float handle_point_c = 1;
									float handle_point_d = 0;

									if(json_curve_key.contains("handles")) {
										auto handles = json_curve_key["handles"];
										handle_point_a = handles[0].get<float>();
										handle_point_b = handles[1].get<float>();
										handle_point_c = handles[2].get<float>();
										handle_point_d = handles[3].get<float>();
									}
									
									ImVec2 key_point = ImVec2(key_point_x, key_point_y);
									ImVec4 key_handles = ImVec4(
										handle_point_a, handle_point_b, handle_point_c, handle_point_d
									);
									curve.curve_points[idx_pt].point = key_point;
									curve.curve_points[idx_pt].handles = key_handles;
									idx_pt++;
								}
								*curve.curve_point_count = idx_pt;
								*curve.curve_is_visible = json_curve_visible;
								break;
							}
						}
					}
				}
				// break;
			}
		}
	}
	
	
}

void Timeline::serialize_to_json() {
	using nlohmann::json;
	
	// json::array timelines();
	json data = {};
	data["bpm"] = 120;

	json json_lanes;
	for(Lane& lane : this->lanes) {
		json json_lane;
		json_lane["name"] = std::string(lane.name.c_str());
		
		json json_lane_curves;
		for(Curve& curve : lane.curves) {
			json json_curve;
			json_curve["visible"] = *curve.curve_is_visible;
			json_curve["name"] = std::string(curve.curve_name->c_str());
			
			json json_curve_keys;
			for(int key_idx = 0; key_idx < *curve.curve_point_count; key_idx++) {
				ImVec2 key_point = (curve.curve_points + key_idx)->point;
				ImVec4 key_handle = (curve.curve_points + key_idx)->handles;
				
				json json_key;
				json_key["pos"] = {key_point.x, key_point.y};
				json_key["handles"] = {key_handle.x, key_handle.y, key_handle.z, key_handle.w};
				
				json_curve_keys.push_back(json_key);
			}
			json_curve["keys"] = json_curve_keys;
			json_lane_curves.push_back(json_curve);
		}
		json_lane["curves"] = json_lane_curves;
		json_lanes.push_back(json_lane);
	}
	
	data["lanes"] = json_lanes;

	std::ofstream file(this->json_path);
	file << data.dump(4); // Pretty print with 4 spaces indentation
	file.close();
}

void Timeline::set_json_path(std::string_view path_string) {
	this->json_path = std::string("src/assets/timelines/") + std::string(path_string);
	this->deserialize_from_json();
	// if(std::filesystem::exists(this->json_path)) {
	// 	
	// } else {
	// 	
	// }
}

void Lane::addCurve() {
	// timeline->rampEdit.
}

Curve Lane::AddCurve(std::string_view name) {
	int curve_idx = timeline->rampEdit.total_curve_count++;
	
	// Error: Call set_json_path() after Curve/Lane init.
	assert(this->timeline->json_path == "");

	Curve curve = Curve{
		.curve_points = &timeline->rampEdit.curve_points[curve_idx][0],
		// .curve_point_handles = &timeline->rampEdit.curve_point_handles[curve_idx][0],
		.curve_name = &timeline->rampEdit.curve_names[curve_idx],
		.curve_point_count = &timeline->rampEdit.curve_point_counts[curve_idx],
		.curve_is_visible = &timeline->rampEdit.curve_is_visibles[curve_idx],
		.curve_idx = curve_idx,
		.colour = WEngine->editor->timeline.rampEdit.GetCurveColor(curve_idx),
	};
	*curve.curve_name = name;
	*curve.curve_point_count = 2;
	curve.curve_points[0].point = ImVec2(0.5f,0.7f);
	curve.curve_points[0].handles = ImVec4(0.5f - 0.1,0.7f, 0.5f + 0.1,0.7f);
	curve.curve_points[1].point = ImVec2(10.2f,.2f);
	curve.curve_points[1].handles = ImVec4(10.2f - 0.1,.2f, 10.2f + 0.1,.2f);
	*curve.curve_is_visible = true;
	this->curve_indices.push_back(curve_idx);
	this->curves.push_back(curve);
	return curve;
}

void Timeline::DoubleClick(int index) {
	if (lanes[index].mExpanded) {
		lanes[index].mExpanded = false;
	} else {
		for (auto& item : lanes) {
			item.mExpanded = false;
		}
		lanes[index].mExpanded = !lanes[index].mExpanded;
	}
}

void Timeline::CustomDraw(
	int lane_index,
	ImDrawList* draw_list,
	const ImRect& rc,
	const ImRect& legendRect,
	const ImRect& clippingRect,
	const ImRect& legendClippingRect
) {
	rampEdit.mMax = ImVec2(float(mFrameMax), 1.f);
	rampEdit.mMin = ImVec2(float(mFrameMin), 0.f);
	// rampEdit.mMin.x = -10;
	// rampEdit.mMax.x = 0;
	draw_list->PushClipRect(legendClippingRect.Min, legendClippingRect.Max, true);
	
	// for (int i = 0; i < 3; i++) {
	Lane* lane = &this->lanes[lane_index];

	// if (ImRect(legendClippingRect.Min, legendClippingRect.Max).Contains(WEngine->editor->imgui_io->MousePos) && WEngine->editor->imgui_io->MouseDoubleClicked[0]) {
	// this->lanes

	if (legendClippingRect.Contains(WEngine->editor->imgui_io->MousePos) && WEngine->editor->imgui_io->MouseDoubleClicked[0]) {
		this->DoubleClick(lane_index);
	}
	
	// ----- DRAW CURVE NAMES ---- //
	int i_curve_name = 0;
	ImVec2 pta;
	ImVec2 ptb;
	for (Curve& curve : lane->curves) {
		pta = ImVec2(legendRect.Min.x + 10, legendRect.Min.y + i_curve_name * 14.f);
		ptb = ImVec2(legendRect.Max.x, legendRect.Min.y + (i_curve_name + 1) * 14.f);
		// this->rampEdit.
		// draw_list->AddText(pta, rampEdit.curve_is_visibles[curve.curve_idx] ? 0xFFFFFFFF : 0x80FFFFFF, curve.curve_name->data());
		uint32_t curve_col = curve.colour;
		uint32_t curve_col_faded = (curve.colour & 0x00FFFFFF) | 0x80000000;
		draw_list->AddText(pta, rampEdit.curve_is_visibles[curve.curve_idx] ? curve_col : curve_col_faded, curve.curve_name->data());
		if (ImRect(pta, ptb).Contains(ImGui::GetMousePos()) && ImGui::IsMouseClicked(0))
			rampEdit.curve_is_visibles[curve.curve_idx] = !rampEdit.curve_is_visibles[curve.curve_idx];
		i_curve_name++;
	}
	static ImVector<ImCurveEdit::EditPoint> temp_selected_points;

	pta.y += 30;
	// for(ImCurveEdit::EditPoint& p : temp_selected_points) {
	// 	pta.y += 20;
	// 	draw_list->AddText(pta, 0xFFFFFFFF, "sel pt:");
	// 	// p.curveIndex
	// 	// this->lanes[]
	// 	CurvePoint& pt = lane->curves[p.curveIndex].curve_points[p.pointIndex];
	// 	
	// 	pta.y += 10;
	// 	// pt.point
	// 	// draw_list->AddText(pta, 0xFFFFFFFF, "sel pt:");
	// 	// ImGui::SetCursorPos();
	// 	// ImVec2 offs = legendClippingRect.Min;
	// 	// ImVec2 offs = pta;
	// 	// offs += pta;
	// 	// ImGui::SetCursorPos(offs);
	// 	// ImGui::PushStyleColor(ImGuiCol_Text, 0x88FFFFFF);
	// 	ImGui::SetCursorScreenPos(pta);
	// 	ImGui::Text("x: %.001f", pt.point.x/10.0f);
	// 	pta.y += 10;
	// 	ImGui::SetCursorScreenPos(pta);
	// 	ImGui::Text("y: %.001f", pt.point.y);
	// 	pta.y += 10;
	// 	pta.y += 10;
	// 	
	// 	ImGui::SetCursorScreenPos(pta);
	// 	ImGui::Text("hnd a:");
	// 	pta.y += 10;
	// 	ImGui::SetCursorScreenPos(pta);
	// 	ImGui::Text("x: %.001f", pt.handles.x/10.0f);
	// 	pta.y += 10;
	// 	ImGui::SetCursorScreenPos(pta);
	// 	ImGui::Text("y: %.001f", pt.handles.y/10.0f);
	// 	
	// 	// ImGui::PopStyleColor(ImGuiCol_Text);
	// 	
	// 	// ImGui::InputFloat2("pos", &pt.point[0]);
	// 	// ImGui::DragFloat2("pos", &pt.point[0]);
	// }
	draw_list->PopClipRect();

	
	ImGui::SetCursorScreenPos(rc.Min);
	// ImCurveEdit::Edit(rampEdit, rc.Max - rc.Min, 137 + index, &clippingRect);
	
	// ----- DISPLAY CURVE ---- //
	
	ImCurveEdit::EditAndDisplayCurve(
		rampEdit, 
		ImVec2(rc.Max.x - rc.Min.x, rc.Max.y - rc.Min.y),
		137 + lane_index,
		&clippingRect,
		&temp_selected_points,
		lane
	);
	if(temp_selected_points.size() > 0) {
		this->selected_points.resize(temp_selected_points.size());
		
		for(int i = 0; i < temp_selected_points.size(); i++) {
			this->selected_points[i] = temp_selected_points[i];
		}
	}
}

void Timeline::CustomDrawCompact(int index, ImDrawList* draw_list, const ImRect& rc, const ImRect& clippingRect) {
	rampEdit.mMax = ImVec2(float(mFrameMax), 1.f);
	rampEdit.mMin = ImVec2(float(mFrameMin), 0.f);
	draw_list->PushClipRect(clippingRect.Min, clippingRect.Max, true);
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < rampEdit.curve_point_counts[i]; j++) {
			float p = rampEdit.curve_points[i][j].point.x;
			if (p < lanes[index].mFrameStart || p > lanes[index].mFrameEnd)
				continue;
			float r = (p - mFrameMin) / float(mFrameMax - mFrameMin);
			float x = ImLerp(rc.Min.x, rc.Max.x, r);
			draw_list->AddLine(ImVec2(x, rc.Min.y + 6), ImVec2(x, rc.Max.y - 4), 0xAA000000, 4.f);
		}
	}
	draw_list->PopClipRect();
}

float Curve::get_value(float t) {
	ImVec2 curve_val = WEngine->editor->timeline.rampEdit.GetCurvePointAtTime(
		this->curve_idx,
		t
	);
	return curve_val.y;
}

