// CountryLookup.cs

using System.IO.Compression;

namespace ReverseGeocode;

/// <summary>
/// Provides the country lookup functionality.
/// </summary>
public static class CountryLookup
{
	internal class Properties
	{
		public string? formal_en { get; set; }
		public string? name_sort { get; set; }
	}
	internal class Geometry
	{
		public List<List<double[]>> coordinates { get; set; }
	}
	internal class Country
	{
		public Properties properties { get; set; }
		public Geometry geometry { get; set; }
	}
	internal class Find
	{
		public string? name;
		public double proximity;
	}

	private static readonly Lazy<List<Country>> LazyData = new(LoadData);

	private static List<Country> LoadData()
	{
		var assembly = typeof(CountryLookup).Assembly;
		using var compressedStream = assembly.GetManifestResourceStream("ReverseGeocode.COUNTRIES.json.gz");
		using var stream = new GZipStream(compressedStream!, CompressionMode.Decompress);

		using (var ms = new MemoryStream())
		{
			stream.CopyTo(ms);
			ms.Seek(0, SeekOrigin.Begin);
			return System.Text.Json.JsonSerializer.Deserialize<List<Country>>(System.Text.Encoding.UTF8.GetString(ms.ToArray())) ?? new List<Country>();
		}
	}

    private static string? FormatCountryName(Country country)
    {
        if (country.properties.formal_en != null && country.properties.name_sort != null && country.properties.formal_en != country.properties.name_sort)
            return country.properties.formal_en + '/' + country.properties.name_sort;
        else
            return country.properties.formal_en ?? country.properties.name_sort;

    }
    
	/// <summary>
    /// Determines the country for given location coordinates.
    /// </summary>
    /// <param name="latitude">The latitude of the location.</param>
    /// <param name="longitude">The longitude of the location.</param>
    /// <returns>A <string?>, which contains the result of the operation or null if not found.</returns>
    public static string? GetCountry(double latitude, double longitude)
	{
		var p = new double[2] { latitude, longitude };
		foreach (var country in LazyData.Value)
		{
			foreach (var list in country.geometry.coordinates)
			{
				if (Common.IsCoordInsideArrayOfCoords(list, p))
					return FormatCountryName(country);
			}
		}
		return null;
	}

	/// <summary>
	/// Determines the nearest country for given location coordinates.
	/// </summary>
	/// <param name="latitude">The latitude of the location.</param>
	/// <param name="longitude">The longitude of the location.</param>
	/// <returns>A <string>, which contains the result of the operation.</returns>
	public static string GetNearestCountry(double latitude, double longitude)
	{
		var finds = new List<Find>();
		foreach (var country in LazyData.Value)
		{
			foreach (var list in country.geometry.coordinates)
			{
				foreach (var coords in list)
					finds.Add(new Find { name = FormatCountryName(country), proximity = (coords[0] - latitude) * (coords[0] - latitude) + (coords[1] - longitude) * (coords[1] - longitude) });
			}
		}
		var find = finds.OrderBy(x => x.proximity).First();
		return find.name ?? "n/a";
	}
}