//Disables console window
#if !_DEBUG
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#endif

#include <iostream>
#include <algorithm>
#include "ImguiBase.h"
#include "ImguiNodes.h"

int main()
{
	ApplicationData applicationData{};
	ImGuiCallbacks callbacks{};

	callbacks.initFunc = std::bind(init, std::placeholders::_1, applicationData);
	callbacks.drawFunc = std::bind(draw, std::placeholders::_1, applicationData);
	startImgui(callbacks, "Node Test", 1280, 720);
}

/// <summary>
/// Setup before first draw.
/// </summary>
void init(DrawData& drawData, ApplicationData& customData)
{
	ImGuiIO& io = ImGui::GetIO();
	io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f);
}

/// <summary>
/// Draw main application content.
/// </summary>
void draw(DrawData& drawData, ApplicationData& applicationData)
{
	// Top menu bar
	ImGui::BeginMainMenuBar();
	if (ImGui::Button("Add Node"))
	{
		std::string name{ "Node " };
		name.append(std::to_string(applicationData.nodes.size() + 1));
		applicationData.nodes.push_back(NodeWindow(name));
	}
	ImGui::EndMainMenuBar();

	// Draw all node windows
	bool isDraggingLine = false;
	ImVec2 lineStartPos = ImVec2{};

	auto nodeIterator = applicationData.nodes.begin();
	while (nodeIterator != applicationData.nodes.end())
	{
		NodeWindow& node = *nodeIterator;

		bool nodeOpen;
		ImGui::Begin(node.title.c_str(), &nodeOpen, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize);
		{
			if (!nodeOpen)
			{
				cleanEreaseNodeElements(*nodeIterator, applicationData);
				nodeIterator = applicationData.nodes.erase(nodeIterator);
				ImGui::End();
				break; // TODO: contine creates bug with closing too many windows???
			}

			auto alertIterator = node.alerts.begin();
			while (alertIterator != node.alerts.end())
			{
				ImGui::Text(alertIterator->name.c_str());
				if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
				{
					if (inlineButton("x", alertIterator._Ptr))
					{
						alertIterator = cleanEraseElement(node.alerts, alertIterator, applicationData);
						continue;
					}
				}
				else
				{
					invisibleInlineButton("x", "x");
				}

				ImGui::SameLine();
				drawCircle(&alertIterator->position);

				float lineHeight = ImGui::GetTextLineHeight();
				ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight };
				invisibleDragArea(alertIterator._Ptr, dragAreaSize);
				if (ImGui::BeginDragDropSource())
				{
					isDraggingLine = true;
					lineStartPos = alertIterator->position;
					ImGui::Text(alertIterator->name.c_str());

					ConnectionPayload payload{ alertIterator->id };

					ImGui::SetDragDropPayload("NODE_CONNECTION", (void*)&payload, sizeof(ConnectionPayload));
					ImGui::EndDragDropSource();
				}

				alertIterator++;
			}

			auto triggerIterator = node.triggers.begin();
			while (triggerIterator != node.triggers.end())
			{
				drawCircle(&triggerIterator->position);

				float lineHeight = ImGui::GetTextLineHeight();
				ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight };
				invisibleDragArea(triggerIterator._Ptr, dragAreaSize);
				if (ImGui::BeginDragDropTarget())
				{
					if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION"))
					{
						ConnectionPayload* payloadData = static_cast<ConnectionPayload*>(payload->Data);
						applicationData.addConnection(payloadData->sourceID, triggerIterator->id);
					}
					ImGui::EndDragDropTarget();
				}

				ImGui::SameLine();
				ImGui::Text(triggerIterator->name.c_str());
				if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
				{
					if (inlineButton("x", triggerIterator._Ptr))
					{
						triggerIterator = cleanEraseElement(node.triggers, triggerIterator, applicationData);
						continue;
					}
				}
				triggerIterator++;
			}

			if (ImGui::Button("New Alert"))
			{
				node.alerts.push_back(NodeElement("Today, 18:00"));
			}
			if (ImGui::Button("New Trigger"))
			{
				node.triggers.push_back(NodeElement("Doot"));
			}
		}
		ImGui::End();

		nodeIterator++;
	}

	// Draw drag line in foreground
	if (isDraggingLine)
	{
		ImDrawList* drawList = ImGui::GetForegroundDrawList();
		drawList->AddLine(lineStartPos, ImGui::GetMousePos(), IM_COL32(200, 200, 200, 255), 3.0f);
	}

	// Draw connected lines in background
	ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
	for (NodeConnection& nodeConnection : applicationData.connections)
	{
		ImVec2 sourcePos = ImVec2{ 0, 0 };
		ImVec2 targetPos = ImVec2{ 0, 0 };
		for (auto node : applicationData.nodes)
		{
			for (auto alert : node.alerts)
			{
				if (alert.id == nodeConnection.sourceID)
				{
					targetPos = alert.position;
				}
			}
			for (auto trigger : node.triggers)
			{
				if (trigger.id == nodeConnection.targetID)
				{
					sourcePos = trigger.position;
				}
			}
		}
		bgDrawList->AddLine(sourcePos, targetPos, IM_COL32_WHITE, 2.0f);
	}
}

/// <summary>
/// Creates an invisible button that does nothing, but can be used for drag/drop operations.
/// </summary>
void invisibleDragArea(const void* id, ImVec2& size)
{
	ImGui::PushID(id);
	ImGui::InvisibleButton("", size);
	ImGui::PopID();
}

/// <summary>
/// Draws a small circle at the current cursor position.
/// </summary>
/// <param name="outCircleCenter">Returns the center position of the circle.</param>
void drawCircle(ImVec2* outCircleCenter = nullptr)
{
	float lineHeight = ImGui::GetTextLineHeight();
	float circleRadius = (lineHeight / 2.) * 0.75;
	float circleOffset = lineHeight / 2.;

	ImVec2 cursor = ImGui::GetCursorScreenPos();
	ImVec2 circlePos = ImVec2{ cursor.x + circleOffset, cursor.y + circleOffset };

	ImDrawList* drawList = ImGui::GetWindowDrawList();
	drawList->AddCircleFilled(circlePos, circleRadius, IM_COL32_WHITE);

	if (outCircleCenter != nullptr)
	{
		*outCircleCenter = circlePos;
	}
}

/// <summary>
/// Draws a small button in the same row.
/// </summary>
bool inlineButton(const char* title, const void* id)
{
	ImGui::SameLine();
	ImGui::PushID(id);

	bool result = ImGui::SmallButton(title);

	ImGui::PopID();
	return result;
}

bool invisibleInlineButton(const char* title, const char* id)
{
	ImGui::SameLine();
	ImVec2 padding = ImGui::GetStyle().FramePadding;
	ImVec2 size = ImGui::CalcTextSize(title);
	size.x += padding.x * 2.;
	return ImGui::InvisibleButton(id, size);
}

/// <summary>
/// Removes any attached connections before erasing this NodeElement from a vector.
/// </summary>
/// <returns>Continued iterator</returns>
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, ApplicationData& data)
{
	auto connectionIterator = data.connections.begin();
	while (connectionIterator != data.connections.end())
	{
		if (connectionIterator->sourceID == toErase->id || connectionIterator->targetID == toErase->id)
		{
			connectionIterator = data.connections.erase(connectionIterator);
			continue;
		}
		connectionIterator++;
	}
	return elements.erase(toErase);
}

void cleanEreaseNodeElements(NodeWindow& node, ApplicationData& data)
{
	auto triggerIterator = node.triggers.begin();
	while (triggerIterator != node.triggers.end())
	{
		triggerIterator = cleanEraseElement(node.triggers, triggerIterator, data);
	}
	auto alertIterator = node.alerts.begin();
	while (alertIterator != node.alerts.end())
	{
		alertIterator = cleanEraseElement(node.alerts, alertIterator, data);
	}
}

NodeElement::NodeElement(std::string name) : name(name)
{
	this->id = globalNodeIdCounter;
	globalNodeIdCounter++;
}

NodeWindow::NodeWindow(std::string title) : title(title)
{
}

void ApplicationData::addConnection(int sourceID, int targetID)
{
	// Remove duplicate connections
	auto connectionIterator = connections.begin();
	while (connectionIterator != connections.end())
	{
		// Currently only remove on exact match, maybe add some other constraints later
		if (connectionIterator->sourceID == sourceID && connectionIterator->targetID == targetID)
		{
			connectionIterator = connections.erase(connectionIterator);
			continue;
		}

		connectionIterator++;
	}

	// Add new connection
	connections.push_back(NodeConnection{ sourceID, targetID });
}
