using System;
using System.Collections.Generic;


namespace InABox.Core
{
    public class GeoPoint
    {
        public GeoPoint(double latitude = 0.0, double longitude = 0.0)
        {
            Latitude = latitude;
            Longitude = longitude;
        }
        public double Longitude { get; set; }
        public double Latitude { get; set; }

        public GeoPoint Copy() => new GeoPoint(Latitude, Longitude);

        public static double DistanceBetween(GeoPoint from, GeoPoint to, UnitOfLength? unitOfLength = null)
        {
            var baseRad = Math.PI * from.Latitude / 180;
            var targetRad = Math.PI * to.Latitude / 180;
            var theta = from.Longitude - to.Longitude;
            var thetaRad = Math.PI * theta / 180;

            var dist =
                Math.Sin(baseRad) * Math.Sin(targetRad) + Math.Cos(baseRad) *
                Math.Cos(targetRad) * Math.Cos(thetaRad);
            dist = Math.Acos(dist);

            dist = dist * 180 / Math.PI;
            dist = dist * 60 * 1.1515;

            var unit = unitOfLength ?? UnitOfLength.Kilometers;
            return unit.ConvertFromMiles(dist);
        }
        
        public double DistanceTo(GeoPoint to, UnitOfLength? unitOfLength = null) 
            => DistanceBetween(this, to, unitOfLength);
        
        private static double WGS84_RADIUS = 6370997.0;
        private static double EarthCircumFence = 2* WGS84_RADIUS * Math.PI;

        public GeoPoint Move(double mEastWest, double mNorthSouth){
            double degreesPerMeterForLat = EarthCircumFence/360.0;
            double shrinkFactor = Math.Cos((Latitude*Math.PI/180.0));
            double degreesPerMeterForLon = degreesPerMeterForLat * shrinkFactor;
            double newLat = Latitude + mNorthSouth * (1.0/degreesPerMeterForLat);
            double newLng = Longitude + mEastWest * (1.0/degreesPerMeterForLon);
            return new GeoPoint(newLat, newLng);
        }
        
    }
    
    public class GeoFenceDefinition
    {
        
        public List<GeoPoint> Coordinates { get;} = new List<GeoPoint>();
        
        public bool Contains(GeoPoint p)
        {
            if (Coordinates.Count < 2)
                return false;
            
            double minX = Coordinates[0].Longitude;  
            double maxX = Coordinates[0].Longitude;  
            double minY = Coordinates[0].Latitude;  
            double maxY = Coordinates[0].Latitude;  
            for (int i = 1; i < Coordinates.Count; i++)  
            {  
                GeoPoint q = Coordinates[i];  
                minX = Math.Min(q.Longitude, minX);  
                maxX = Math.Max(q.Longitude, maxX);  
                minY = Math.Min(q.Latitude, minY);  
                maxY = Math.Max(q.Latitude, maxY);  
            }  
     
            if (p.Longitude < minX || p.Longitude > maxX || p.Latitude < minY || p.Latitude > maxY)  
            {  
                return false;  
            }  
            bool inside = false;  
            for (int i = 0, j = Coordinates.Count - 1; i < Coordinates.Count; j = i++)  
            {  
                if ((Coordinates[i].Latitude > p.Latitude) != (Coordinates[j].Latitude > p.Latitude) &&  
                    p.Longitude < (Coordinates[j].Longitude - Coordinates[i].Longitude) * (p.Latitude - Coordinates[i].Latitude) / (Coordinates[j].Latitude - Coordinates[i].Latitude) + Coordinates[i].Longitude)  
                {  
                    inside = !inside;  
                }  
            }  
            return inside;  
        }
    }
}