using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using EDPlayerJournal;
using EDPlayerJournal.BGS;
using EDPlayerJournal.Entries;
using EliteBGS.BGS;
using EliteBGS.Util;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Diagnostics;

namespace EliteBGS;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow {
    private PlayerJournal journal;
    private Report report;

    public Config Config { get; set; } = new Config();

    private LoadEntriesWindow loadentries = null;

    private static readonly List<DiscordLogGenerator> logtypes = new List<DiscordLogGenerator>() {
        new GenericDiscordLog(),
        new NonaDiscordLog(),
        new OneLineDiscordLog(),
    };

    public MainWindow() {
        InitializeComponent();

        try {
            Config.LoadGlobal();
        } catch (Exception) {
            /* ignored */
        }

        Webhooks.ItemsSource = Config.Global.Webhooks;
        RefreshPostMenu();

        foreach (DiscordLogGenerator type in logtypes) {
            LogType.Items.Add(type);
        }

        string lastused = Config.Global.LastUsedDiscordTemplate;
        int lastindex = logtypes.FindIndex(x => x.ToString() == lastused);
        if (lastindex > -1) {
            LogType.SelectedIndex = lastindex;
        } else {
            LogType.SelectedIndex = 0;
        }

        this.NoInfluenceSupport.IsOn = Config.Global.IgnoreInfluenceSupport;
        this.NoMarketBuy.IsOn = Config.Global.IgnoreMarketBuy;
        this.NoFleetCarrier.IsOn = Config.Global.IgnoreFleetCarrier;

        // Apply theme
        try {
            AddCustomThemes();

            string[] colours = ThemeManager.Current.Themes
                .Select(x => x.ColorScheme)
                .DistinctBy(x => x)
                .OrderBy(x => x)
                .ToArray()
                ;

            foreach (var colour in colours) {
                Colour.Items.Add(colour);
                if (!string.IsNullOrEmpty(Config.Global.Colour) &&
                    string.Compare(colour, Config.Global.Colour, true) == 0) {
                    Colour.SelectedIndex = Colour.Items.Count - 1;
                }
            }

            if (Colour.SelectedIndex < 0) {
                Colour.SelectedIndex = 0;
            }

            SwitchTheme.IsOn = (string.Compare(Config.Global.Theme, "dark", true) == 0);

            ThemeManager.Current.ChangeTheme(this, Config.Global.FullTheme);
        } catch (Exception) {
            // Theme is invalid, revert back to our standard dark theme
            Config.Global.Colour = "Amber";
            Config.Global.Theme = "Dark";
        }

        journal = new PlayerJournal(Config.Global.JournalLocation);

        // Set both to now
        InitialiseTime();

        journallocation.Text = Config.Global.JournalLocation;
    }

    private void AddCustomThemes() {
        Dictionary<string, Color> colorThemes = new() {
            //{ "HouseSalus", Color.FromRgb(0xBC, 0x94, 0x39) },
            { "HouseSalus", Color.FromRgb(0xED, 0xDA, 0x70) },
            { "NovaNavy", Color.FromRgb(0xA1, 0xA4, 0xDB) },
            // Official Red of the Polish Flag
            { "PolskaGurom", Color.FromRgb(0xD4, 0x21, 0x3D) },
            // Official Blue in the Armenian Flag
            { "ArmeniaBlue", Color.FromRgb(0x00, 0x33, 0xA0) },
        };

        foreach (var colourtheme in colorThemes) {
            var brush = new SolidColorBrush(colourtheme.Value);

            // Add light theme
            ThemeManager.Current.AddTheme(new Theme(
                "Light." + colourtheme.Key,
                "Light." + colourtheme.Key,
                "Light",
                colourtheme.Key,
                colourtheme.Value,
                brush,
                true,
                false)
                );
            // Add dark theme
            ThemeManager.Current.AddTheme(new Theme(
                "Dark." + colourtheme.Key,
                "Dark." + colourtheme.Key,
                "Dark",
                colourtheme.Key,
                colourtheme.Value,
                brush,
                true,
                false)
                );
        }
    }

    private void InitialiseTime() {
        DateTime today = DateTime.Today;
        DateTime tomorrow = today.AddDays(1);

        // HOCHKULTUR
        startdate.Culture = enddate.Culture = CultureInfo.GetCultureInfo("de-AT");

        startdate.SelectedDateTime = today;
        enddate.SelectedDateTime = tomorrow;
    }

    private void TreeView_CheckBox_Updated(object sender, RoutedEventArgs args) {
        GenerateLog();
    }

    private void Report_OnLog(string message) {
        StringBuilder builder = new StringBuilder();

        builder.Append(DateTime.Now.ToString());
        builder.Append(": ");
        builder.Append(message);
        builder.Append("\n");

        log.AppendText(builder.ToString());
    }

    private void Log(string message) {
        Report_OnLog(message);
    }

    private void HandleEntries(List<Entry> entries, DateTime start, DateTime end) {
        try {
            TransactionParser parser = new();
            TransactionParserOptions options = new();

            options.IgnoreInfluenceSupport = Config.Global.IgnoreInfluenceSupport;
            options.IgnoreMarketBuy = Config.Global.IgnoreMarketBuy;
            options.IgnoreFleetCarrierFaction = Config.Global.IgnoreFleetCarrier;

            List<Transaction> transactions = parser.Parse(entries, options);

            Commanders.ItemsSource = parser.Commanders;
            if (Commanders.Items.Count > 0) {
                Commanders.SelectedIndex = 0;
            }

            // Filter the transactions down to the given time frame
            transactions = transactions
                .Where(t => t.CompletedAtDateTime >= start && t.CompletedAtDateTime <= end)
                .ToList()
                ;

            List<IncompleteTransaction> incompletes = transactions.OfType<IncompleteTransaction>().ToList();
            // Log incomplete and remove them from the results.
            foreach (var incomplete in incompletes) {
                Log(incomplete.Reason);
            }
            transactions.RemoveAll(x => incompletes.Contains(x));

            report = new Report(transactions);
            this.entries.ItemsSource = report.SystemObjectives;
        } catch (Exception exception) {
            Log("Something went terribly wrong while parsing the E:D player journal.");
            Log("Please send this to CMDR Hekateh:");
            Log(exception.ToString());
        }
    }

    private void HandleEntries(List<Entry> entries) {
        HandleEntries(entries, startdate.SelectedDateTime ?? DateTime.Now, enddate.SelectedDateTime ?? DateTime.Now);
    }

    private void Loadentries_EntriesLoaded(List<Entry> lines) {
        HandleEntries(lines);
    }

    private void ParseJournal_Click(object sender, RoutedEventArgs e) {
        try {
            TransactionParser parser = new TransactionParser();

            DateTime start = startdate.SelectedDateTime ?? DateTime.Now;
            DateTime end = enddate.SelectedDateTime ?? DateTime.Now;

            journal.Open(); // Load all files
            // Log files only get rotated if you restart the game client. This means that there might
            // be - say - entries from the 4th of May in the file with a timestamp of 3rd of May. This
            // happens if you happen to play a session late into the night.
            // At first I tried extracting the first and last line of a file to see the date range, but
            // if you have a lot of files this becomes quite slow, and quite the memory hog (as journal
            // files have to be read in their entirety to check this). So we assume that you can't play
            // three days straight, and keep the code fast.
            DateTime actualstart = start.AddDays(-3);
            DateTime actualend = end.AddDays(1);
            List<Entry> entries = journal.Files
                .Where(f => f.NormalisedDateTime >= actualstart && f.NormalisedDateTime <= actualend)
                .SelectMany(e => e.Entries)
                .ToList()
                ;

            HandleEntries(entries, start, end);
            GenerateLog();

            var errors = journal.AllErrors;
            foreach (var error in errors) {
                Log("An error has occured in the Journal file, please send this to CMDR Hekateh:");
                Log(error.ToString());
            }
        } catch (Exception exception) {
            Log("Something went terribly wrong while parsing the E:D player journal.");
            Log("Please send this to CMDR Hekateh:");
            Log(exception.ToString());
        }
    }

    private void GenerateLog() {
        try {
            DiscordLogGenerator discord = LogType.SelectedItem as DiscordLogGenerator;
            string report = discord.GenerateDiscordLog(this.report);

            DiscordLog.Text = report;
        } catch (Exception exception) {
            Log("Something went terribly wrong while generating the Discord log.");
            Log("Please send this to CMDR Hekateh:");
            Log(exception.ToString());
        }
    }

    private void GenerateDiscord_Click(object sender, RoutedEventArgs e) {
        GenerateLog();
    }

    private void RemoveCurrentObjective() {
        if (entries.SelectedItem == null) {
            return;
        }

        object obj = entries.SelectedItem;
        bool removed = false;

        if (obj.GetType() == typeof(SystemObjectives)) {
            removed = report.SystemObjectives.Remove(obj as SystemObjectives);
        } else if (obj.GetType() == typeof(Objective)) {
            report
                .SystemObjectives
                .ForEach(x => {
                    if (x.Objectives.Remove(obj as Objective)) {
                        removed = true;
                    }
                });
        } else if (obj.GetType() == typeof(UITransaction) ||
                   obj.GetType().IsSubclassOf(typeof(UITransaction))) {
            report
                .SystemObjectives
                .SelectMany(x => 
                    x.Objectives
                    .Where(x => x.UITransactions.Contains(obj as UITransaction))
                    )
                .ToList()
                .ForEach(x => removed = x.UITransactions.Remove(obj as UITransaction))
                ;
        }

        if (removed) {
            RefreshView();
        }
    }

    private void entries_KeyUp(object sender, KeyEventArgs e) {
        if (e.Key == Key.Delete) {
            RemoveCurrentObjective();
        }
    }

    private void browsejournallocation_Click(object sender, RoutedEventArgs e) {
        var dialog = new VistaFolderBrowserDialog();

        if ((bool)!dialog.ShowDialog()) {
            return;
        }

        Config.Global.JournalLocation = dialog.SelectedPath;
        journallocation.Text = Config.Global.JournalLocation;
        journal = new PlayerJournal(Config.Global.JournalLocation);
    }

    private Objective GetObjectiveFromControl(object sender) {
        Control control = sender as Control;
        if (control == null || control.DataContext == null) {
            return null;
        }

        return control.DataContext as Objective;
    }

    private void AddCombatZone_Click(object sender, RoutedEventArgs e) {
        Objective objective = GetObjectiveFromControl(sender);
        if (objective == null) {
            return;
        }

        CombatZone zone = new CombatZone {
            Faction = objective.Faction,
            System = objective.System,
            Grade = "Low",
            Type = "Ship",
        };

        UITransaction uitransaction = new UITransaction(zone);
        objective.UITransactions.Add(uitransaction);

        RefreshView();
    }

    private void RefreshView() {
        entries.Items.Refresh();
        GenerateLog();
    }

    private void LogType_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        if (LogType.SelectedItem == null) {
            return;
        }

        string template = LogType.SelectedItem.ToString();
        Config.Global.LastUsedDiscordTemplate = template;
        GenerateLog();
    }

    private void ManuallyParse_Click(object sender, RoutedEventArgs e) {
        if (loadentries != null) {
            loadentries.Show();
            return;
        }

        loadentries = new LoadEntriesWindow();
        loadentries.Closed += Loadentries_Closed;
        loadentries.EntriesLoaded += Loadentries_EntriesLoaded;
        loadentries.Show();
    }

    private void Loadentries_Closed(object sender, EventArgs e) {
        loadentries = null;
    }

    private void window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
        loadentries?.Close();
        loadentries = null;

        try {
            Config.SaveGlobal();
        } catch (Exception error) {
            MessageBox.Show("There was an error saving your settings: " + error.Message);
        }
    }

    private void Transaction_Initialized(object sender, EventArgs e) {
    }

    private TransactionType GetTransaction<TransactionType>(object sender) where TransactionType : Transaction {
        Control button = sender as Control;
        if (button == null || button.DataContext == null) {
            return null;
        }

        UITransaction transaction = button.DataContext as UITransaction;
        if (transaction == null) {
            return null;
        }

        return transaction.Transaction as TransactionType;
    }

    private void Low_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Grade = CombatZones.DifficultyLow;
        RefreshView();
    }

    private void Med_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Grade = CombatZones.DifficultyMedium;
        RefreshView();
    }

    private void High_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Grade = CombatZones.DifficultyHigh;
        RefreshView();
    }

    private void VeryHigh_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Grade = CombatZones.DifficultyVeryHigh;
        RefreshView();
    }

    private void Ground_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Type = CombatZones.GroundCombatZone;
        RefreshView();
    }

    private void Ship_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Type = CombatZones.ShipCombatZone;
        RefreshView();
    }

    private void Thargoid_Click(object sender, RoutedEventArgs e) {
        CombatZone transaction = GetTransaction<CombatZone>(sender);
        if (transaction == null) {
            return;
        }

        transaction.Type = CombatZones.AXCombatZone;
        RefreshView();
    }

    private void Profit_LostFocus(object sender, RoutedEventArgs e) {
        RefreshView();
    }

    private void Profit_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) {
        if (e.Key == Key.Enter) {
            RefreshView();
        }
    }

    private DateTime ResetTimeToZero(DateTime d) {
        DateTime obj = d;

        obj = obj.AddHours(d.Hour * -1);
        obj = obj.AddMinutes(d.Minute * -1);
        obj = obj.AddSeconds(d.Second * -1);

        return obj;
    }

    private void ResetTime_Click(object sender, RoutedEventArgs e) {
        DateTime? d = startdate.SelectedDateTime;
        if (d != null) {
            startdate.SelectedDateTime = ResetTimeToZero(d.Value);
        }

        d = enddate.SelectedDateTime;
        if (d != null) {
            enddate.SelectedDateTime = ResetTimeToZero(d.Value);
        }
    }

    private void ToggleAll_Click(object sender, RoutedEventArgs e) {
        ToggleButton button = sender as ToggleButton;
        Objective objective = GetObjectiveFromControl(sender);
        if (objective == null) {
            return;
        }

        objective.UITransactions
            .ForEach(x => x.IsEnabled = (button.IsChecked ?? true))
            ;
    }

    private void UpdateTheme() {
        ThemeManager.Current.ChangeTheme(this, Config.Global.FullTheme);
    }

    private void SwitchTheme_Toggled(object sender, RoutedEventArgs e) {
        ToggleSwitch toggle = sender as ToggleSwitch;
        if (toggle.IsOn) {
            Config.Global.Theme = "Dark";
        } else {
            Config.Global.Theme = "Light";
        }
        UpdateTheme();
    }

    private void Colour_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        if (Colour.SelectedItem == null) {
            return;
        }

        Config.Global.Colour = Colour.SelectedItem.ToString();
        UpdateTheme();
    }

    private void URL_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
        ProcessStartInfo info = new ProcessStartInfo();
        info.UseShellExecute = true;
        info.FileName = e.Uri.AbsoluteUri;
        Process.Start(info);
        e.Handled = true;
    }

    private void NoInfluenceSupport_Toggled(object sender, RoutedEventArgs e) {
        Config.Global.IgnoreInfluenceSupport = this.NoInfluenceSupport.IsOn;
    }

    private void NoMarketBuy_Toggled(object sender, RoutedEventArgs e) {
        Config.Global.IgnoreMarketBuy = this.NoMarketBuy.IsOn;
    }

    private void NoFleetCarrier_Toggled(object sender, RoutedEventArgs e) {
        Config.Global.IgnoreFleetCarrier = this.NoFleetCarrier.IsOn;
    }

    private void OpenInExplorer_Click(object sender, RoutedEventArgs e) {
        try {
            Process.Start(new ProcessStartInfo(Config.Global.JournalLocation) {
                UseShellExecute = true,
            });
        } catch (Exception) {
        }
    }

    private void SelectAll_Click(object sender, RoutedEventArgs e) {
        if (report == null) {
            return;
        }
        report.SystemObjectives.ForEach(t => { t.IsEnabled = (bool)SelectAll.IsChecked; });
    }

    private void AddWebHook_Click(object sender, RoutedEventArgs e) {
        Config.Global.Webhooks.Add(new DiscordWebhook {
            Name = "Discord Server Name",
            Webhook = "..."
        });
        Webhooks.Items.Refresh();
        RefreshPostMenu();
    }

    private void RemoveWebHook_Click(object sender, RoutedEventArgs e) {
        if (Webhooks.SelectedItems.Count <= 0) {
            return;
        }

        var selection = Webhooks.SelectedItems
            .OfType<DiscordWebhook>()
            .ToList()
            ;
        foreach (var item in selection) {
            Config.Global.Webhooks.Remove(item);
        }
        Webhooks.Items.Refresh();
        RefreshPostMenu();
    }

    private void Webhooks_KeyUp(object sender, KeyEventArgs e) {
        DataGridCell cell = e.OriginalSource as DataGridCell;
        /* We also get keypresses from DataGridCells that are currently
         * editing their content. Filter those out. We don't want to delete
         * the row when the user presses DEL while editing the cells content
         */
        if (cell == null || cell.IsEditing) {
            return;
        }
        if (e.Key == Key.Delete) {
            RemoveWebHook_Click(this, new RoutedEventArgs());
        }
    }

    private void Webhooks_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) {
        try {
            Config.SaveGlobal();
        } catch (Exception) { }
        e.Cancel = false;
        RefreshPostMenu();
    }

    private void RefreshPostMenu() {
        MenuItem menu;
        PostToDiscord.Items.Clear();
        if (Config.Global.Webhooks.Count <= 0) {
            PostToDiscord.IsEnabled = false;
        } else {
            PostToDiscord.IsEnabled = true;
            foreach (var item in Config.Global.Webhooks) {
                menu = new MenuItem();
                menu.Header = item.Name;
                menu.Click += DiscordWebhook_Click;
                PostToDiscord.Items.Add(menu);
            }
            PostToDiscord.Items.Add(new Separator());

            menu = new MenuItem();
            menu.Header = "All";
            menu.Click += PostToAll_Click;
            PostToDiscord.Items.Add(menu);
        }
    }

    private void PostToDiscordWebhook(IEnumerable<DiscordWebhook> hooks) {
        if (string.IsNullOrEmpty(DiscordLog.Text)) {
            return;
        }

        DiscordLogGenerator discord = LogType.SelectedItem as DiscordLogGenerator;
        string[] chunks;

        try {
            chunks = discord.SplitLog(DiscordLog.Text);
        } catch (Exception) {
            MessageBox.Show(
                "The log could not be split into discord appropriate length.\n" +
                "This happens with the bigger logs formats if you do lots of things in one system.\n" +
                "Try posting the log in the OneLine format.",
                "Sorry!", MessageBoxButton.OK, MessageBoxImage.Error
                );
            return;
        }

        string commander = "EDBGS";
        if (Commanders.SelectedIndex >= 0) {
            commander = Commanders.SelectedItem.ToString();
        }

        foreach (var hook in hooks) {
            try {
                foreach (var chunk in chunks) {
                    DiscordPoster.PostToDiscord(hook, chunk, commander);
                }
                Log(string.Format("successfully posted to discord webhook `{0}`", hook.Name));
            } catch (Exception ex) {
                Log(string.Format("failed to post to discord webhook `{0}`: {1}",
                    hook.Name, ex.Message));
            }
        }
    }

    private void DiscordWebhook_Click(object sender, RoutedEventArgs e) {
        MenuItem item = sender as MenuItem;
        if (item == null) {
            return;
        }
        DiscordWebhook hook = Config.Global.Webhooks
            .Find(x => string.Compare(x.Name, item.Header.ToString()) == 0)
            ;
        if (hook == null) {
            return;
        }
        PostToDiscordWebhook(new DiscordWebhook[] { hook });
    }

    private void PostToAll_Click(object sender, RoutedEventArgs e) {
        PostToDiscordWebhook(Config.Global.Webhooks);
    }
}
