using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using pkNX.Game;
using pkNX.Randomization;
using pkNX.Structures;
using pkNX.Structures.FlatBuffers.SWSH;

namespace pkNX.WinForms;

public sealed partial class SSWE : Form
{
    private readonly EncounterArchive Symbols;
    private readonly EncounterArchive Hidden;
    private readonly GameManagerSWSH ROM;
    private ulong entry;

    private readonly EncounterList8[] SL;

    public SSWE(GameManagerSWSH rom, EncounterArchive sym, EncounterArchive hid)
    {
        InitializeComponent();
        Symbols = sym;
        Hidden = hid;
        ROM = rom;

        var spec = rom.GetStrings(TextName.SpeciesNames);
        var species = (string[])spec.Clone();
        species[0] = "";
        EncounterList8.SpeciesNames = species;

        SL = new[] { SL_0, SL_1, SL_2, SL_3, SL_4, SL_5, SL_6, SL_7, SL_8, SL_9, SL_10 };
        foreach (var z in SL)
            z.Initialize();

        PG_Species.SelectedObject = EditUtil.Settings.Species;

        LoadLocations();
    }

    private class LocationHash
    {
        public ulong Hash { get; }
        public string LocationName { get; }

        public LocationHash(ulong hash, string loc)
        {
            Hash = hash;
            LocationName = loc;
        }
    }

    public bool Modified { get; private set; }

    public void LoadLocations()
    {
        static string GetLocationName(ulong id) => SWSHInfo.Zones.TryGetValue(id, out var zoneName) ? zoneName : id.ToString("X16");
        var sl = Symbols.EncounterTables
            .Select(area => new LocationHash(area.ZoneID, GetLocationName(area.ZoneID) + " [S]"));
        var hl = Hidden.EncounterTables
            .Select(area => new LocationHash(area.ZoneID, GetLocationName(area.ZoneID) + " [H]"));
        var locs = sl.Concat(hl)
            .OrderBy(z => z.LocationName)
            .ToArray();

        CB_Location.ValueMember = nameof(LocationHash.Hash);
        CB_Location.DisplayMember = nameof(LocationHash.LocationName);
        CB_Location.DataSource = new BindingSource(locs, null);

        CB_Location.SelectedIndex = 0;
    }

    private void CB_Location_SelectedIndexChanged(object sender, EventArgs e)
    {
        SaveEntry(entry);
        var item = (LocationHash)CB_Location.SelectedItem;
        entry = item.Hash;
        Debug.WriteLine($"Loading area data for [0x{entry:X16}] {item.LocationName}");
        L_Hash.Text = entry.ToString("X16");
        LoadEntry(entry);
    }

    private void LoadEntry(ulong zone)
    {
        Load(SL, Symbols, "Symbols");
        Load(SL, Hidden, "Hidden");
        void Load(EncounterList8[] arr, EncounterArchive arc, string name)
        {
            var table = arc.EncounterTables.FirstOrDefault(z => z.ZoneID == zone);
            if (table == null)
                return;

            L_Type.Text = name;

            var subs = table.SubTables;
            for (int i = 0; i < subs.Count; i++)
            {
                var t = subs[i];
                arr[i].NUD_Max.Value = t.LevelMax;
                arr[i].NUD_Min.Value = t.LevelMin;
                arr[i].LoadSlots(subs[i].Slots);
                arr[i].Visible = true;
            }

            // some tables don't have tree/fish
            for (int i = subs.Count; i < arr.Length; i++)
                arr[i].Visible = false;
        }
    }

    private void SaveEntry(ulong zone)
    {
        if (zone == 0)
            return;

        Save(SL, Symbols);
        Save(SL, Hidden);
        void Save(EncounterList8[] arr, EncounterArchive arc)
        {
            var table = arc.EncounterTables.FirstOrDefault(z => z.ZoneID == zone);
            if (table == null)
                return;

            var subs = table.SubTables;
            for (int i = 0; i < subs.Count; i++)
            {
                var t = subs[i];
                t.LevelMax = (byte)arr[i].NUD_Max.Value;
                t.LevelMin = (byte)arr[i].NUD_Min.Value;
                arr[i].SaveCurrent();
            }
        }
    }

    private void B_Save_Click(object sender, EventArgs e)
    {
        SaveEntry(entry);
        Modified = true;
        Close();
    }

    private void B_RandAll_Click(object sender, EventArgs e)
    {
        SaveEntry(entry);
        var settings = (SpeciesSettings)PG_Species.SelectedObject;
        var rand = new SpeciesRandomizer(ROM.Info, ROM.Data.PersonalData);

        var pt = ROM.Data.PersonalData;
        var ban = pt.Table.Take(ROM.Info.MaxSpeciesID + 1)
            .Select((z, i) => new { Species = i, Present = ((IPersonalInfoSWSH)z).IsPresentInGame })
            .Where(z => !z.Present).Select(z => z.Species).ToArray();

        rand.Initialize(settings, ban);
        RandomizeWild(rand, CHK_FillEmpty.Checked, CHK_Level.Checked);
        LoadEntry(entry);
        System.Media.SystemSounds.Asterisk.Play();
    }

    private void RandomizeWild(SpeciesRandomizer rand, bool fill, bool boost)
    {
        var pt = ROM.Data.PersonalData;
        var fr = new FormRandomizer(pt);
        var settings = (SpeciesSettings)PG_Species.SelectedObject;
        foreach (var area in Symbols.EncounterTables.Concat(Hidden.EncounterTables))
        {
            foreach (var sub in area.SubTables)
            {
                if (boost)
                {
                    sub.LevelMin = (byte)Legal.GetModifiedLevel(sub.LevelMin, (double)NUD_LevelBoost.Value);
                    sub.LevelMax = (byte)Legal.GetModifiedLevel(sub.LevelMax, (double)NUD_LevelBoost.Value);
                }
                ApplyRand(sub.Slots);
            }
        }

        void ApplyRand(IList<EncounterSlot> slots)
        {
            if (slots[0].Species == 0)
                return;

            for (int i = 0; i < slots.Count; i++)
            {
                var s = slots[i];
                if (s.Species == 0)
                {
                    if (!fill)
                        continue;
                    s.Species = slots.FirstOrDefault(z => z.Species != 0)?.Species ?? rand.GetRandomSpecies();
                    s.Form = 0; // ensure it's not junk
                }

                s.Species = rand.GetRandomSpecies(s.Species);
                s.Form = (byte)fr.GetRandomForm(s.Species, false, settings.AllowRandomFusions, ROM.Info.Generation, ROM.Data.PersonalData.Table);
                if (fill)
                    s.Probability = RandomScaledRates[slots.Count][i];
            }
        }
    }

    public static readonly Dictionary<int, byte[]> RandomScaledRates = new()
    {
        [01] = new byte[] { 100 },
        [04] = new byte[] { 60, 30, 7, 3 },
        [05] = new byte[] { 40, 30, 18, 10, 2 },
        [10] = new byte[] { 20, 15, 15, 10, 10, 10, 10, 5, 4, 1 },
    };

    private void TC_Tables_DrawItem(object sender, DrawItemEventArgs e)
    {
        var tc = (TabControl)sender;
        Graphics g = e.Graphics;

        // Get the item from the collection.
        TabPage _tabPage = tc.TabPages[e.Index];

        // Get the real bounds for the tab rectangle.
        Rectangle _tabBounds = tc.GetTabRect(e.Index);

        Brush _textBrush;
        if (e.State == DrawItemState.Selected)
        {
            // Draw a different background color, and don't paint a focus rectangle.
            _textBrush = new SolidBrush(Color.Red);
            g.FillRectangle(Brushes.Beige, e.Bounds);
        }
        else
        {
            _textBrush = new SolidBrush(e.ForeColor);
            e.DrawBackground();
        }

        // Use our own font.
        Font _tabFont = new("Arial", (float)10.0, FontStyle.Bold, GraphicsUnit.Pixel);

        // Draw string. Center the text.
        StringFormat _stringFlags = new()
        {
            Alignment = StringAlignment.Center,
            LineAlignment = StringAlignment.Center,
        };
        g.DrawString(_tabPage.Text, _tabFont, _textBrush, _tabBounds, new StringFormat(_stringFlags));
    }
}
