/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "ImGuiPlotTwoLines.hpp"

#include <imgui.h>
#include <imgui_internal.h>

namespace ImGui
{
	namespace
	{
		void RenderLine(ImDrawList* drawList, float (*getter)(void* data, int idx), void* data, int valuesCount, float scaleMin, float scaleMax, ImRect innerBb, ImColor color, int resW)
		{
			const float tStep = 1.0f / (float)resW;
			const float invScale = (scaleMin == scaleMax) ? 0.0f : (1.0f / (scaleMax - scaleMin));

			float v0 = getter(data, 0);
			float t0 = 0.0f;
			ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scaleMin) * invScale));

			for (int n = 0; n < resW; n++)
			{
				const float t1 = t0 + tStep;
				const int v1Idx = (int)(t0 * (valuesCount - 1) + 0.5f);
				IM_ASSERT(v1Idx >= 0 && v1Idx < valuesCount);
				const float v1 = getter(data, ImMin(v1Idx + 1, valuesCount - 1));
				const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scaleMin) * invScale) );

				ImVec2 pos0 = ImLerp(innerBb.Min, innerBb.Max, tp0);
				ImVec2 pos1 = ImLerp(innerBb.Min, innerBb.Max, tp1);

				drawList->AddLine(pos0, pos1, color);

				t0 = t1;
				tp0 = tp1;
			}
		}
	}

	void PlotTwoLines(const char* label,
		float (*getter1)(void* data, int idx), void* data1, const char *tooltip1,
		float (*getter2)(void* data, int idx), void* data2, const char *tooltip2,
		int valuesCount, float scaleMin, float scaleMax, ImVec2 graphSize, ImColor color1, ImColor color2)
	{
		ImGuiWindow* window = GetCurrentWindow();
		if (window->SkipItems)
			return;

		const ImGuiStyle& style = ImGui::GetCurrentContext()->Style;
		const ImGuiID id = window->GetID(label);

		const ImVec2 labelSize = CalcTextSize(label, NULL, true);
		const ImVec2 frameSize = CalcItemSize(graphSize, CalcItemWidth(), labelSize.y + style.FramePadding.y * 2.0f);

		const ImRect frameBb(window->DC.CursorPos, ImVec2(window->DC.CursorPos.x + frameSize.x, window->DC.CursorPos.y + frameSize.y));
		const ImRect innerBb(ImVec2(frameBb.Min.x + style.FramePadding.x, frameBb.Min.y + style.FramePadding.y), ImVec2(frameBb.Max.x - style.FramePadding.x, frameBb.Max.y - style.FramePadding.y));
		const ImRect totalBb(frameBb.Min, ImVec2(frameBb.Max.x + (labelSize.x > 0.0f ? (style.ItemInnerSpacing.x + labelSize.x) : 0.0f), frameBb.Max.y));

		ItemSize(totalBb, style.FramePadding.y);
		if (!ItemAdd(totalBb, 0, &frameBb))
			return;
		const bool hovered = ItemHoverable(frameBb, id, ImGui::GetCurrentContext()->LastItemData.InFlags);

		// Determine scale from values if not specified
		if(scaleMin == FLT_MAX || scaleMax == FLT_MAX)
		{
			float vMin = FLT_MAX;
			float vMax = -FLT_MAX;
			for(int i = 0; i < valuesCount; i++)
			{
				const float v1 = getter1(data1, i);
				const float v2 = getter2(data2, i);
				if(v1 != v1 || v2 != v2) // Ignore NaN values
					continue;
				vMin = ImMin(ImMin(vMin, v1), v2);
				vMax = ImMax(ImMax(vMax, v1), v2);
			}
			if(scaleMin == FLT_MAX)
				scaleMin = vMin;
			if(scaleMax == FLT_MAX)
				scaleMax = vMax;
		}

		RenderFrame(frameBb.Min, frameBb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

		if(valuesCount >= 2)
		{
			int resW = ImMin((int)frameSize.x, valuesCount) - 1;
			int itemCount = valuesCount - 1;

			// Tooltip on hover
			if(hovered && innerBb.Contains(ImGui::GetCurrentContext()->IO.MousePos))
			{
				const float t = ImClamp((ImGui::GetCurrentContext()->IO.MousePos.x - innerBb.Min.x) / (innerBb.Max.x - innerBb.Min.x), 0.0f, 0.9999f);
				const int vIdx = (int)(t * itemCount);
				IM_ASSERT(vIdx >= 0 && vIdx < valuesCount);

				const float v0 = getter1(data1, vIdx % valuesCount);
				const float v1 = getter2(data2, (vIdx + 1) % valuesCount);
				SetTooltip("%s: %8.4g\n%s: %8.4g", tooltip1, v0, tooltip2, v1);
			}

			RenderLine(window->DrawList, getter1, data1, valuesCount, scaleMin, scaleMax, innerBb, color1, resW);
			RenderLine(window->DrawList, getter2, data2, valuesCount, scaleMin, scaleMax, innerBb, color2, resW);
		}

		if(labelSize.x > 0.0f)
			RenderText(ImVec2(frameBb.Max.x + style.ItemInnerSpacing.x, innerBb.Min.y), label);
	}
}