using ModMenu;
using Func;
using System;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Collections.Generic;
using System.Linq;

namespace ModMenu {
    [StructLayout(LayoutKind.Sequential)]
    public struct Gear {
        public uint GearID;
        public uint UnlockedSlots;
        public uint AvailableSlots;
        public uint Slot1;
        public uint Slot2;
        public uint Slot3;
        public uint Unk1; // Default: 0x00000000
        public uint Unk2; // Default: 0x00000000
        public uint Unk3; // Default: 0x00000000
        public uint TimestampLastUsed;
        public uint HideNewIcon; // Enabled: 0x00010000 | Disabled: 0x00000000
        public uint Unk4; // Default: 0x00000000
    }

    public class GearQueueSlot {
        public string Class { get; set; }
        public uint Slot { get; set; }
        public byte[] Data { get; set; }
    }

    public class SaveEditor {
        // [[0x106E975C] + 0xC] + 0x61A0 # Start of Headgear Definitions
        // [[0x106E975C] + 0xC] + 0x31A0 # Start of Clothes Definitions
        // [[0x106E975C] + 0xC] + 0x1A0 # Start of Shoes Definitions
        // Each Gear class is 0x3000 big and each indivdual gear is 0x30 bytes (48 Bytes)
        // 0x3000 / 0x30 = 0x100 Slots (256 Slots)
        // 48 Bytes are split evenly according to struct in order above.
        public static uint SAVEDATAMGR_ROOT_PTR = 0x101E675C; // Begin of Save Data [0x101E675C] + 0xC

        public static string FetchAllGear() {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            uint GearSectionSize = 0x3000;
            var results = new Dictionary<string, List<Gear>>();
            int structSize = Marshal.SizeOf<Gear>();

            var categories = new (string Name, uint Offset)[] {
                ("headgear", 0x61A0),
                ("clothing",  0x31A0),
                ("shoes",    0x1A0)
            };

            foreach (var cat in categories) {
                var gearList = new List<Gear>();
                
                byte[] sectionRaw = Program.readBytes(p_save_start + cat.Offset, GearSectionSize);

                for (int i = 0; i < GearSectionSize; i += structSize) {
                    byte[] structBuffer = new byte[structSize];
                    Buffer.BlockCopy(sectionRaw, i, structBuffer, 0, structSize);

                    for (int j = 0; j < structBuffer.Length; j += 4) {
                        Array.Reverse(structBuffer, j, 4);
                    }
                    Gear item = ByteArrayToStructure<Gear>(structBuffer);

                    gearList.Add(item);
                }
                results.Add(cat.Name, gearList);
            }

            return JsonSerializer.Serialize(results, new JsonSerializerOptions {
                WriteIndented = true,
                IncludeFields = true
            });
        }

        public static void QueueGearSlot(uint Slot, string Class, uint GearID, uint Slot1, uint Slot2, uint Slot3) {
            if (Slot > 255) {
                Console.WriteLine("You can only write to Slot 0-255...");
                return;
            }

            Gear newGear = new Gear {
                GearID = GearID,
                UnlockedSlots = 4,
                AvailableSlots = 4,
                Slot1 = Slot1,
                Slot2 = Slot2,
                Slot3 = Slot3,
                Unk1 = 0,
                Unk2 = 0,
                Unk3 = 0,
                TimestampLastUsed = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                HideNewIcon = 0x00010000,
                Unk4 = 0
            };

            byte[] rawData = StructureToByteArray(newGear);
            for (int i = 0; i < rawData.Length; i += 4) {
                Array.Reverse(rawData, i, 4);
            }

            httpMenu.GearQueue.Add(new GearQueueSlot { Class = Class, Slot = Slot, Data = rawData });
        }

        public static void WriteGearQueue() {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            uint start_offset = 0;

            foreach (var Gear in httpMenu.GearQueue.ToList()) {
                switch (Gear.Class.ToLower()) {
                    case "headgear": start_offset = 0x61A0; break;
                    case "clothing": start_offset = 0x31A0; break;
                    case "shoes": start_offset = 0x1A0; break;
                    default:
                        Console.WriteLine("Invalid Class selected");
                        return;
                }

                Program.writeBytes((uint)(p_save_start + start_offset + (Gear.Slot * 0x30)), Gear.Data);
            }

        }

        public static void ChangeGender(uint gender) {
            if (gender > 2) {
                gender = 0;
            }
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0x190), gender);
        }

        public static void ChangeLevel(uint level) {
            uint ActualLevel = level - 1;
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0xA5A8), ActualLevel);
        }

        public static void ChangeCash(uint amount) {
            if (amount > 9999999) {
                amount = 9999999;
            }
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0xA5A0), amount);
        }

        public static void ChangeSeaSnails(uint amount) {
            if (amount > 999) {
                amount = 999;
            }
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0xA5B4), amount);
        }

        public static void ChangeEquippedHeadgear(uint WeaponSetID) {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0x188), WeaponSetID);
        }
        public static void ChangeEquippedClothes(uint ClothesID) {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0x180), ClothesID);
        }
        public static void ChangeEquippedShoes(uint ShoesID) {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0x184), ShoesID);
        }
        public static void ChangeEquippedWeapon(uint WeaponSetID) {
            var p_save_start = Program.readUInt32((uint)(Program.readUInt32(SAVEDATAMGR_ROOT_PTR) + 0xC));
            Program.writeUInt32((uint)(p_save_start + 0x18C), WeaponSetID);
        }

        private static T ByteArrayToStructure<T>(byte[] bytes) where T : struct {
            GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
            try {
                return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
            } finally {
                handle.Free();
            }
        }

        private static byte[] StructureToByteArray(object obj) {
            int len = Marshal.SizeOf(obj);
            byte[] arr = new byte[len];
            IntPtr ptr = Marshal.AllocHGlobal(len);
            try {
                Marshal.StructureToPtr(obj, ptr, true);
                Marshal.Copy(ptr, arr, 0, len);
            } finally {
                Marshal.FreeHGlobal(ptr);
            }
            return arr;
        }
    }
}
