using MetadataExtractor;
using View_by_Distance.Metadata.Models.Stateless;
using View_by_Distance.Shared.Models;

namespace View_by_Distance.Metadata.Models.Stateless;

internal abstract class GPS
{

    private static bool CoordinateValidatorValidate(double latitude, double longitude)
    {
        if (latitude is < (-90) or > 90)
            return false;
        if (longitude is < (-180) or > 180)
            return false;

        return true;
    }

    private static double GetRadius(IMetadata.DistanceUnit distanceUnit)
    {
        return distanceUnit switch
        {
            IMetadata.DistanceUnit.Kilometers => 6371.0, // EarthRadiusInKilometers;
            IMetadata.DistanceUnit.Meters => 6371000.0, // EarthRadiusInMeters;
            IMetadata.DistanceUnit.NauticalMiles => 3440.0, // EarthRadiusInNauticalMiles;
            IMetadata.DistanceUnit.Miles => 3959.0, // EarthRadiusInMiles;
            _ => throw new NotSupportedException()
        };
    }

    private static double ToRadian(double d) =>
        d * (Math.PI / 180);

    private static double DiffRadian(double val1, double val2) =>
        ToRadian(val2) - ToRadian(val1);

    internal static double GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, IMetadata.DistanceUnit distanceUnit = IMetadata.DistanceUnit.Miles)
    {
        if (!CoordinateValidatorValidate(originLatitude, originLongitude))
            throw new ArgumentException("Invalid origin coordinates supplied.");
        if (!CoordinateValidatorValidate(destinationLatitude, destinationLongitude))
            throw new ArgumentException("Invalid destination coordinates supplied.");
        double radius = GetRadius(distanceUnit);
        return Math.Round(
                radius * 2 *
                Math.Asin(Math.Min(1,
                                   Math.Sqrt(
                                       Math.Pow(Math.Sin(DiffRadian(originLatitude, destinationLatitude) / 2.0), 2.0) +
                                        (Math.Cos(ToRadian(originLatitude)) * Math.Cos(ToRadian(destinationLatitude)) *
                                        Math.Pow(Math.Sin(DiffRadian(originLongitude, destinationLongitude) / 2.0),
                                                 2.0))))), decimalPlaces);
    }

    private static double ParseValueFromDmsString(string value)
    {
        double result;
        if (string.IsNullOrEmpty(value))
            return double.MinValue;

        double secondsValue;
        string[] degrees = value.Split('°');
        if (degrees.Length != 2)
            return double.MinValue;
        if (!double.TryParse(degrees[0], out double degreesValue))
            return double.MinValue;

        string[] minutes = degrees[1].Split('\'');
        if (minutes.Length != 2)
            return double.MinValue;
        if (!double.TryParse(minutes[0], out double minutesValue))
            return double.MinValue;

        string[] seconds = minutes[1].Split('"');
        if (seconds.Length != 2)
            secondsValue = 0;
        else
        {
            if (!double.TryParse(seconds[0], out secondsValue))
                return double.MinValue;
        }
        result = Math.Abs(degreesValue) + (minutesValue / 60) + (secondsValue / 3600);

        if (degreesValue < 0)
            result *= -1;

        return result;
    }

    internal static GeoLocation? GeoLocation(GpsDirectory[]? gpsDirectories)
    {
        GeoLocation? result = null;
        if (gpsDirectories is not null)
        {
            foreach (GpsDirectory gpsDirectory in gpsDirectories)
            {
                if (string.IsNullOrEmpty(gpsDirectory?.Latitude))
                    result = null;
                else
                {
                    string latitudeDMS = gpsDirectory.Latitude;
                    double latitude = ParseValueFromDmsString(latitudeDMS);
                    if (string.IsNullOrEmpty(gpsDirectory.Longitude))
                        result = null;
                    else
                    {
                        string longitudeDMS = gpsDirectory.Longitude;
                        double longitude = ParseValueFromDmsString(longitudeDMS);
                        result = new(latitude, longitude);
                        string dms = result.ToDmsString();
                        if ($"{latitudeDMS}, {longitudeDMS}" != dms)
                            result = null;
                    }
                }
                if (result is not null)
                    break;
            }
        }
        return result;
    }

}