/*
 * This source file is part of RmlUi, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://github.com/mikke89/RmlUi
 *
 * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
 * Copyright (c) 2019-2023 The RmlUi Team, and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "../Common/TestsShell.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/DataModelHandle.h>
#include <RmlUi/Core/Element.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/Elements/ElementFormControlSelect.h>
#include <RmlUi/Core/EventListener.h>
#include <doctest.h>

using namespace Rml;

static const String basic_doc_rml = R"(
<rml>
<head>
	<title>Test</title>
	<link type="text/rcss" href="/assets/rml.rcss"/>
	<link type="text/rcss" href="/assets/invader.rcss"/>
</head>
<body/>
</rml>
)";

static const String data_model_doc_rml_pre = R"(
<rml>
<head>
	<title>Test</title>
	<link type="text/template" href="/assets/window.rml"/>
	<style>
		body.window
		{
			top: 100px;
			left: 200px;
			width: 600px;
			height: 450px;
		}
	</style>
</head>
<body template="window">
<div data-model="select-test" id="select_wrap">
)";
static const String data_model_doc_rml_post = R"(
</div>
</body>
</rml>
)";

static String GetSelectValueRml(ElementFormControlSelect* select_element)
{
	for (int child_index = 0; child_index < select_element->GetNumChildren(true); child_index++)
	{
		Element* child = select_element->GetChild(child_index);
		if (child->GetTagName() == "selectvalue")
			return child->GetInnerRML();
	}

	return String();
}

TEST_CASE("form.select.value")
{
	Context* context = TestsShell::GetContext();
	REQUIRE(context);

	struct Test {
		const String rml;
		const String expected_value;
		const String expected_value_rml;
	};

	Test tests[] = {
		{
			R"(
			<select>
				<option>A</option>
				<option>B</option>
				<option>C</option>
				<option>D</option>
			</select>
			)",
			"", "A"},

		{
			R"(
			<select>
				<option value="a">A</option>
				<option>B</option>
				<option>C</option>
				<option>D</option>
			</select>
			)",
			"a", "A"},

		{
			R"(
			<select value="z">
				<option>A</option>
				<option>B</option>
				<option>C</option>
				<option>D</option>
			</select>
			)",
			"", "A"},

		{
			R"(
			<select value="c">
				<option value="a">A</option>
				<option value="b">B</option>
				<option value="c">C</option>
				<option value="d">D</option>
			</select>
			)",
			"c", "C"},

		{
			R"(
			<select value="c">
				<option value="a">A</option>
				<option value="b">B</option>
				<option value="c">C</option>
				<option value="d" selected>D</option>
			</select>
			)",
			"d", "D"},

		{
			R"(
			<select value="c">
				<option value="a" selected>A</option>
				<option value="b">B</option>
				<option value="c">C</option>
				<option value="d">D</option>
			</select>
			)",
			"a", "A"},

		{
			R"(
			<select value="c">
				<option value="a" selected>A</option>
				<option value="b">B</option>
				<option value="c">C</option>
				<option value="d" selected>D</option>
			</select>
			)",
			"d", "D"},

		{
			R"(
			<select>
				<option value="a">A</option>
				<option value="b">B</option>
				<option value="c" selected>C</option>
				<option value="d">D</option>
			</select>
			)",
			"c", "C"},
	};

	ElementDocument* document = context->LoadDocumentFromMemory(basic_doc_rml);
	REQUIRE(document);

	document->Show();
	context->Update();
	context->Render();

	int i = 0;
	for (const Test& test : tests)
	{
		document->SetInnerRML(test.rml);
		context->Update();

		REQUIRE(document->GetNumChildren() == 1);

		ElementFormControlSelect* select_element = rmlui_dynamic_cast<ElementFormControlSelect*>(document->GetFirstChild());
		REQUIRE(select_element);

		const String value = select_element->GetValue();
		const String selectvalue_rml = GetSelectValueRml(select_element);

		CHECK_MESSAGE(value == test.expected_value, "Document " << i);
		CHECK_MESSAGE(selectvalue_rml == test.expected_value_rml, "Document " << i);

		i++;
	}

	document->Close();
	TestsShell::ShutdownShell();
}

TEST_CASE("form.select.data_binding")
{
	Context* context = TestsShell::GetContext();
	REQUIRE(context);

	struct Test {
		const String rml;
		const String expected_value;
		const String expected_value_rml;
	};

	int selected_index = 2;
	String selected_value = "b";
	Vector<String> values = {"a", "b", "c", "d"};

	Test tests[] = {
		{
			R"(
			<select data-value="selected_index">
				<option value="0">A</option>
				<option value="1">B</option>
				<option value="2">C</option>
				<option value="3">D</option>
			</select>
			)",
			"2", "C"},

		{
			R"(
			<select data-value="selected_value">
				<option value="a">A</option>
				<option value="b">B</option>
				<option value="c">C</option>
				<option value="d">D</option>
			</select>
			)",
			"b", "B"},

		{
			R"(
			<select>
				<option value="a" data-attrif-selected="selected_value == 'a'">A</option>
				<option value="b" data-attrif-selected="selected_value == 'b'">B</option>
				<option value="c" data-attrif-selected="selected_value == 'c'">C</option>
				<option value="d" data-attrif-selected="selected_value == 'd'">D</option>
			</select>
			)",
			"b", "B"},

		{
			R"(
			<select>
				 <option data-for="s : subjects" data-attr-value="s" data-attrif-selected="s == selected_value">{{ s | to_upper }}</option>
			</select>
			)",
			"b", "B"},

		{
			R"(
			<select>
				 <option data-for="s : subjects" data-attr-value="it_index" data-attrif-selected="it_index == selected_index">{{ s | to_upper }}</option>
			</select>
			)",
			"2", "C"},

		{
			R"(
			<select data-value="selected_value">
				 <option data-for="s : subjects" data-attr-value="s">{{ s | to_upper }}</option>
			</select>
			)",
			"b", "B"},

		{
			R"(
			<select data-value="selected_index">
				 <option data-for="s : subjects" data-attr-value="it_index">{{ s | to_upper }}</option>
			</select>
			)",
			"2", "C"},

		{
			R"(
			<select data-value="selected_index">
				 <option data-for="s : subjects" data-attr-value="it_index"><p data-rml="s | to_upper"></p></option>
			</select>
			)",
			"2", "<p data-rml=\"s | to_upper\">C</p>"},
	};

	DataModelConstructor constructor = context->CreateDataModel("select-test");
	constructor.RegisterArray<Vector<String>>();

	constructor.Bind("selected_index", &selected_index);
	constructor.Bind("selected_value", &selected_value);
	constructor.Bind("subjects", &values);

	int i = 0;
	for (const Test& test : tests)
	{
		const String document_rml = data_model_doc_rml_pre + test.rml + data_model_doc_rml_post;

		ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
		REQUIRE(document);

		document->Show();

		TestsShell::RenderLoop();

		Element* wrapper_element = document->GetElementById("select_wrap");
		REQUIRE(wrapper_element);
		REQUIRE(wrapper_element->GetNumChildren() == 1);

		ElementFormControlSelect* select_element = rmlui_dynamic_cast<ElementFormControlSelect*>(wrapper_element->GetFirstChild());
		REQUIRE(select_element);

		const String value = select_element->GetValue();
		const String selectvalue_rml = GetSelectValueRml(select_element);

		CHECK_MESSAGE(value == test.expected_value, "Document " << i);
		CHECK_MESSAGE(selectvalue_rml == test.expected_value_rml, "Document " << i);

		i++;
		document->Close();
	}

	TestsShell::ShutdownShell();
}

TEST_CASE("form.select.data-for")
{
	Context* context = TestsShell::GetContext();
	REQUIRE(context);

	struct Test {
		const String rml;
		const String expected_value;
		const String expected_value_rml;
	};

	String selected_value = "b";
	Vector<String> values = {"a", "b", "c", "d"};

	DataModelConstructor constructor = context->CreateDataModel("select-test");
	constructor.RegisterArray<Vector<String>>();

	constructor.Bind("selected_value", &selected_value);
	constructor.Bind("subjects", &values);

	DataModelHandle handle = constructor.GetModelHandle();

	{
		const String select_rml = R"(
			<select data-value="selected_value">
				 <option data-for="s : subjects" data-attr-value="s">{{ s | to_upper }}</option>
			</select>
		)";

		const String document_rml = data_model_doc_rml_pre + select_rml + data_model_doc_rml_post;

		ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
		REQUIRE(document);

		document->Show();

		Element* wrapper_element = document->GetElementById("select_wrap");
		REQUIRE(wrapper_element);
		REQUIRE(wrapper_element->GetNumChildren() == 1);

		ElementFormControlSelect* select_element = rmlui_dynamic_cast<ElementFormControlSelect*>(wrapper_element->GetFirstChild());
		REQUIRE(select_element);

		{
			// TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(value == "b");
			CHECK(selected_index == 1);
			CHECK(selectvalue_rml == "B");
		}

		{
			selected_value = "d";
			handle.DirtyVariable("selected_value");
			context->Update();
			// TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(value == "d");
			CHECK(selected_index == 3);
			CHECK(selectvalue_rml == "D");
		}

		{
			select_element->SetValue("a");
			context->Update();
			TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(selected_value == "a");
			CHECK(value == "a");
			CHECK(selected_index == 0);
			CHECK(selectvalue_rml == "A");
		}

		{
			select_element->SetSelection(1);
			context->Update();
			TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(selected_value == "b");
			CHECK(value == "b");
			CHECK(selected_index == 1);
			CHECK(selectvalue_rml == "B");
		}

#if 0
		// These are not supported now, we may want to add support later.

		{
			// Values: a c b d
			std::swap(values[1], values[2]);
			handle.DirtyVariable("subjects");
			context->Update();
			TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(value == "b");
			CHECK(selected_index == 2);
			CHECK(selectvalue_rml == "B");
		}

		{
			// Values: c b d
			values.erase(values.begin());
			handle.DirtyVariable("subjects");
			context->Update();
			TestsShell::RenderLoop();

			const String value = select_element->GetValue();
			const int selected_index = select_element->GetSelection();
			const String selectvalue_rml = GetSelectValueRml(select_element);

			CHECK(value == "b");
			CHECK(selected_index == 1);
			CHECK(selectvalue_rml == "B");
		}
#endif

		document->Close();
	}

	TestsShell::ShutdownShell();
}

static const String event_doc_rml = R"(
<rml>
<head>
	<link type="text/rcss" href="/assets/rml.rcss"/>
	<link type="text/rcss" href="/assets/invader.rcss"/>
</head>
<body>
    <select id="sel">
        <option selected>Cube</option>
        <option>Cone</option>
        <option value="c">Cylinder</option>
        <option value="d">Sphere</option>
    </select>
</body>
</rml>
)";

static void ClickAt(Context* context, int x, int y)
{
	context->Update();
	context->Render();
	context->ProcessMouseMove(x, y, 0);
	context->Update();
	context->Render();
	context->ProcessMouseButtonDown(0, 0);
	context->ProcessMouseButtonUp(0, 0);
	context->Update();
	context->Render();
}

TEST_CASE("form.select.event.change")
{
	Context* context = TestsShell::GetContext();
	REQUIRE(context);

	ElementDocument* document = context->LoadDocumentFromMemory(event_doc_rml);
	REQUIRE(document);

	document->Show();

	struct SelectionEventListener : public Rml::EventListener {
		void ProcessEvent(Rml::Event& ev) override
		{
			num_events_processed += 1;
			value = ev.GetParameter<Rml::String>("value", "*empty*");
		}
		int num_events_processed = 0;
		String value;
	};
	auto listener = MakeUnique<SelectionEventListener>();
	document->GetElementById("sel")->AddEventListener(Rml::EventId::Change, listener.get());

	ClickAt(context, 100, 20); // Open select
	ClickAt(context, 100, 40); // Click option 'Cube'
	CHECK(listener->num_events_processed == 0);

	ClickAt(context, 100, 20); // Open select
	ClickAt(context, 100, 63); // Click option 'Cone'
	CHECK(listener->num_events_processed == 1);
	CHECK(listener->value == "");

	ClickAt(context, 100, 20); // Open select again
	ClickAt(context, 100, 85); // Click option 'Cylinder'
	CHECK(listener->num_events_processed == 2);
	CHECK(listener->value == "c");

	ClickAt(context, 100, 20);             // Open select again
	context->ProcessMouseMove(100, 85, 0); // Hover option 'Cylinder'
	context->Update();
	context->Render();
	context->ProcessKeyDown(Rml::Input::KI_DOWN, 0);
	context->Update();
	context->Render();
	CHECK(listener->num_events_processed == 3);
	CHECK(listener->value == "d");
	context->ProcessKeyDown(Rml::Input::KI_DOWN, 0);
	context->Update();
	context->Render();
	CHECK(listener->num_events_processed == 3);

	document->Close();

	TestsShell::ShutdownShell();
}
