using Essgee.Emulation;
using Essgee.Exceptions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;

namespace Essgee.Metadata
{
    /// <summary>
    /// 单独定义meta加载接口
    /// </summary>
    public interface IGameMetaReources
    {
        public bool GetCartMetadataDatabase(out string loadedData);
        public bool GetDatBytes(string DatName, out byte[] loadedData);
    }

    public class GameMetadataHandler
    {
        public static GameMetadataHandler instance;
        //static string datDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "No-Intro");
        //static string metadataDatabaseFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "MetadataDatabase.json");

        public IGameMetaReources gameMetaReources;
        //readonly Dictionary<string, DatFile> datFiles;
        readonly List<CartridgeJSON> cartMetadataDatabase;

        //public int NumKnownSystems { get { return datFiles.Count; } }
        //public int NumKnownGames { get { return datFiles.Sum(x => x.Value.Game.Count()); } }

        public GameMetadataHandler(IGameMetaReources metaresources)
        {
            instance = this;
            gameMetaReources = metaresources;

            //if (!gameMetaReources.GetCartMetadataDatabase(out string loadedData))
            //    throw new HandlerException("CartMetadataDatabase file not found");

            //cartMetadataDatabase = JsonConvert.DeserializeObject<List<CartridgeJSON>>(loadedData);
            cartMetadataDatabase = new List<CartridgeJSON>()
            {
                new CartridgeJSON(){
    Name = "The Castle (SG-1000)",
    Notes = "8k volatile RAM",
    Crc32 = 0x092F29D6,
    RomSize = 32768,
    RamSize = 8192
  },
  new CartridgeJSON(){
    Name = "Othello (SG-1000)",
    Notes = "2k volatile RAM",
    Crc32 = 0xAF4F14BC,
    RomSize = 32768,
    RamSize = 2048
  },
  new CartridgeJSON(){
    Name = "Sega Basic Level II (SC-3000)",
    Notes = "2k volatile RAM (for Level IIb)",
    Crc32 = 0xF691F9C7,
    RomSize = 32768,
    RamSize = 2048
  },
  new CartridgeJSON(){
    Name = "Sega Basic Level III (SC-3000)",
    Notes = "32k volatile RAM (for Level IIIb)",
    Crc32 = 0x5D9F11CA,
    RomSize = 32768,
    RamSize = 32768
  },
  new CartridgeJSON(){
    Name = "Back to the Future 2 (SMS)",
    Notes = "PAL only",
    Crc32 = 0xE5FF50D8,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Back to the Future 3 (SMS)",
    Notes = "PAL only",
    Crc32 = 0x2D48C1D3,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "California Games 2 (SMS, Europe)",
    Notes = "PAL only",
    Crc32 = 0xC0E25D62,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Chase HQ (SMS)",
    Notes = "PAL only",
    Crc32 = 0x85CFC9C9,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Cosmic Spacehead (SMS)",
    Notes = "Codemasters mapper & PAL only",
    Crc32 = 0x29822980,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Dinobasher (SMS)",
    Notes = "Codemasters mapper",
    Crc32 = 0xEA5C3A6F,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Excellent Dizzy Collection (SMS)",
    Notes = "Codemasters mapper & PAL only",
    Crc32 = 0x8813514B,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Fantastic Dizzy (SMS)",
    Notes = "Codemasters mapper & PAL only",
    Crc32 = 0xB9664AE1,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Home Alone (SMS)",
    Notes = "PAL only",
    Crc32 = 0xC9DBF936,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Micro Machines (SMS)",
    Notes = "Codemasters mapper & PAL only",
    Crc32 = 0xA577CE46,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "NewZealand Story (SMS)",
    Notes = "PAL only",
    Crc32 = 0xC660FF34,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Power Strike 2 (SMS)",
    Notes = "PAL only",
    Crc32 = 0xA109A6FE,
    RomSize = 524288,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Predator 2 (SMS, Europe)",
    Notes = "PAL only",
    Crc32 = 0x0047B615,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Sonic the Hedgehog 2 (SMS)",
    Notes = "PAL only",
    Crc32 = 0x5B3B922C,
    RomSize = 524288,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Sonic the Hedgehog 2 (SMS, Revision 1)",
    Notes = "PAL only",
    Crc32 = 0xD6F2BFCA,
    RomSize = 524288,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Space Harrier (SMS, Europe)",
    Notes = "PAL only",
    Crc32 = 0xCA1D3752,
    RomSize = 262144,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "CJ Elephant Fugitive (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0x72981057,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Cosmic Spacehead (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0x6CAA625B,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Dropzone (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0x152F0DCC,
    RomSize = 131072,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Ernie Els Golf (GG)",
    Notes = "Codemasters mapper & 8k volatile RAM",
    Crc32 = 0x5E53C7F7,
    RomSize = 262144,
    RamSize = 8192,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Micro Machines (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0xF7C524F6,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Micro Machines 2: Turbo Tournament (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0xDBE8895C,
    RomSize = 524288,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Pete Sampras Tennis (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0xC1756BEE,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Pop Breaker (GG, Japan)",
    Notes = "Domestic/Japan only",
    Crc32 = 0x71DEBA5A,
    RomSize = 131072,
    PreferredRegion = Region.Domestic
  },
  new CartridgeJSON(){
    Name = "S.S. Lucifer: Man Overboard (GG)",
    Notes = "Codemasters mapper",
    Crc32 = 0xD9A7F170,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.CodemastersCartridge),
  },
  new CartridgeJSON(){
    Name = "Sonic Chaos (SMS, Jun 30 1993 Prototype)",
    Notes = "Disallow memory control",
    Crc32 = 0xD3AD67FA,
    RomSize = 524288,
    AllowMemoryControl = false
  },
  new CartridgeJSON(){
    Name = "94 Super World Cup Soccer (SMS)",
    Notes = "Korean mapper",
    Crc32 = 0x060D6A7C,
    RomSize = 262144,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Jang Pung II (SMS)",
    Notes = "Korean mapper",
    Crc32 = 0x929222C4,
    RomSize = 524288,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Jang Pung 3 (SMS)",
    Notes = "Korean mapper",
    Crc32 = 0x18FB98A3,
    RomSize = 1048576,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Janggun-ui Adeul (SMS)",
    Notes = "Korean sprite-flip mapper",
    Crc32 = 0x192949D5,
    RomSize = 524288,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanSpriteMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Sangokushi 3 (SMS)",
    Notes = "Korean mapper",
    Crc32 = 0x97D03541,
    RomSize = 1048576,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "4 Pak All Action (SMS)",
    Notes = "4 Pak mapper",
    Crc32 = 0xA67F2A5C,
    RomSize = 1048576,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.Multicart4PakAllActionCartridge),
  },
  new CartridgeJSON(){
    Name = "Cyborg Z (SMS)",
    Notes = "Korean MSX 8k mapper",
    Crc32 = 0x77EFE84A,
    RomSize = 131072,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMSX8kMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Street Master (SMS)",
    Notes = "Korean MSX 8k mapper",
    Crc32 = 0x83F0EEDE,
    RomSize = 131072,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMSX8kMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "Wonsiin (SMS)",
    Notes = "Korean MSX 8k mapper",
    Crc32 = 0xA05258F5,
    RomSize = 131072,
    Mapper = typeof(Essgee.Emulation.Cartridges.Sega.KoreanMSX8kMapperCartridge),
  },
  new CartridgeJSON(){
    Name = "SMS Bad Apple 1.00 (SMS)",
    Notes = "PAL only",
    Crc32 = 0x38434560,
    RomSize = 4194304,
    PreferredTVStandard = TVStandard.PAL
  },
  new CartridgeJSON(){
    Name = "Be No Sqr 1.01 (SMS)",
    Notes = "PAL only",
    Crc32 = 0xEE701BE6,
    RomSize = 524288,
    PreferredTVStandard = TVStandard.PAL
  }
            };

            //改为接口直接读取
            //XmlRootAttribute root;
            //XmlSerializer serializer;

            ///* Read No-Intro .dat files */

            //datFiles = new Dictionary<string, DatFile>();
            //foreach (var file in Directory.EnumerateFiles(EmuStandInfo.datDirectoryPath, "*.dat"))
            //{
            //    root = new XmlRootAttribute("datafile") { IsNullable = true };
            //    serializer = new XmlSerializer(typeof(DatFile), root);
            //    using (FileStream stream = new FileStream(Path.Combine(EmuStandInfo.datDirectoryPath, file), FileMode.Open))
            //    {
            //        datFiles.Add(Path.GetFileName(file), (DatFile)serializer.Deserialize(stream));
            //    }
            //}

            ///* Read cartridge metadata database */
            //cartMetadataDatabase = EmuStandInfo.metadataDatabaseFilePath.DeserializeFromFile<List<CartridgeJSON>>();

            ////EssgeeLogger.EnqueueMessageSuccess($"Metadata initialized; {NumKnownGames} game(s) known across {NumKnownSystems} system(s).");
        }

        ~GameMetadataHandler()
        {
            if(instance == this)
                instance = null;
        }

        public GameMetadata GetGameMetadata(string datFilename, string romFilename, uint romCrc32, int romSize)
        {
            /* Sanity checks */
            //if (!datFiles.ContainsKey(datFilename)) throw new HandlerException("Requested .dat file not found");

            //接口直接读取
            if (!gameMetaReources.GetDatBytes(datFilename, out byte[] loadedData))
                throw new HandlerException("Requested .dat file not found");

            DatFile datFile;

            XmlRootAttribute root;
            XmlSerializer serializer;
            root = new XmlRootAttribute("datafile") { IsNullable = true };
            serializer = new XmlSerializer(typeof(DatFile), root);
            using (MemoryStream stream = new MemoryStream(loadedData))
            {
                datFile = (DatFile)serializer.Deserialize(stream);
            }

            /* Get information from No-Intro .dat */
            //var datFile = datFiles[datFilename];
            var crcString = string.Format("{0:X8}", romCrc32);
            var sizeString = string.Format("{0:D}", romSize);
            var gameInfo = datFile.Game.FirstOrDefault(x => x.Rom.Any(y => y.Crc == crcString && y.Size == sizeString));

            /* Get information from cartridge metadata database */
            var cartridgeInfo = cartMetadataDatabase.FirstOrDefault(x => x.Crc32 == romCrc32 && x.RomSize == romSize);

            /* Create game metadata */
            var gameMetadata = new GameMetadata()
            {
                FileName = Path.GetFileName(romFilename),
                KnownName = gameInfo?.Name,
                RomCrc32 = romCrc32,
                RomSize = romSize
            };

            if (cartridgeInfo != null)
            {
                if (gameMetadata.KnownName == null)
                    gameMetadata.KnownName = cartridgeInfo.Name;

                gameMetadata.Notes = cartridgeInfo.Notes;
                gameMetadata.RamSize = cartridgeInfo.RamSize;
                gameMetadata.MapperType = cartridgeInfo.Mapper;
                gameMetadata.HasNonVolatileRam = cartridgeInfo.HasNonVolatileRam;
                gameMetadata.PreferredTVStandard = cartridgeInfo.PreferredTVStandard;
                gameMetadata.PreferredRegion = cartridgeInfo.PreferredRegion;
                gameMetadata.AllowMemoryControl = cartridgeInfo.AllowMemoryControl;
            }

            if (gameMetadata.KnownName == null)
                gameMetadata.KnownName = "unrecognized game";

            return gameMetadata;
        }

        public class CartridgeJSON
        {
            //[JsonProperty(Required = Required.Always)]
            public string Name { get; set; } = string.Empty;

            //[JsonProperty(Required = Required.Always)]
            public string Notes { get; set; } = string.Empty;

            //[JsonProperty(Required = Required.Always), JsonConverter(typeof(HexadecimalJsonConverter))]
            public uint Crc32 { get; set; } = 0xFFFFFFFF;

            //[JsonProperty(Required = Required.Always)]
            public int RomSize { get; set; } = 0;

            //[JsonProperty(Required = Required.Default), DefaultValue(0)]
            public int RamSize { get; set; } = 0;

            //[JsonProperty(Required = Required.Default), JsonConverter(typeof(TypeNameJsonConverter), "Essgee.Emulation.Cartridges"), DefaultValue(null)]
            public Type Mapper { get; set; } = null;

            //[JsonProperty(Required = Required.Default), DefaultValue(false)]
            public bool HasNonVolatileRam { get; set; } = false;

            //[JsonProperty(Required = Required.Default), JsonConverter(typeof(StringEnumConverter)), DefaultValue(TVStandard.Auto)]
            public TVStandard PreferredTVStandard { get; set; } = TVStandard.Auto;

            //[JsonProperty(Required = Required.Default), JsonConverter(typeof(StringEnumConverter)), DefaultValue(Region.Auto)]
            public Region PreferredRegion { get; set; } = Region.Auto;

            //[JsonProperty(Required = Required.Default), DefaultValue(true)]
            public bool AllowMemoryControl { get; set; } = true;
        }

        public class DatHeader
        {
            [XmlElement("name")]
            public string Name { get; set; }
            [XmlElement("description")]
            public string Description { get; set; }
            [XmlElement("category")]
            public string Category { get; set; }
            [XmlElement("version")]
            public string Version { get; set; }
            [XmlElement("date")]
            public string Date { get; set; }
            [XmlElement("author")]
            public string Author { get; set; }
            [XmlElement("email")]
            public string Email { get; set; }
            [XmlElement("homepage")]
            public string Homepage { get; set; }
            [XmlElement("url")]
            public string Url { get; set; }
            [XmlElement("comment")]
            public string Comment { get; set; }
        }

        public class DatRelease
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
            [XmlAttribute("region")]
            public string Region { get; set; }
            [XmlAttribute("language")]
            public string Language { get; set; }
            [XmlAttribute("date")]
            public string Date { get; set; }
            [XmlAttribute("default")]
            public string Default { get; set; }
        }

        public class DatBiosSet
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
            [XmlAttribute("description")]
            public string Description { get; set; }
            [XmlAttribute("default")]
            public string Default { get; set; }
        }

        public class DatRom
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
            [XmlAttribute("size")]
            public string Size { get; set; }
            [XmlAttribute("crc")]
            public string Crc { get; set; }
            [XmlAttribute("sha1")]
            public string Sha1 { get; set; }
            [XmlAttribute("md5")]
            public string Md5 { get; set; }
            [XmlAttribute("merge")]
            public string Merge { get; set; }
            [XmlAttribute("status")]
            public string Status { get; set; }
            [XmlAttribute("date")]
            public string Date { get; set; }
        }

        public class DatDisk
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
            [XmlAttribute("sha1")]
            public string Sha1 { get; set; }
            [XmlAttribute("md5")]
            public string Md5 { get; set; }
            [XmlAttribute("merge")]
            public string Merge { get; set; }
            [XmlAttribute("status")]
            public string Status { get; set; }
        }

        public class DatSample
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
        }

        public class DatArchive
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
        }

        public class DatGame
        {
            [XmlAttribute("name")]
            public string Name { get; set; }
            [XmlAttribute("sourcefile")]
            public string SourceFile { get; set; }
            [XmlAttribute("isbios")]
            public string IsBios { get; set; }
            [XmlAttribute("cloneof")]
            public string CloneOf { get; set; }
            [XmlAttribute("romof")]
            public string RomOf { get; set; }
            [XmlAttribute("sampleof")]
            public string SampleOf { get; set; }
            [XmlAttribute("board")]
            public string Board { get; set; }
            [XmlAttribute("rebuildto")]
            public string RebuildTo { get; set; }

            [XmlElement("year")]
            public string Year { get; set; }
            [XmlElement("manufacturer")]
            public string Manufacturer { get; set; }

            [XmlElement("release")]
            public DatRelease[] Release { get; set; }

            [XmlElement("biosset")]
            public DatBiosSet[] BiosSet { get; set; }

            [XmlElement("rom")]
            public DatRom[] Rom { get; set; }

            [XmlElement("disk")]
            public DatDisk[] Disk { get; set; }

            [XmlElement("sample")]
            public DatSample[] Sample { get; set; }

            [XmlElement("archive")]
            public DatArchive[] Archive { get; set; }
        }

        [Serializable()]
        public class DatFile
        {
            [XmlElement("header")]
            public DatHeader Header { get; set; }

            [XmlElement("game")]
            public DatGame[] Game { get; set; }
        }
    }
}