using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using PKHeX.Core;
using static PKHeX.Core.MessageStrings;

namespace PKHeX.WinForms
{
    public partial class SAV_Inventory : Form
    {
        private readonly SaveFile Origin;
        private readonly SaveFile SAV;

        public SAV_Inventory(SaveFile sav)
        {
            InitializeComponent();
            WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
            SAV = (Origin = sav).Clone();
            itemlist = GameInfo.Strings.GetItemStrings(SAV.Generation, SAV.Version).ToArray();

            for (int i = 0; i < itemlist.Length; i++)
            {
                if (string.IsNullOrEmpty(itemlist[i]))
                    itemlist[i] = $"(Item #{i:000})";
            }

            Pouches = SAV.Inventory;
            var item0 = Pouches[0].Items[0];
            HasFreeSpace = item0 is IItemFreeSpace;
            HasFreeSpaceIndex = item0 is IItemFreeSpaceIndex;
            HasFavorite = item0 is IItemFavorite;
            HasNew = item0 is IItemNew;

            CreateBagViews();
            LoadAllBags();
            ChangeViewedPouch(0);
        }

        private readonly IReadOnlyList<InventoryPouch> Pouches;
        private readonly bool HasFreeSpace;
        private readonly bool HasFreeSpaceIndex;
        private readonly bool HasFavorite;
        private readonly bool HasNew;

        // assume that all pouches have the same amount of columns
        private int ColumnItem;
        private int ColumnCount;
        private int ColumnFreeSpace;
        private int ColumnFreeSpaceIndex;
        private int ColumnFavorite;
        private int ColumnNEW;

        private readonly Dictionary<InventoryType, DataGridView> ControlGrids = new();
        private DataGridView GetGrid(InventoryType type) => ControlGrids[type];
        private DataGridView GetGrid(int pouch) => ControlGrids[Pouches[pouch].Type];

        private void B_Cancel_Click(object sender, EventArgs e) => Close();

        private void B_Save_Click(object sender, EventArgs e)
        {
            SetBags();
            SAV.Inventory = Pouches;
            Origin.CopyChangesFrom(SAV);
            Close();
        }

        private void CreateBagViews()
        {
            tabControl1.SizeMode = TabSizeMode.Fixed;
            tabControl1.ItemSize = new Size(IL_Pouch.Images[0].Width + 4, IL_Pouch.Images[0].Height + 4);
            foreach (var pouch in Pouches)
            {
                var tab = new TabPage {ImageIndex = (int)(pouch.Type - 1)};
                var dgv = GetDGV(pouch);
                ControlGrids.Add(pouch.Type, dgv);
                tab.Controls.Add(dgv);
                tabControl1.TabPages.Add(tab);
                tabControl1.ShowToolTips = true;
                tab.ToolTipText = pouch.Type.ToString();
            }
        }

        private DataGridView GetDGV(InventoryPouch pouch)
        {
            // Add DataGrid
            var dgv = GetBaseDataGrid(pouch);

            // Get Columns
            var item = GetItemColumn(ColumnItem = dgv.Columns.Count);
            dgv.Columns.Add(item);
            dgv.Columns.Add(GetCountColumn(pouch, Main.HaX, ColumnCount = dgv.Columns.Count));
            if (HasFavorite)
                dgv.Columns.Add(GetCheckColumn(ColumnFavorite = dgv.Columns.Count,"Fav"));
            if (HasNew)
                dgv.Columns.Add(GetCheckColumn(ColumnNEW = dgv.Columns.Count, "New"));

            if (HasFreeSpace)
                dgv.Columns.Add(GetCheckColumn(ColumnFreeSpace = dgv.Columns.Count, "Free"));
            if (HasFreeSpaceIndex)
                dgv.Columns.Add(GetCountColumn(pouch, true, ColumnFreeSpaceIndex = dgv.Columns.Count, "Free"));

            // Populate with rows
            var itemarr = Main.HaX ? itemlist : GetStringsForPouch(pouch.LegalItems);
            item.Items.AddRange(itemarr);

            var items = pouch.Items;
            if (items.Length != 0)
                dgv.Rows.Add(items.Length);
            dgv.CancelEdit();

            return dgv;
        }

        private static DataGridView GetBaseDataGrid(InventoryPouch pouch) => new()
        {
            Dock = DockStyle.Fill,
            Text = $"{pouch.Type}",
            Name = $"DGV_{pouch.Type}",

            AllowUserToAddRows = false,
            AllowUserToDeleteRows = false,
            AllowUserToResizeRows = false,
            AllowUserToResizeColumns = false,
            RowHeadersVisible = false,
            MultiSelect = false,
            ShowEditingIcon = false,

            EditMode = DataGridViewEditMode.EditOnEnter,
            ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single,
            ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize,
            SelectionMode = DataGridViewSelectionMode.CellSelect,
            CellBorderStyle = DataGridViewCellBorderStyle.None,
        };

        private static DataGridViewComboBoxColumn GetItemColumn(int c, string name = "Item") => new()
        {
            HeaderText = name,
            DisplayStyle = DataGridViewComboBoxDisplayStyle.Nothing,
            DisplayIndex = c,
            Width = 135,
            FlatStyle = FlatStyle.Flat,
        };

        private static DataGridViewCheckBoxColumn GetCheckColumn(int c, string name) => new()
        {
            HeaderText = name,
            DisplayIndex = c,
            Width = 40,
            FlatStyle = FlatStyle.Flat,
        };

        private static DataGridViewColumn GetCountColumn(InventoryPouch pouch, bool HaX, int c, string name = "Count")
        {
            var dgvIndex = new DataGridViewTextBoxColumn
            {
                HeaderText = name,
                DisplayIndex = c,
                Width = 45,
                DefaultCellStyle = {Alignment = DataGridViewContentAlignment.MiddleCenter},
            };
            if (!HaX)
                dgvIndex.MaxInputLength = (int)(Math.Log10(Math.Max(1, pouch.MaxCount)) + 1);
            return dgvIndex;
        }

        private void LoadAllBags()
        {
            foreach (var pouch in Pouches)
            {
                var dgv = GetGrid(pouch.Type);

                // Sanity Screen
                var invalid = Array.FindAll(pouch.Items, item => item.Index != 0 && !pouch.LegalItems.Contains((ushort)item.Index));
                var outOfBounds = Array.FindAll(invalid, item => item.Index >= itemlist.Length);
                var incorrectPouch = Array.FindAll(invalid, item => item.Index < itemlist.Length);

                if (outOfBounds.Length > 0)
                    WinFormsUtil.Error(MsgItemPouchUnknown, $"Item ID(s): {string.Join(", ", outOfBounds.Select(item => item.Index))}");
                if (!Main.HaX && incorrectPouch.Length > 0)
                    WinFormsUtil.Alert(string.Format(MsgItemPouchRemoved, pouch.Type), string.Join(", ", incorrectPouch.Select(item => itemlist[item.Index])), MsgItemPouchWarning);

                pouch.Sanitize(itemlist.Length - 1, Main.HaX);
                GetBag(dgv, pouch);
            }
        }

        private void SetBags()
        {
            foreach (var pouch in Pouches)
            {
                var dgv = GetGrid(pouch.Type);
                SetBag(dgv, pouch);
            }
        }

        private void GetBag(DataGridView dgv, InventoryPouch pouch)
        {
            for (int i = 0; i < dgv.Rows.Count; i++)
            {
                var item = pouch.Items[i];
                var cells = dgv.Rows[i].Cells;
                cells[ColumnItem].Value = itemlist[item.Index];
                cells[ColumnCount].Value = item.Count;

                if (item is IItemFreeSpace f)
                    cells[ColumnFreeSpace].Value = f.IsFreeSpace;
                if (item is IItemFreeSpaceIndex fi)
                    cells[ColumnFreeSpaceIndex].Value = fi.FreeSpaceIndex;
                if (item is IItemFavorite v)
                    cells[ColumnFavorite].Value = v.IsFavorite;
                if (item is IItemNew n)
                    cells[ColumnNEW].Value = n.IsNew;
            }
        }

        private void SetBag(DataGridView dgv, InventoryPouch pouch)
        {
            int ctr = 0;
            for (int i = 0; i < dgv.Rows.Count; i++)
            {
                var cells = dgv.Rows[i].Cells;
                var str = cells[ColumnItem].Value.ToString();
                var itemindex = Array.IndexOf(itemlist, str);

                if (itemindex <= 0 && !HasNew) // Compression of Empty Slots
                    continue;

                bool result = int.TryParse(cells[ColumnCount].Value?.ToString(), out int itemcnt);
                if (!result)
                    continue;
                if (!pouch.IsValidItemAndCount(SAV, itemindex, HasNew, Main.HaX, ref itemcnt))
                    continue; // ignore item

                // create clean item data when saving
                var item = pouch.GetEmpty(itemindex, itemcnt);
                if (item is IItemFreeSpace f)
                    f.IsFreeSpace = (bool)cells[ColumnFreeSpace].Value;
                if (item is IItemFreeSpaceIndex fi)
                    fi.FreeSpaceIndex = uint.TryParse(cells[ColumnFreeSpaceIndex].Value?.ToString(), out var fsi) ? fsi : 0;
                if (item is IItemFavorite v)
                    v.IsFavorite = (bool)cells[ColumnFavorite].Value;
                if (item is IItemNew n)
                    n.IsNew = (bool)cells[ColumnNEW].Value;

                pouch.Items[ctr] = item;
                ctr++;
            }
            for (int i = ctr; i < pouch.Items.Length; i++)
                pouch.Items[i] = pouch.GetEmpty(); // Empty Slots at the end
        }

        private void ChangeViewedPouch(int index)
        {
            var pouch = Pouches[index];
            NUD_Count.Maximum = GetMax(SAV, pouch, Main.HaX);

            bool disable = pouch.Type is InventoryType.PCItems or InventoryType.FreeSpace && SAV is not SAV8LA;
            NUD_Count.Visible = L_Count.Visible = B_GiveAll.Visible = !disable;
            if (disable && !Main.HaX)
            {
                giveMenu.Items.Remove(giveAll);
                giveMenu.Items.Remove(giveModify);
            }
            else if (!giveMenu.Items.Contains(giveAll))
            {
                giveMenu.Items.Insert(0, giveAll);
                giveMenu.Items.Add(giveModify);
            }
            NUD_Count.Value = Math.Max(1, pouch.MaxCount - 4);
        }

        private static int GetMax(ITrainerInfo sav, InventoryPouch pouch, bool HaX)
        {
            if (HaX)
                return pouch.MaxCount;

            return sav.Generation switch
            {
                // Cap at absolute maximum
                <= 2 => byte.MaxValue,
                >= 7 => pouch.MaxCount,
                _ => ushort.MaxValue,
            };
        }

        // Initialize String Tables
        private readonly string[] itemlist;

        private string[] GetStringsForPouch(ushort[] items, bool sort = true)
        {
            string[] res = new string[items.Length + 1];
            for (int i = 0; i < res.Length - 1; i++)
                res[i] = itemlist[items[i]];
            res[items.Length] = itemlist[0];
            if (sort)
                Array.Sort(res);
            return res;
        }

        // User Cheats
        private int CurrentPouch => tabControl1.SelectedIndex;
        private void SwitchBag(object sender, EventArgs e) => ChangeViewedPouch(CurrentPouch);
        private void B_GiveAll_Click(object sender, EventArgs e) => ShowContextMenuBelow(giveMenu, (Control)sender);
        private void B_Sort_Click(object sender, EventArgs e) => ShowContextMenuBelow(sortMenu, (Control)sender);

        private void SortByName(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByName(itemlist, reverse: sender == mnuSortNameReverse));
        private void SortByCount(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByCount(reverse: sender == mnuSortCountReverse));
        private void SortByIndex(object sender, EventArgs e) => ModifyPouch(CurrentPouch, p => p.SortByIndex(reverse: sender == mnuSortIndexReverse));

        private static void ShowContextMenuBelow(ToolStripDropDown c, Control n) => c.Show(n.PointToScreen(new Point(0, n.Height)));

        private void GiveAllItems(object sender, EventArgs e)
        {
            var pouch = Pouches[CurrentPouch];
            if (!GetModifySettings(pouch, out var truncate, out var shuffle))
                return;

            var items = pouch.LegalItems;
            if (truncate)
            {
                items = (ushort[])items.Clone();
                if (shuffle)
                    Util.Shuffle(items.AsSpan());
                Array.Resize(ref items, pouch.Items.Length);
            }

            ModifyPouch(CurrentPouch, p => p.GiveAllItems(SAV, items, (int)NUD_Count.Value));
            System.Media.SystemSounds.Asterisk.Play();
        }

        private static bool GetModifySettings(InventoryPouch pouch, out bool truncate, out bool shuffle)
        {
            truncate = false;
            shuffle = false;
            if (!pouch.IsCramped)
                return true;

            var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgItemPouchSizeSmall,
                string.Format(MsgItemPouchRandom, Environment.NewLine));
            if (dr == DialogResult.Cancel)
                return false;
            truncate = true;
            if (dr == DialogResult.No)
                shuffle = true;

            return true;
        }

        private void RemoveAllItems(object sender, EventArgs e)
        {
            ModifyPouch(CurrentPouch, p => p.RemoveAll());
            WinFormsUtil.Alert(MsgItemCleared);
        }

        private void ModifyAllItems(object sender, EventArgs e)
        {
            ModifyPouch(CurrentPouch, p => p.ModifyAllCount(SAV, (int)NUD_Count.Value));
            WinFormsUtil.Alert(MsgItemPouchCountUpdated);
        }

        private void ModifyPouch(int pouch, Action<InventoryPouch> func)
        {
            if (func == null)
                throw new ArgumentNullException(nameof(func));

            var dgv = GetGrid(pouch);
            var p = Pouches[pouch];
            SetBag(dgv, p); // save current
            func(p); // update
            GetBag(dgv, p); // load current
        }
    }
}
