using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;

namespace skyscraper5.Skyscraper.Gps.GeoIp
{
    internal class GeoIp : IGpsReceiver
    {
        public void Start()
        {
            keepTryping = true;
            workerThread = new Thread(WorkerThreadFunction);
            workerThread.Priority = ThreadPriority.Lowest;
            workerThread.Start();
        }

        public void Stop()
        {
            keepTryping = false;
        }

        private bool gotLock;
        public bool HasLock => gotLock;

        private GpsCoordinate _resolvedCoordinate;

        public GpsCoordinate Coordinate => _resolvedCoordinate;
        public bool ProvidesAdditionalNmeaData => false;
        public AdditionalNmeaData AdditionalNmeaData => null;


        private bool keepTryping;
        private Thread workerThread;

        private void WorkerThreadFunction()
        {
            while (keepTryping)
            {
                try
                {
                    TryGeoIp();
                }
                catch (GeoIpException e)
                {
                    Console.WriteLine(e.Error);
                    Thread.Sleep(e.CooldownTime);
                }
            }
        }

        private void TryGeoIp()
        {
            DateTime currentDateTime = DateTime.Now;
            string fname = string.Format("dbip-city-lite-{0}-{1}.mmdb.gz", currentDateTime.Year, currentDateTime.Month);
            FileInfo mmdbFileInfo = new FileInfo(fname);
            if (!mmdbFileInfo.Exists)
            {
                if (!DownloadMmdb(fname))
                {
                    mmdbFileInfo = LookForOlderMmdb();
                    if (mmdbFileInfo == null)
                    {
                        throw new GeoIpException(GeoIpError.NoDatabaseAndDownloadFailed, 60000);
                    }
                }
            }

            MaxMindDb mmdb = new MaxMindDb(mmdbFileInfo);
            IPAddress ipAddress = GetPublicIPAddress();
            _resolvedCoordinate = mmdb.Locate(ipAddress);
            gotLock = true;
            keepTryping = false;
        }

        #region Get MMDB from disk or internet
        private bool DownloadMmdb(string version)
        {
            string url = string.Format("https://download.db-ip.com/free/{0}", version);
            WebClient wc = new WebClient();
            wc.DownloadFile(url, version);
            return true;
        }

        private FileInfo LookForOlderMmdb()
        {
            DirectoryInfo currentDirectory = new DirectoryInfo(".");
            FileInfo[] fileInfos = currentDirectory.GetFiles("dbip-city-lite-*.mmdb.gz");
            if (fileInfos.Length == 0)
                return null;
            fileInfos.OrderBy(x => x.Name);
            return fileInfos[0];
        }
#endregion

        #region IP Fetching strategies
        private IPAddress GetPublicIPAddress()
        {
	        RetrievePublicIpAddress[] strategies = new RetrievePublicIpAddress[]
		        { TryIfconfigMe, TryICanHazIp, TryIpInfoIo, TryApiIpifyOrg, TryIdentMe };
	        strategies.Shuffle();

	        for (int i = 0; i < strategies.Length; i++)
	        {
		        try
		        {
			        return strategies[i]();
				}
		        catch (Exception e)
		        {
			        Debug.WriteLine("Failed to retrieve IP using {0}", strategies[i].Method.Name);
			        Thread.Sleep(1000);
		        }
	        }
	        return null;
        }

        private delegate IPAddress RetrievePublicIpAddress();
        private IPAddress TryIfconfigMe()
        {
	        WebClient wc = new WebClient();
	        string downloadString = wc.DownloadString(new Uri("http://ifconfig.me"));
	        return IPAddress.Parse(downloadString);
        }

        private IPAddress TryICanHazIp()
        {
	        WebClient wc = new WebClient();
	        string downloadString = wc.DownloadString(new Uri("http://icanhazip.com"));
	        downloadString = downloadString.Replace("\n", "");
	        return IPAddress.Parse(downloadString);
		}
        
        private IPAddress TryIpInfoIo()
        {
	        WebClient wc = new WebClient();
	        string downloadString = wc.DownloadString(new Uri("http://ipinfo.io/ip"));
	        return IPAddress.Parse(downloadString);
        }

        private IPAddress TryApiIpifyOrg()
        {
	        WebClient wc = new WebClient();
	        string downloadString = wc.DownloadString(new Uri("http://api.ipify.org"));
	        return IPAddress.Parse(downloadString);
        }

		private IPAddress TryIdentMe()
		{
			WebClient wc = new WebClient();
			string downloadString = wc.DownloadString(new Uri("http://ident.me"));
			return IPAddress.Parse(downloadString);
		}
        #endregion
    }
}
