/*
 * ministream - minimal (subset) appstream metadata parser
 * Copyright (c) 2026  Florian Leander Singer <sp1rit@disroot.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <ministream.h>
#include <appstream.h>
#include <libxml/parser.h>
#include <libxml/c14n.h>

static void compare_component(MsComponent* ms, AsComponent* as) {
	g_assert_cmpstr(ms_component_get_id(ms), ==, as_component_get_id(as));
	g_assert_cmpstr(ms_component_get_name(ms), ==, as_component_get_name(as));
	g_assert_cmpstr(ms_component_get_homepage_uri(ms), ==, as_component_get_url(as, AS_URL_KIND_HOMEPAGE));
	g_assert_cmpstr(ms_component_get_bugtracker_uri(ms), ==, as_component_get_url(as, AS_URL_KIND_BUGTRACKER));
	g_assert_cmpstr(ms_component_get_help_uri(ms), ==, as_component_get_url(as, AS_URL_KIND_HELP));

	const gchar** mdl = ms_component_get_desktop_launchables(ms);
	AsLaunchable* adl = as_component_get_launchable(as, AS_LAUNCHABLE_KIND_DESKTOP_ID);
	g_assert_true((!mdl && (!adl || !as_launchable_get_entries(adl))) || (mdl && adl && as_launchable_get_entries(adl)));
	if (mdl) {
		GPtrArray* mdle = g_ptr_array_new_from_null_terminated_array((gpointer*)mdl, NULL, NULL, NULL);
		GPtrArray* adle = as_launchable_get_entries(adl);

		g_assert_cmpint(mdle->len, ==, adle->len);

		g_ptr_array_sort_values(mdle, (GCompareFunc)g_strcmp0);
		g_ptr_array_sort_values(adle, (GCompareFunc)g_strcmp0); // this is modifing data owned by appstream

		for (guint i = 0; i < mdle->len; i++)
			g_assert_cmpstr(g_ptr_array_index(mdle, i), ==, g_ptr_array_index(adle, i));

		g_ptr_array_unref(mdle);
	}

	guint m_nrel;
	const MsRelease** mr = ms_component_get_releases(ms, &m_nrel);
#if AS_CHECK_VERSION (1, 0, 0)
	GPtrArray* ar = as_release_list_get_entries (as_component_get_releases_plain (as));
#else
	GPtrArray* ar = as_component_get_releases (as);
#endif
	g_assert_cmpint(m_nrel, ==, ar->len);
	if (m_nrel > 0) {
		for (guint i = 0; i < m_nrel; i++) {
			const MsRelease* mri = mr[i];
			AsRelease* ari = g_ptr_array_index(ar, i);
			g_assert_cmpstr(ms_release_get_version(mri), ==, as_release_get_version(ari));

			// XML canonicalization might be a little overkill here, but its easy :)
			const gchar* mrds = ms_release_get_description(mri);
			const gchar* ards = as_release_get_description(ari);
			g_assert_true((!mrds && !ards) || (mrds && ards));
			if (mrds) {
				// libxml always expects a single root node
				gchar* mrds_mod = g_strdup_printf("<root>%s</root>", mrds);
				// AppStream doesn't replace newlines inside markup with spaces
				GString* ards_mod = g_string_sized_new(strlen(ards));
				g_string_append(ards_mod, "<root>");
				gboolean lastwasspace = TRUE;
				for (gsize i = 0; ards[i]; i++) {
					if (ards[i] == '\n' || ards[i] == '\t' || ards[i] == ' ') {
						if (lastwasspace)
							continue;
						g_string_append_c(ards_mod, ' ');
						lastwasspace = TRUE;
						continue;
					}

					g_string_append_c(ards_mod, ards[i]);
					lastwasspace = ards[i] == '>';
				}
				g_string_append(ards_mod, "</root>");

				xmlDocPtr mrd = xmlReadMemory(mrds_mod, strlen(mrds_mod), NULL, "UTF-8", XML_PARSE_NOBLANKS);
				xmlDocPtr ard = xmlReadMemory(ards_mod->str, ards_mod->len, NULL, "UTF-8", XML_PARSE_NOBLANKS);
				g_assert_nonnull(mrd);
				g_assert_nonnull(ard);

				gint mrdc_len, ardc_len;
				xmlChar *mrdc, *ardc;

				mrdc_len = xmlC14NDocDumpMemory(mrd, NULL, XML_C14N_1_0, NULL, 0, &mrdc);
				ardc_len = xmlC14NDocDumpMemory(ard, NULL, XML_C14N_1_0, NULL, 0, &ardc);

				g_assert_true(mrdc_len >= 0);
				if (mrdc_len != ardc_len || memcmp(mrdc, ardc, mrdc_len) != 0)
					g_assertion_message_cmpstr(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, "mrdc == ardc", (const gchar*)mrdc, "==", (const gchar*)ardc);

				xmlFree(mrdc);
				xmlFree(ardc);

				xmlFreeDoc(mrd);
				xmlFreeDoc(ard);

				g_free(mrds_mod);
				g_string_free(ards_mod, TRUE);
			}
		}
	}

	g_assert_cmpstr(ms_component_get_project_license(ms), ==, as_component_get_project_license(as));
#if AS_CHECK_VERSION (0, 16, 4)
	g_assert_cmpstr(ms_component_get_developer_name(ms), ==, as_developer_get_name(as_component_get_developer(as)));
#else
	g_assert_cmpstr(ms_component_get_developer_name(ms), ==, as_component_get_developer_name(as));
#endif
}

static void compare_file(const gchar* path) {
	gchar* buf;
	gsize len;
	GError* err = NULL;
	g_file_get_contents(path, &buf, &len, &err);
	g_assert_no_error(err);

	MsComponent* mc = ms_component_new_from_xml_data((const guchar*)buf, len, &err);
	g_assert_no_error(err);

	AsMetadata* am = as_metadata_new();
	as_metadata_parse_data(am, buf, len, AS_FORMAT_KIND_XML, &err);
	g_assert_no_error(err);

	g_free(buf);

	AsComponent* ac = as_metadata_get_component(am);

	compare_component(mc, ac);
	
	ms_component_unref(mc);
	g_object_unref(am);
}

int main(int argc, char** argv) {
	if (argc < 2)
		return 2;

	for (int i = 1; i < argc; i++)
		compare_file(argv[i]);
	return 0;
}
