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

namespace EliteBGS.BGS {
    public class NonaDiscordLog : IDiscordLogGenerator {
        private string FormatDate() {
            CultureInfo cultureInfo = CultureInfo.InvariantCulture;
            StringBuilder date = new StringBuilder();
            DateTime today = DateTime.Now;
            string suffix;

            if (today.Day == 1 || today.Day == 21 || today.Day == 31) {
                suffix = "st";
            } else if (today.Day == 2 || today.Day == 22) {
                suffix = "nd";
            } else {
                suffix = "th";
            }

            date.AppendFormat("{0} {1}{2}, {3}",
                cultureInfo.DateTimeFormat.GetMonthName(today.Month), 
                today.Day, suffix,
                today.Year + EliteDangerous.YearOffset
                );

            return date.ToString();
        }

        private string BuildCartoGraphics(Objective objective) {
            var total = from entries in objective.LogEntries
                        where entries.GetType() == typeof(Cartographics)
                        select entries
                        ;
            var pages = total.Count();
            var sum = total.Sum(x => (x as Cartographics).TotalSum);

            if (pages <= 0 || sum <= 0) {
                return "";
            }

            return string.Format("Sold {0} page(s) worth of universal cartographics\n" +
                                 "(Total value: {1})\n\n", 
                                 pages, Credits.FormatCredits(sum)
                                 );
        }

        private string BuildCargoSold(Objective objective) {
            StringBuilder builder = new StringBuilder();
            SellCargo[] sold = objective.LogEntries
                                .OfType<SellCargo>()
                                .ToArray()
                                ;

            if (sold == null && sold.Length > 0) {
                return builder.ToString();
            }


            // This groups everything together by cargo sold, and then by market sold to.
            //  Dictionary<string Cargo, Dictionary<string Market, { Market, Amount, Profit }> >
            var entries = sold.GroupBy(x => x.Cargo,
                                       (key, cargos) => new {
                                           Cargo = key,
                                           Markets = cargos.GroupBy(y => y.Market,
                                                               (market, markets) => new {
                                                                   Market = market,
                                                                   Amount = markets.Sum(x => x.Amount),
                                                                   Profit = markets.Sum(x => x.Profit)
                                                               })
                                       }
                                       )
                              ;

            foreach (var cargo in entries) {
                foreach (var market in cargo.Markets) {
                    builder.AppendFormat("Sold {0} {1} to the {2}",
                            market.Amount,
                            cargo.Cargo,
                            market.Market
                        );

                    if (market.Profit != 0) {
                        builder.AppendFormat(" ({0} {1})",
                            Credits.FormatCredits(market.Profit),
                            market.Profit < 0 ? "loss" : "profit"
                            );
                    }

                    builder.Append("\n");
                }
            }

            builder.AppendFormat("\n");

            return builder.ToString();
        }

        private string BuildMicroResourcesSold(Objective objective) {
            var total = from entries in objective.LogEntries
                        where entries.GetType() == typeof(SellMicroResources)
                        select entries
                        ;
            var sum = total.Sum(x => (x as SellMicroResources).TotalSum);

            if (sum <= 0) {
                return "";
            }

            return string.Format("Sold {0} worth of Micro Resources\n",
                Credits.FormatCredits(sum));
        }

        private string BuildKillBonds(Objective objective) {
            StringBuilder builder = new StringBuilder();
            FactionKillBonds[] bonds = objective.LogEntries
                                        .OfType<FactionKillBonds>()
                                        .ToArray()
                                        ;

            if (bonds == null || bonds.Length == 0) {
                return builder.ToString();
            }

            foreach (FactionKillBonds bond in bonds) {
                builder.AppendFormat("{0}\n", bond.ToString());
            }

            builder.AppendFormat("\n");

            return builder.ToString();
        }

        private string BuildVouchers(Objective objective) {
            StringBuilder builder = new StringBuilder();
            var missions = from entries in objective.LogEntries
                           where entries.GetType() == typeof(Vouchers)
                           select entries
                           ;

            if (missions == null || missions.Count() <= 0) {
                return "";
            }

            foreach (var mission in missions) {
                var m = mission as Vouchers;
                builder.AppendFormat("Handed in {0} vouchers for {1}\n", m.Type, m.Faction);
                builder.AppendFormat("(Total value: {0})\n", Credits.FormatCredits(m.TotalSum));
                builder.AppendFormat("\n");
            }

            return builder.ToString();
        }

        private string BuildMissionList(Objective objective) {
            Dictionary<string, Dictionary<string, int>> collated = new Dictionary<string, Dictionary<string, int>>();
            StringBuilder output = new StringBuilder();
            int total_influence = 0;

            var missions = from entries in objective.LogEntries
                           where entries.GetType() == typeof(MissionCompleted)
                           select entries
                           ;

            if (missions == null) {
                return "";
            }

            foreach (MissionCompleted m in missions) {
                if (!collated.ContainsKey(m.MissionName)) {
                    collated[m.MissionName] = new Dictionary<string, int>();
                }
                if (!collated[m.MissionName].ContainsKey(m.Influence)) {
                    collated[m.MissionName][m.Influence] = 0;
                }

                ++collated[m.MissionName][m.Influence];

                total_influence += m.Influence.Length;
            }

            foreach (var mission in collated) {
                if (objective.Faction != null) {
                    output.AppendFormat("{0} for {1}\n", mission.Key, objective.Faction);
                } else {
                    output.AppendFormat("{0}\n", mission.Key);
                }
                output.Append("(");
                foreach (var influence in mission.Value.OrderBy(x => x.Key.Length)) {
                    output.AppendFormat("Inf{0} x{1}, ", influence.Key, influence.Value);
                }
                output.Remove(output.Length - 2, 2); // remove last ", "
                output.Append(")\n\n");
            }

            var support = objective.LogEntries.OfType<InfluenceSupport>();
            foreach (InfluenceSupport inf in support) {
                output.Append(inf.ToString());
                output.Append("\n");
                total_influence += inf.Influence.Length;
            }

            if (support.Count() > 0) {
                output.Append("\n");
            }

            if (total_influence > 0) {
                output.AppendFormat("Total Influence: {0}\n\n", total_influence);
            }

            return output.ToString();
        }

        private string BuildCombatZones(Objective objective) {
            StringBuilder builder = new StringBuilder();
            CombatZone[] zones = objective.LogEntries
                .OfType<CombatZone>()
                .ToArray()
                ;

            if (zones == null || zones.Length == 0) {
                return builder.ToString();
            }

            foreach (CombatZone zone in zones) {
                builder.AppendFormat("{0}\n", zone.ToString());
            }

            builder.Append("\n");

            return builder.ToString();
        }
        private string BuildFailedMissions(Objective objective) {
            MissionFailed[] missions = objective.LogEntries.OfType<MissionFailed>().ToArray();
            StringBuilder builder = new StringBuilder();

            if (missions.Length <= 0) {
                return "";
            }

            foreach (MissionFailed failed in missions) {
                MissionFailedEntry f = failed.Failed;
                builder.AppendFormat("Failed {0} mission(s) \"{1}\" targeting {2}\n",
                    failed.Amount,
                    f.HumanReadableName == null ? f.Name : f.HumanReadableName,
                    failed.Faction
                    );
            }

            builder.Append("\n");

            return builder.ToString();
        }

        private string BuildMurders(Objective objective) {
            FoulMurder[] murders = objective.LogEntries.OfType<FoulMurder>().ToArray();
            StringBuilder builder = new StringBuilder();

            if (murders.Length <= 0) {
                return "";
            }

            foreach (FoulMurder murder in murders) {
                builder.Append(murder.ToString());
                builder.Append("\n");
            }

            builder.Append("\n");

            return builder.ToString();
        }

        private string BuildMarketBuy(Objective objective) {
            BuyCargo[] buys = objective.LogEntries.OfType<BuyCargo>().ToArray();
            StringBuilder builder = new StringBuilder();

            if (buys.Length <= 0) {
                return "";
            }

            foreach (BuyCargo buy in buys) {
                builder.Append(buy.ToString());
                builder.Append("\n");
            }

            builder.Append("\n");

            return builder.ToString();
        }

        public string GenerateDiscordLog(Report report) {
            StringBuilder log = new StringBuilder();

            log.AppendFormat(":clock2: `Date:` {0}\n", FormatDate());
            foreach (var objective in report.Objectives) {
                if (objective.LogEntries.Count <= 0) {
                    continue;
                }

                if (!objective.IsEnabled) {
                    continue;
                }

                log.AppendFormat(":globe_with_meridians: `Location:` {0}\n", objective.ToShortString());
                log.Append(":clipboard: `Conducted:`\n");
                log.Append("```\n");

                StringBuilder entries = new StringBuilder();

                var missions = BuildMissionList(objective);
                entries.Append(missions);

                var failed = BuildFailedMissions(objective);
                entries.Append(failed);

                var murders = BuildMurders(objective);
                entries.Append(murders);

                var vouchers = BuildVouchers(objective);
                entries.Append(vouchers);

                var zones = BuildCombatZones(objective);
                entries.Append(zones);

                var bonds = BuildKillBonds(objective);
                entries.Append(bonds);

                var carto = BuildCartoGraphics(objective);
                entries.Append(carto);

                var micro = BuildMicroResourcesSold(objective);
                entries.Append(micro);

                var buy = BuildMarketBuy(objective);
                entries.Append(buy);

                var sold = BuildCargoSold(objective);
                entries.Append(sold);

                log.Append(entries.ToString().Trim());
                log.Append("\n```\n");
            }

            return log.ToString();
        }

        public override string ToString() {
            return "Nova Navy Log";
        }
    }
}