using System;
using System.Collections.Generic;
using System.Linq;
using EDJournal;

namespace NonaBGS.BGS {
    public class Report {
        private List<Objective> objectives = new List<Objective>();

        public delegate void OnLogDelegate(string log);

        public event OnLogDelegate OnLog;

        public List<Objective> Objectives {
            get { return objectives; }
            set { objectives = value; }
        }

        public bool AddObjective(Objective objective) {
            var found = objectives.Find(x => x.CompareTo(objective) == 0);
            bool added = false;

            if (found == null) {
                objectives.Add(objective);
                added = true;
            }

            return added;
        }

        private bool IsRelevant(Entry e) {
            return e.Is(Events.MissionCompleted) ||
                e.Is(Events.Docked) ||
                e.Is(Events.FSDJump) ||
                e.Is(Events.MultiSellExplorationData) ||
                e.Is(Events.SellMicroResources) ||
                e.Is(Events.RedeemVoucher) ||
                e.Is(Events.FactionKillBond)
                ;
        }

        public void Scan(PlayerJournal journal, DateTime start, DateTime end) {
            var entries = from file in journal.Files
                          where file.NormalisedTimestamp >= start && file.NormalisedTimestamp <= end
                          select file.Entries
                          ;
            var relevant = from log in entries.SelectMany(array => array)
                           where IsRelevant(log)
                           select log
                           ;

            string current_system = null;
            string current_station = null;
            string controlling_faction = null;

            this.objectives.ForEach(x => x.LogEntries.Clear());

            foreach (var e in relevant) {
                LogEntry entry = null;
                bool collate = false;

                if (e.Is(Events.Docked)) {
                    /* gleem the current station from this message
                     */
                    current_station = (e as DockedEntry).StationName;
                } else if (e.Is(Events.FSDJump)) {
                    /* Gleem current system and controlling faction from this message.
                     */
                    current_system = (e as FSDJumpEntry).StarSystem;
                    controlling_faction = (e as FSDJumpEntry).SystemFaction;
                } else if (e.Is(Events.MissionCompleted)) {
                    var completed = e as MissionCompletedEntry;
                    entry = new MissionCompleted(completed, current_system, current_station);
                    if (completed.HumanReadableNameWasGenerated) {
                        /* If the human readable name was generated, we send a log message. Because the
                         * generated names all sort of suck, we should have more human readable names in
                         * in the lookup dictionary.
                         */
                        OnLog?.Invoke("Human readable name for mission \"" +
                            completed.Name +
                            "\" was generated, please report this.");
                    }
                } else if (e.Is(Events.MultiSellExplorationData)) {
                    /* For multi-sell-exploraton-data only the controlling faction of the station sold to matters.
                     */
                    entry = new Cartographics(e as MultiSellExplorationDataEntry, current_system, current_station);
                    entry.Faction = controlling_faction;
                } else if (e.Is(Events.RedeemVoucher)) {
                    /* Same for selling combat vouchers. Only the current controlling faction matters here.
                     */
                    entry = new Vouchers();
                    entry.Entries.Add(e);
                    entry.System = current_system;
                    entry.Station = current_station;
                    entry.Faction = controlling_faction;

                    collate = true;
                } else if (e.Is(Events.FactionKillBond)) {
                    entry = new FactionKillBonds();
                    entry.Entries.Add(e);
                    entry.System = current_system;
                    entry.Station = current_station;
                    entry.Faction = (e as FactionKillBondEntry).AwardingFaction;

                    collate = true;
                } else if (e.Is(Events.SellMicroResources)) {
                    entry = new SellMicroResources(current_system, current_station);
                    entry.Entries.Add(e);
                }

                if (entry == null) {
                    continue;
                }

                /* Find all objectives that generally match.
                 */
                var matches = objectives
                    .Where(x => x.Matches(entry) > 0)
                    .OrderBy(x => x.Matches(entry))
                    ;
                if (matches == null || matches.Count() <= 0) {
                     continue;
                }

                /* Then select the one that matches the most.
                 */
                var objective = matches
                    .OrderBy(x => x.Matches(entry))
                    .Reverse()
                    .First()
                    ;

                if (objective == null) {
                    continue;
                }

                LogEntry existing = null;

                try {
                    existing = objective.LogEntries.Find(x => x.CompareTo(entry) == 0);
                } catch (NotImplementedException) {
                    // Equivalent to not having found anything
                    existing = null;
                }
                if (collate && existing != null) {
                    existing.Entries.Add(e);
                } else if (!collate || existing == null) {
                    objective.LogEntries.Add(entry);
                }
            }
        }

        public void Scan(PlayerJournal journal) {
            Scan(journal, DateTime.Now, DateTime.Now);
        }
    }
}