using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

internal class Program
{
	private static string app = AppDomain.CurrentDomain.FriendlyName;
	private static TextWriter stderr = Console.Error;
	private static TextWriter stdout = Console.Out;

	private static void usage()
	{
		stderr.WriteLine("Usage:");
		stderr.WriteLine(app + " <path to output files>");
		Environment.Exit(1);
	}

	private static bool writeZoneMapC(string path)
	{
		string fname = "TimeZoneNameMap";
		string fpath = Path.Combine(path, fname + "_static.h");

		using (StreamWriter fs = new StreamWriter(fpath))
		{
			fs.WriteLine("/* Automatically generated by " + app + " */");
			fs.WriteLine("");
			fs.WriteLine("#include \"" + fname + ".h\"");
			fs.WriteLine("");
			fs.WriteLine("static const " + fname + "Entry " + fname + "[] ={");

			bool first = true;
			foreach (System.TimeZoneInfo tz in System.TimeZoneInfo.GetSystemTimeZones())
			{
				string iana;
				System.TimeZoneInfo.TryConvertWindowsIdToIanaId(tz.Id, out iana);

				StringBuilder sb = new StringBuilder();
				if (!first)
					sb.Append(",");
				first = false;
				sb.Append("{ \"");
				sb.Append(tz.Id);
				sb.Append("\", \"");
				sb.Append(tz.StandardName);
				sb.Append("\", \"");
				sb.Append(tz.DisplayName);
				sb.Append("\", \"");
				sb.Append(tz.DaylightName);
				sb.Append("\", \"");
				sb.Append(iana);
				sb.Append("\" }");
				fs.WriteLine(sb.ToString());
			}

			fs.WriteLine("};");
			fs.WriteLine("");
			fs.WriteLine("static const size_t " + fname + "Size = ARRAYSIZE(" + fname + ");");
		}
		return true;
	}

	private static bool writeZoneMapJSON(string path)
	{
		string fname = "TimeZoneNameMap";
		string fpath = Path.Combine(path, fname + ".json");

		using (StreamWriter fs = new StreamWriter(fpath))
		{
			fs.WriteLine("{");
			fs.WriteLine("\"TimeZoneNameMap\": [");

			bool first = true;
			foreach (System.TimeZoneInfo tz in System.TimeZoneInfo.GetSystemTimeZones())
			{
				string iana;
				System.TimeZoneInfo.TryConvertWindowsIdToIanaId(tz.Id, out iana);

				StringBuilder sb = new StringBuilder();
				if (!first)
					sb.Append(",");
				first = false;
				sb.Append("{ ");
				sb.Append("\"Id\": \"");
				sb.Append(tz.Id);
				sb.Append("\", \"StandardName\": \"");
				sb.Append(tz.StandardName);
				sb.Append("\", \"DisplayName\": \"");
				sb.Append(tz.DisplayName);
				sb.Append("\", \"DaylightName\": \"");
				sb.Append(tz.DaylightName);
				sb.Append("\", \"Iana\": \"");
				sb.Append(iana);
				sb.Append("\" }");
				fs.WriteLine(sb.ToString());
			}

			fs.WriteLine("]");
			fs.WriteLine("}");
		}
		return true;
	}

	private static bool writeZoneMap(string path)
	{
		if (!writeZoneMapC(path))
			return false;
		if (!writeZoneMapJSON(path))
			return false;
		return true;
	}

	private static void onValidation(object sender, ValidationEventArgs e)
	{
		switch (e.Severity)
		{
			case XmlSeverityType.Warning:
			case XmlSeverityType.Error:
				stderr.WriteLine(e.ToString());
				break;
			default:
				break;
		}
	}
	private static bool updateWindowsIanaMap(string path)
	{
		string url =
		    "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml";
		string fname = "WindowsZones";
		string fpath = Path.Combine(path, fname + ".c");

		XmlDocument doc = new XmlDocument();
		doc.Load(url);

		stdout.WriteLine("Downloaded and parsed XML from '" + url + "'");

		ValidationEventHandler handler = new ValidationEventHandler(onValidation);
		// doc.Validate(handler);

		XmlNodeList versions = doc.SelectNodes("//supplementalData/version");
		XmlNodeList zones = doc.SelectNodes("//supplementalData/windowsZones/mapTimezones");
		XmlNodeList mzones =
		    doc.SelectNodes("//supplementalData/windowsZones/mapTimezones/mapZone");

		using (StreamWriter fs = new StreamWriter(fpath))
		{
			fs.WriteLine("/* Automatically generated by " + app);
			fs.WriteLine(" *");
			fs.WriteLine(" * url " + url);

			foreach (XmlNode version in versions)
			{
				XmlNode nr = version.Attributes.GetNamedItem("number");
				fs.WriteLine(" * version: " + nr.InnerText);
			}

			foreach (XmlNode node in zones)
			{
				XmlNode over = node.Attributes.GetNamedItem("otherVersion");
				XmlNode tver = node.Attributes.GetNamedItem("typeVersion");
				fs.WriteLine(" * mapTimezones: otherVersion: " + over.InnerText +
				             ", typeVersion: " + tver.InnerText);
			}

			fs.WriteLine(" */");
			fs.WriteLine("");
			fs.WriteLine("#include \"" + fname + ".h\"");
			fs.WriteLine("");
			fs.WriteLine("const WINDOWS_TZID_ENTRY " + fname + "[] = {");

			foreach (XmlNode mzone in mzones)
			{
				XmlAttributeCollection attrs = mzone.Attributes;
				XmlNode wzid = attrs.GetNamedItem("other");
				XmlNode territory = attrs.GetNamedItem("territory");
				XmlNode iana = attrs.GetNamedItem("type");
				fs.WriteLine("\t{ \"" + iana.InnerText + "\", \"" + wzid.InnerText + "\" }, // " +
				             territory.InnerText);
			}

			fs.WriteLine("};");
			fs.WriteLine("");
			fs.WriteLine("const size_t " + fname + "NrElements = ARRAYSIZE(" + fname + ");");
		}
		stdout.WriteLine("Finished update");
		return true;
	}

	private static void Main(string[] args)
	{
		try
		{
			if (args.Length == 0)
			{
				stderr.WriteLine("Missing arguments!");
				usage();
			}

			DirectoryInfo info = new DirectoryInfo(args[0]);
			if (!info.Exists)
			{
				stderr.WriteLine("Path '" + info.FullName + "' does not exist");
				usage();
			}

			if (!writeZoneMap(info.FullName))
				return;

			updateWindowsIanaMap(info.FullName);
		}
		catch (Exception e)
		{
			stderr.WriteLine(e.ToString());
			Environment.Exit(-23);
		}
	}
}
