using System;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace EliteBGS.EDDB {
    public class API {
        private static readonly string EDDB_SYSTEMS_ARCHIVE = "https://eddb.io/archive/v6/systems_populated.json";
        private static readonly string EDDB_STATIONS_ARCHIVE = "https://eddb.io/archive/v6/stations.json";

        private string cache_folder = null;

        private string systems_file = null;
        private string stations_file = null;
        private string stations_file_short = null;

        public delegate void DatabaseAvailableDelegate();
        public delegate void DatabaseUpdateProgressDelegate();

        public event DatabaseAvailableDelegate SystemsAvailable;
        public event DatabaseAvailableDelegate StationsAvailable;

        public event DatabaseUpdateProgressDelegate DatabaseUpdateProgress;
        public event DatabaseUpdateProgressDelegate DatabaseUpdateFinished;

        public string SystemsFile => systems_file;
        public string StationsFile => stations_file;
        public string StationsFileShort => stations_file_short;

        public string Cache {
            get => cache_folder;
            set => cache_folder = value;
        }

        public API(string cache_folder) {
            Initialise(cache_folder);
        }

        private void Initialise(string cache_folder) {
            this.cache_folder = cache_folder;
            systems_file = Path.Combine(this.cache_folder, "systems_populated.json");
            stations_file = Path.Combine(this.cache_folder, "stations.json");
            stations_file_short = Path.Combine(this.cache_folder, "stations_short.json");

            this.StationsAvailable += API_StationsAvailable;
        }

        private void API_StationsAvailable() {
            TranslateStations();
            DatabaseUpdateFinished?.Invoke();
        }

        private void DownloadFile(string url, string file, DatabaseAvailableDelegate notifier) {
            WebClient client = new WebClient();
            client.DownloadFileCompleted += Client_DownloadFileCompleted;
            client.DownloadProgressChanged += Client_DownloadProgressChanged;
            client.DownloadFileAsync(new Uri(url), file, notifier);
        }

        private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {
            DatabaseUpdateProgress?.Invoke();
        }

        private void Client_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) {
            DatabaseAvailableDelegate notifier = e.UserState as DatabaseAvailableDelegate;
            notifier?.Invoke();
        }

        private void TranslateStations() {
            if (!HaveStationsFile) {
                return;
            }

            var short_time = File.GetLastWriteTimeUtc(StationsFileShort);
            var long_time = File.GetLastWriteTimeUtc(StationsFile);

            if (HaveStationsFileShort && long_time <= short_time) {
                return;
            }

            Dictionary<int, List<string>> systems = new Dictionary<int, List<string>>();

            using (var str = new StreamReader(StationsFile)) {
                using (var reader = new JsonTextReader(str)) {
                    JArray obj = (JArray)JToken.ReadFrom(reader);

                    foreach (JObject child in obj.Children<JObject>()) {
                        int system_id = child.Value<int>("system_id");
                        string name = child.Value<string>("name");

                        if (!systems.ContainsKey(system_id)) {
                            systems.Add(system_id, new List<string>());
                        }

                        DatabaseUpdateProgress?.Invoke();

                        systems[system_id].Add(name);
                    }
                }
            }

            JObject short_stations = new JObject();

            foreach(int ids in systems.Keys) {
                JArray station_names = new JArray();
                foreach(string system in systems[ids]) {
                    station_names.Add(system);
                }
                short_stations.Add(ids.ToString(), station_names);
            }

            using (var outstr = new StreamWriter(stations_file_short)) {
                using (var jwriter = new JsonTextWriter(outstr)) {
                    short_stations.WriteTo(jwriter);
                }
            }
        }

        public void CheckDatabases() {
            if (HaveSystemsFile) {
                SystemsAvailable?.Invoke();
            }

            if (HaveStationsFile) {
                StationsAvailable?.Invoke();
            }
        }

        public void Download(bool force) {
            if (!HaveSystemsFile || force) {
                DownloadFile(EDDB_SYSTEMS_ARCHIVE, systems_file, SystemsAvailable);
            } else if (HaveSystemsFile) {
                SystemsAvailable?.Invoke();
            }

            if (!HaveStationsFile || force) {
                DownloadFile(EDDB_STATIONS_ARCHIVE, stations_file, StationsAvailable);
            } else if (HaveStationsFile) {
                StationsAvailable?.Invoke();
            }
        }

        public void Download() {
            Download(false);
        }

        public bool HaveSystemsFile {
            get { return systems_file != null && File.Exists(systems_file); }
        }

        public bool HaveStationsFile {
            get { return stations_file != null && File.Exists(stations_file); }
        }

        public bool HaveStationsFileShort {
            get { return stations_file_short != null && File.Exists(stations_file_short); }
        }

        public PopulatedSystems MakePopulatedSystems() {
            if (!HaveSystemsFile) {
                throw new InvalidOperationException("no local systems file downloaded");
            }

            return PopulatedSystems.FromFile(SystemsFile);
        }

        public Stations MakeStations() {
            if (!HaveStationsFile) {
                throw new InvalidOperationException("no local systems file downloaded");
            }

            TranslateStations();

            return Stations.FromFile(StationsFileShort);
        }
    }
}