using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
using PKHeX.Core;

namespace PKHeX.WinForms
{
    public partial class SAV_PokedexLA : Form
    {
        private readonly SAV8LA Origin;
        private readonly SAV8LA SAV;
        private readonly PokedexSave8a Dex;
        private readonly CheckBox[] CHK_SeenWild;
        private readonly CheckBox[] CHK_Obtained;
        private readonly CheckBox[] CHK_CaughtWild;

        private readonly Controls.PokedexResearchTask8aPanel[] TaskControls;

        private readonly int[] SpeciesToDex;
        private readonly int[] DexToSpecies;

        private int lastIndex = -1;
        private int lastForm = -1;
        private bool Editing;
        private readonly bool CanSave;

        private readonly IList<ComboItem> DisplayedForms;

        private readonly string[] TaskDescriptions = Util.GetStringList("tasks8a", Main.CurrentLanguage);
        private readonly string[] SpeciesQuests = Util.GetStringList("species_tasks8a", Main.CurrentLanguage);
        private readonly string[] TimeTaskDescriptions = Util.GetStringList("time_tasks8a", Main.CurrentLanguage);

        public SAV_PokedexLA(SAV8LA sav)
        {
            InitializeComponent();
            WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
            SAV = (SAV8LA)(Origin = sav).Clone();
            Dex = SAV.Blocks.PokedexSave;
            var speciesNames = GameInfo.Strings.Species;
            CHK_SeenWild = new[] { CHK_S0, CHK_S1, CHK_S2, CHK_S3, CHK_S4, CHK_S5, CHK_S6, CHK_S7 };
            CHK_Obtained = new[] { CHK_O0, CHK_O1, CHK_O2, CHK_O3, CHK_O4, CHK_O5, CHK_O6, CHK_O7 };
            CHK_CaughtWild = new[] { CHK_C0, CHK_C1, CHK_C2, CHK_C3, CHK_C4, CHK_C5, CHK_C6, CHK_C7 };

            TaskControls = new []
            {
                PRT_1,
                PRT_2,
                PRT_3,
                PRT_4,
                PRT_5,
                PRT_6,
                PRT_7,
                PRT_8,
                PRT_9,
                PRT_10,
            };

            foreach (var tc in TaskControls)
            {
                tc.Visible = false;
                tc.SetStrings(TaskDescriptions, SpeciesQuests, TimeTaskDescriptions);
            }

            SpeciesToDex = new int[SAV.Personal.MaxSpeciesID + 1];

            var maxDex = 0;
            for (var s = 1; s <= SAV.Personal.MaxSpeciesID; s++)
            {
                var hisuiDex = PokedexSave8a.GetDexIndex(PokedexType8a.Hisui, s);
                if (hisuiDex == 0)
                    continue;

                SpeciesToDex[s] = hisuiDex;
                if (hisuiDex > maxDex)
                    maxDex = hisuiDex;
            }

            DexToSpecies = new int[maxDex + 1];
            for (var s = 1; s <= SAV.Personal.MaxSpeciesID; s++)
            {
                if (SpeciesToDex[s] != 0)
                    DexToSpecies[SpeciesToDex[s]] = s;
            }

            Editing = true;
            // Clear Listbox and ComboBox
            LB_Species.Items.Clear();
            CB_Species.Items.Clear();

            // Fill List
            CB_Species.InitializeBinding();
            var species = GameInfo.FilteredSources.Species.Where(z => PokedexSave8a.GetDexIndex(PokedexType8a.Hisui, z.Value) != 0).ToArray();
            CB_Species.DataSource = new BindingSource(species, null);

            CB_DisplayForm.InitializeBinding();
            DisplayedForms = new List<ComboItem> { new(GameInfo.Strings.types[0], 0) };
            CB_DisplayForm.DataSource = new BindingSource(DisplayedForms, null);

            for (var d = 1; d < DexToSpecies.Length; d++)
                LB_Species.Items.Add($"{d:000} - {speciesNames[DexToSpecies[d]]}");

            Editing = false;
            LB_Species.SelectedIndex = 0;
            CB_Species.KeyDown += WinFormsUtil.RemoveDropCB;
            CanSave = true;
        }

        private void ChangeCBSpecies(object sender, EventArgs e)
        {
            if (Editing)
                return;

            var species = WinFormsUtil.GetIndex(CB_Species);
            var index = SpeciesToDex[species] - 1;
            if (LB_Species.SelectedIndex != index)
                LB_Species.SelectedIndex = index; // trigger event
        }

        private void ChangeLBSpecies(object sender, EventArgs e)
        {
            if (Editing || LB_Species.SelectedIndex < 0)
                return;

            SetEntry(lastIndex, lastForm);
            Editing = true;
            SuspendLayout();

            lastIndex = LB_Species.SelectedIndex;
            FillLBForms(lastIndex);
            FillResearchTasks(lastIndex);
            GetEntry(lastIndex, lastForm);
            ResumeLayout();
            Editing = false;
        }

        private void ChangeLBForms(object sender, EventArgs e)
        {
            if (Editing)
                return;

            SetEntry(lastIndex, lastForm);
            lastForm = LB_Forms.SelectedIndex;

            Editing = true;
            SuspendLayout();
            GetEntry(lastIndex, lastForm);
            ResumeLayout();
            Editing = false;
        }

        private bool FillLBForms(int index)
        {
            LB_Forms.DataSource = null;
            LB_Forms.Items.Clear();

            DisplayedForms.Clear();
            DisplayedForms.Add(new ComboItem(GameInfo.Strings.types[0], 0));
            CB_DisplayForm.DataSource = new BindingSource(DisplayedForms, null);

            lastForm = 0;

            int species = DexToSpecies[index + 1];
            bool hasForms = FormInfo.HasFormSelection(SAV.Personal[species], species, 8);
            LB_Forms.Enabled = CB_DisplayForm.Enabled = hasForms;
            if (!hasForms)
                return false;

            var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Main.GenderSymbols, SAV.Generation).ToList();
            if (ds.Count == 1 && string.IsNullOrEmpty(ds[0]))
            {
                // empty
                LB_Forms.Enabled = CB_DisplayForm.Enabled = false;
                return false;
            }

            // Sanitize forms to only show entries with form storage
            var formCount = SAV.Personal[species].FormCount;
            var sanitized = new List<string>();
            DisplayedForms.Clear();
            for (var form = 0; form < formCount; form++)
            {
                if (!Dex.HasFormStorage(species, form) || Dex.IsBlacklisted(species, form))
                    continue;

                sanitized.Add(ds[form]);
                DisplayedForms.Add(new ComboItem(ds[form], form));
            }

            CB_DisplayForm.DataSource = new BindingSource(DisplayedForms, null);
            LB_Forms.DataSource = sanitized;
            LB_Forms.SelectedIndex = 0;

            return true;
        }

        private void FillResearchTasks(int index)
        {
            var species = DexToSpecies[index + 1];
            var tasks = PokedexConstants8a.ResearchTasks[index];

            for (var i = 0; i < tasks.Length; i++)
            {
                var tc = TaskControls[i];
                tc.Visible = false;
                Dex.GetResearchTaskLevel(species, i, out var repLevel, out _, out _);
                tc.SetTask(species, tasks[i], repLevel);
                tc.Visible = true;
            }

            for (var i = tasks.Length; i < TaskControls.Length; i++)
                TaskControls[i].Visible = false;
        }

        private void GetEntry(int index, int formIndex)
        {
            var species = DexToSpecies[index + 1];
            var form = DisplayedForms[formIndex].Value;

            // Flags
            var seenWild = Dex.GetPokeSeenInWildFlags(species, form);
            var obtain = Dex.GetPokeObtainFlags(species, form);
            var caughtWild = Dex.GetPokeCaughtInWildFlags(species, form);
            CHK_Solitude.Checked = Dex.GetSolitudeComplete(species);
            for (var i = 0; i < CHK_SeenWild.Length; ++i)
            {
                CHK_SeenWild[i].Checked = (seenWild & (1 << i)) != 0;
                CHK_Obtained[i].Checked = (obtain & (1 << i)) != 0;
                CHK_CaughtWild[i].Checked = (caughtWild & (1 << i)) != 0;
            }

            // Display
            if (CB_DisplayForm.Enabled)
            {
                var selectedForm = Dex.GetSelectedForm(species);
                CB_DisplayForm.SelectedIndex = 0;
                for (var i = 0; i < CB_DisplayForm.Items.Count; ++i)
                {
                    if (((ComboItem)CB_DisplayForm.Items[i]).Value != selectedForm)
                        continue;

                    CB_DisplayForm.SelectedIndex = i;
                    break;
                }
            }

            CHK_A.Checked = Dex.GetSelectedAlpha(species);
            CHK_S.Checked = Dex.GetSelectedShiny(species);

            CHK_G.Enabled = PokedexSave8a.HasMultipleGenders(species);
            CHK_G.Checked = Dex.GetSelectedGender1(species);

            // Research
            var reportedRate = Dex.GetPokeResearchRate(species);
            var unreportedRate = reportedRate;
            for (var i = 0; i < PokedexConstants8a.ResearchTasks[index].Length; i++)
            {
                var unreportedLevels = Dex.GetResearchTaskLevel(species, i, out _, out var taskValue, out _);
                TaskControls[i].CurrentValue = taskValue;
                unreportedRate += unreportedLevels * TaskControls[i].PointsPerLevel;
            }

            MTB_UpdateIndex.Text = Dex.GetUpdateIndex(species).ToString();
            MTB_ResearchLevelReported.Text = reportedRate.ToString();
            MTB_ResearchLevelUnreported.Text = unreportedRate.ToString();

            CHK_Seen.Checked = Dex.HasPokeEverBeenUpdated(species);
            CHK_Complete.Checked = Dex.IsComplete(species);
            CHK_Perfect.Checked = Dex.IsPerfect(species);

            // Statistics
            Dex.GetSizeStatistics(species, form, out var hasMax, out var minHeight, out var maxHeight, out var minWeight, out var maxWeight);
            CHK_MinAndMax.Checked = hasMax;
            TB_MinHeight.Text = minHeight.ToString(CultureInfo.InvariantCulture);
            TB_MaxHeight.Text = maxHeight.ToString(CultureInfo.InvariantCulture);
            TB_MinWeight.Text = minWeight.ToString(CultureInfo.InvariantCulture);
            TB_MaxWeight.Text = maxWeight.ToString(CultureInfo.InvariantCulture);

            var pt = SAV.Personal;
            var pi = pt.GetFormEntry(species, form);
            var minTheoryHeight = PA8.GetHeightAbsolute(pi, 0x00).ToString(CultureInfo.InvariantCulture);
            var maxTheoryHeight = PA8.GetHeightAbsolute(pi, 0xFF).ToString(CultureInfo.InvariantCulture);
            var minTheoryWeight = PA8.GetWeightAbsolute(pi, 0x00, 0x00).ToString(CultureInfo.InvariantCulture);
            var maxTheoryWeight = PA8.GetWeightAbsolute(pi, 0xFF, 0xFF).ToString(CultureInfo.InvariantCulture);

            L_TheoryHeight.Text = $"Min: {minTheoryHeight}, Max: {maxTheoryHeight}";
            L_TheoryWeight.Text = $"Min: {minTheoryWeight}, Max: {maxTheoryWeight}";
        }

        private bool IsEntryEmpty(int index, int formIndex)
        {
            var species = DexToSpecies[index + 1];
            var form = DisplayedForms[formIndex].Value;

            // Any seen/obtain flags
            for (var i = 0; i < CHK_SeenWild.Length; i++)
            {
                if (CHK_SeenWild[i].Checked)
                    return false;
                if (CHK_Obtained[i].Checked)
                    return false;
                if (CHK_CaughtWild[i].Checked)
                    return false;
            }

            // Any display flags
            if ((CHK_G.Enabled && CHK_G.Checked) || CHK_S.Checked || CHK_A.Checked)
                return false;

            // Any research
            for (var i = 0; i < PokedexConstants8a.ResearchTasks[index].Length; i++)
            {
                Dex.GetResearchTaskLevel(species, i, out var reportedLevels, out _, out _);
                if (reportedLevels > 1)
                    return false;
                if (TaskControls[i].CurrentValue != 0)
                    return false;
            }

            if (CHK_Complete.Checked || CHK_Perfect.Checked)
                return false;

            // Any statistics
            Dex.GetSizeStatistics(species, form, out _, out var oldMinHeight, out var oldMaxHeight, out var oldMinWeight, out var oldMaxWeight);

            if (!float.TryParse(TB_MinHeight.Text, out var minHeight))
                minHeight = oldMinHeight;

            if (!float.TryParse(TB_MaxHeight.Text, out var maxHeight))
                maxHeight = oldMaxHeight;

            if (!float.TryParse(TB_MinWeight.Text, out var minWeight))
                minWeight = oldMinWeight;

            if (!float.TryParse(TB_MaxWeight.Text, out var maxWeight))
                maxWeight = oldMaxWeight;

            if (CHK_MinAndMax.Checked)
                return false;

            return minHeight == 0 && maxHeight == 0 && minWeight == 0 && maxWeight == 0;
        }

        private void SetEntry(int index, int formIndex)
        {
            if (!CanSave || Editing || index < 0 || formIndex < 0)
                return;

            var empty = IsEntryEmpty(index, formIndex);

            if (!CHK_Seen.Checked && empty)
                return;

            var species = DexToSpecies[index + 1];
            var form = DisplayedForms[formIndex].Value;

            if (!empty)
                Dex.SetPokeHasBeenUpdated(species);

            // Flags
            var seenWild = 0;
            var obtain = 0;
            var caughtWild = 0;
            for (var i = 0; i < CHK_SeenWild.Length; i++)
            {
                seenWild |= CHK_SeenWild[i].Checked ? (1 << i) : 0;
                obtain |= CHK_Obtained[i].Checked ? (1 << i) : 0;
                caughtWild |= CHK_CaughtWild[i].Checked ? (1 << i) : 0;
            }

            Dex.SetPokeSeenInWildFlags(species, form, (byte)seenWild);
            Dex.SetPokeObtainFlags(species, form, (byte)obtain);
            Dex.SetPokeCaughtInWildFlags(species, form, (byte)caughtWild);
            Dex.SetSolitudeComplete(species, CHK_Solitude.Checked);

            // Display
            var dispForm = form;
            if (CB_DisplayForm.Enabled)
                dispForm = WinFormsUtil.GetIndex(CB_DisplayForm);

            Dex.SetSelectedGenderForm(species, dispForm, CHK_G.Checked, CHK_S.Checked, CHK_A.Checked);

            // Set research
            for (var i = 0; i < PokedexConstants8a.ResearchTasks[index].Length; i++)
            {
                if (TaskControls[i].CanSetCurrentValue)
                    Dex.SetResearchTaskProgressByForce(species, TaskControls[i].Task, TaskControls[i].CurrentValue);
            }

            // Statistics
            Dex.GetSizeStatistics(species, form, out _, out var oldMinHeight, out var oldMaxHeight, out var oldMinWeight, out var oldMaxWeight);

            if (!float.TryParse(TB_MinHeight.Text, out var minHeight))
                minHeight = oldMinHeight;

            if (!float.TryParse(TB_MaxHeight.Text, out var maxHeight))
                maxHeight = oldMaxHeight;

            if (!float.TryParse(TB_MinWeight.Text, out var minWeight))
                minWeight = oldMinWeight;

            if (!float.TryParse(TB_MaxWeight.Text, out var maxWeight))
                maxWeight = oldMaxWeight;

            Dex.SetSizeStatistics(species, form, CHK_MinAndMax.Checked, minHeight, maxHeight, minWeight, maxWeight);
        }

        private void B_Cancel_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void B_Save_Click(object sender, EventArgs e)
        {
            SetEntry(lastIndex, lastForm);
            Origin.CopyChangesFrom(SAV);
            Close();
        }

        private void CHK_ObtainFlag_Changed(object sender, EventArgs e)
        {
            if (Editing)
                return;

            var overrideObtainFlags = 0;
            for (var i = 0; i < CHK_Obtained.Length; i++)
            {
                if (CHK_Obtained[i].Checked)
                    overrideObtainFlags |= (1 << i);
            }

            var tasks = PokedexConstants8a.ResearchTasks[lastIndex];
            var species = DexToSpecies[lastIndex + 1];
            var form = DisplayedForms[lastForm].Value;

            SuspendLayout();
            for (var i = 0; i < tasks.Length; i++)
            {
                if (tasks[i].Task != PokedexResearchTaskType8a.ObtainForms)
                    continue;

                var formCount = Dex.GetObtainedFormCounts(species, form | (overrideObtainFlags << 16));
                var tc = TaskControls[i];
                if (tc.CurrentValue != formCount)
                    tc.CurrentValue = formCount;
            }
            ResumeLayout();
        }

        private void B_Report_Click(object sender, EventArgs e)
        {
            // Set the entry
            SetEntry(lastIndex, lastForm);

            Editing = true;
            SuspendLayout();

            // Perform a report on the specific species
            var species = DexToSpecies[lastIndex + 1];
            Dex.UpdateSpecificReportPoke(species, out _);

            // Refresh all tasks
            FillResearchTasks(lastIndex);

            // Refresh all values
            GetEntry(lastIndex, lastForm);
            ResumeLayout();
            Editing = false;
            System.Media.SystemSounds.Asterisk.Play();
        }

        private void B_AdvancedResearch_Click(object sender, EventArgs e)
        {
            // Set the entry
            SetEntry(lastIndex, lastForm);

            // Show detailed editor form
            using var form = new SAV_PokedexResearchEditorLA(SAV, DexToSpecies[lastIndex + 1], lastIndex, TaskDescriptions, TimeTaskDescriptions);
            form.ShowDialog();

            Editing = true;
            SuspendLayout();

            // Refresh all tasks
            FillResearchTasks(lastIndex);

            // Refresh all values
            GetEntry(lastIndex, lastForm);
            ResumeLayout();
            Editing = false;
        }
    }
}
