using AxibugEmuOnline.Client.ClientCore;
using AxibugEmuOnline.Client.Tools;
using AxibugProtobuf;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace AxibugEmuOnline.Client
{
    /// <summary> 存档文件抽象类 </summary>
    public class SaveFile
    {
        public SavCloudApi CloudAPI => App.SavMgr.CloudApi;

        /// <summary> 指示该存档是否是自动存档 </summary>
        public bool AutoSave => SlotIndex == 0;
        /// <summary> 指示该存档所在槽位 </summary>
        public int SlotIndex { get; private set; }
        /// <summary> 指示该存档所属Rom的ID </summary>
        public int RomID { get; private set; }
        /// <summary> 指示该存档所属模拟器平台 </summary>
        public RomPlatformType EmuPlatform { get; private set; }

        /// <summary> 指示该存档是否为空 </summary>
        public bool IsEmpty { get; private set; }
        /// <summary> 存档文件路径 </summary>
        public string FilePath
        {
            get
            {
                var path = App.UserPersistenDataPath(EmuPlatform);
                path = $"{path}/Slot/{RomID}";

                Directory.CreateDirectory(path);

                var filePath = $"{path}/slot{SlotIndex}.SlotSav";
                return filePath;
            }
        }

        public event Action OnSavSuccessed;

        /// <summary> 存档顺序号,用于判断云存档和本地存档的同步状态,是否存在冲突 </summary>
        public uint Sequecen { get; private set; }

        SimpleFSM<SaveFile> FSM;
        byte[] m_savDataCaches;
        byte[] m_screenShotCaches;
        Header m_headerCache;
        bool m_cacheOutdate = true;

        public SaveFile(int romID, RomPlatformType platform, int slotIndex)
        {
            RomID = romID;
            EmuPlatform = platform;
            SlotIndex = slotIndex;
            FSM = new SimpleFSM<SaveFile>(this);
            FSM.AddState<UnkownState>();
            FSM.AddState<CheckingState>();
            FSM.AddState<DownloadingState>();
            FSM.AddState<UploadingState>();
            FSM.AddState<SyncedState>();

            IsEmpty = !File.Exists(FilePath);

            if (IsEmpty) Sequecen = 0;
            else //从文件头读取存储序号
            {
                byte[] saveOrderData = new byte[4];
                var streaming = File.OpenRead(FilePath);
                int res = streaming.Read(saveOrderData, 0, 4);
                if (res != 4) //无效的存档文件
                {
                    IsEmpty = true;
                    File.Delete(FilePath);
                }
                else
                {
                    Sequecen = BitConverter.ToUInt32(saveOrderData, 0);
                }

                streaming.Dispose();
            }

            FSM.ChangeState<UnkownState>();
        }

        public void Update()
        {
            FSM.Update();
        }

        /// <summary>
        /// 获得存档的保存时间(UTC)
        /// </summary>
        /// <returns></returns>
        public DateTime GetSavTimeUTC()
        {
            GetSavData(out _, out _);
            return new DateTime((long)m_headerCache.SavTicks, DateTimeKind.Utc);
        }

        public unsafe void GetSavData(out byte[] savData, out byte[] screenShotData)
        {
            if (!m_cacheOutdate)
            {
                savData = m_savDataCaches;
                screenShotData = m_screenShotCaches;
                return;
            }

            m_cacheOutdate = false;

            savData = null;
            screenShotData = null;

            if (!File.Exists(FilePath)) return;

            var raw = File.ReadAllBytes(FilePath);
            int headerSize = Marshal.SizeOf(typeof(Header));

            if (raw.Length < headerSize)
            {
                App.log.Warning("无效存档");
                return;
            }

            m_headerCache = new Header();
            fixed (Header* headPtr = &m_headerCache)
            {
                var headP = (byte*)headPtr;
                Marshal.Copy(raw, 0, (IntPtr)headP, sizeof(Header));
            }

            savData = new byte[m_headerCache.DataLength];
            Array.Copy(raw, headerSize, savData, 0, savData.Length);
            screenShotData = new byte[m_headerCache.ScreenShotLength];
            Array.Copy(raw, headerSize + savData.Length, screenShotData, 0, screenShotData.Length);

            m_savDataCaches = savData;
            m_screenShotCaches = screenShotData;

            return;
        }

        public unsafe void Save(uint sequence, byte[] savData, byte[] screenShotData)
        {
            var filePath = FilePath;

            var header = new Header
            {
                Sequence = sequence,
                RomID = RomID,
                SavTicks = (ulong)DateTime.UtcNow.Ticks,
                DataLength = (uint)savData.Length,
                ScreenShotLength = (uint)screenShotData.Length,
            };
            int headerSize = Marshal.SizeOf(typeof(Header));
            IntPtr ptr = Marshal.AllocHGlobal(headerSize);

            var totalSize = headerSize + savData.Length + screenShotData.Length;
            byte[] raw = new byte[totalSize];

            try
            {
                Marshal.StructureToPtr(header, ptr, false);
                Marshal.Copy(ptr, raw, 0, headerSize);
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }
            Array.Copy(savData, 0, raw, headerSize, savData.Length);
            Array.Copy(screenShotData, 0, raw, headerSize + savData.Length, screenShotData.Length);

            File.WriteAllBytes(filePath, raw);
            Sequecen = sequence;

            m_headerCache = header;
            m_savDataCaches = savData;
            m_screenShotCaches = screenShotData;

            IsEmpty = false;

            OnSavSuccessed?.Invoke();
        }

        /// <summary>
        /// 尝试同步存档
        /// </summary>
        public void TrySync()
        {
            if (FSM.CurrentState is not UnkownState && FSM.CurrentState is not SyncedState) return;

            FSM.ChangeState<CheckingState>();
        }


        [StructLayout(LayoutKind.Explicit, Size = 24)]
        struct Header
        {
            [FieldOffset(0)]
            public uint Sequence;
            [FieldOffset(4)]
            public int RomID;
            [FieldOffset(8)]
            public ulong SavTicks;
            [FieldOffset(16)]
            public uint DataLength;
            [FieldOffset(20)]
            public uint ScreenShotLength;
        }

        public enum EnumState
        {
            Unkown,
            Checking,
            Downloading,
            Uploading,
            Synced
        }
    }
}