using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Ookii.Dialogs.Wpf;
using EDPlayerJournal;
using EDPlayerJournal.BGS;
using EDPlayerJournal.Entries;
using EliteBGS.BGS;
using EliteBGS.Util;

namespace EliteBGS;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
    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 NonaDiscordLog(),
        new GenericDiscordLog(),
    };

    public MainWindow() {
        InitializeComponent();

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

        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;
        }

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

        // Set both to now
        startdate.SelectedDate = DateTime.Now;
        enddate.SelectedDate = DateTime.Now;
        journallocation.Text = Config.Global.JournalLocation;
    }

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

    private void HandleEntries(List<Entry> entries) {
        try {
            TransactionParser parser = new TransactionParser();
            List<Transaction> transactions = parser.Parse(entries);

            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.Objectives;
        } 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 Loadentries_EntriesLoaded(List<Entry> lines) {
        HandleEntries(lines);
    }

    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 ParseJournal_Click(object sender, RoutedEventArgs e) {
        try {
            TransactionParser parser = new TransactionParser();

            DateTime start = startdate.SelectedDate ?? DateTime.Now;
            DateTime end = enddate.SelectedDate ?? 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);
            List<Entry> entries = journal.Files
                .Where(f => f.NormalisedDateTime >= actualstart && f.NormalisedDateTime <= end)
                .SelectMany(e => e.Entries)
                .ToList()
                ;
            // Now further sort the list down to entries that are actually within the given datetime
            // Note that entry datetimes are not normalised, so we have to sort until end + 1 day
            DateTime actualend = end.AddDays(1);

            entries = entries
                .Where(e => e.Timestamp >= start && e.Timestamp < actualend)
                .ToList()
                ;

            HandleEntries(entries);
            GenerateLog();
        } 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(Objective)) {
            removed = report.Objectives.Remove(obj as Objective);
        } else if (obj.GetType() == typeof(UITransaction) ||
            obj.GetType().IsSubclassOf(typeof(UITransaction))) {
            foreach (Objective parent in report.Objectives) {
                if (parent.UITransactions.Remove(obj as UITransaction)) {
                    removed = true;
                }
            }
        }

        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);
    }

    /// <summary>
    /// Gets the currently selected objective, even if a log entry in said objective
    /// is selected instead. If nothing is selected, returns null.
    /// </summary>
    /// <returns></returns>
    private Objective GetSelectedObjective() {
        var obj = entries.SelectedItem;

        if (obj == null) {
            return null;
        }

        if (obj.GetType() == typeof(Objective)) {
            return obj as Objective;
        }

        // Some form of entry perhaps?
        if (obj.GetType().IsSubclassOf(typeof(Transaction))) {
            Transaction entry = obj as Transaction;
            Objective objective = entries.Items
                .OfType<Objective>()
                .First(x => x.Transactions.Contains(entry))
                ;

            return objective;
        }

        return null;
    }

    private void AddCombatZone_Click(object sender, RoutedEventArgs e) {
        Objective objective = GetSelectedObjective();

        if (objective == null) {
            return;
        }

        CombatZoneDialog dialog = new CombatZoneDialog() { Owner = this };

        if (!(dialog.ShowDialog() ?? false)) {
            return;
        }

        CombatZone zone = new CombatZone {
            Faction = objective.Faction,
            System = objective.System,

            Grade = dialog.Grade,
            Type = dialog.Type,
            Amount = dialog.Amount
        };

        objective.Transactions.Add(zone);
        RefreshView();
    }

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

    private void AdjustProfit_Click(object sender, RoutedEventArgs e) {
        if (entries.SelectedItem == null || entries.SelectedItem.GetType() != typeof(SellCargo)) {
            return;
        }

        SellCargo sell = entries.SelectedItem as SellCargo;
        AdjustProfitWindow adjust = new AdjustProfitWindow() { Owner = this };

        adjust.Profit.Text = sell.Profit.ToString();

        if (!(adjust.ShowDialog() ?? false)) {
            return;
        }
        
        if (int.TryParse(adjust.Profit.Text, out int newprofit)) {
            sell.Profit = newprofit;
            RefreshView();
        }
    }

    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;
    }
}
