using System;
using System.Collections.Generic;
using System.Text;

namespace everdrive3x.Rom
{
    class SegaRom : RomInterface
    {
        public const String ROM_TYPE_BIN = "BIN";
        public const String ROM_TYPE_SMD = "SMD";
        public const String ROM_TYPE_FUS = "FUS";
        const byte SELECT_MODE_RESET = 1;
        const byte SELECT_MODE_LOADER = 2;
        const byte SELECT_MODE_NO = 0xff;

        byte[] bin_data;
        byte[] config;
        String rom_type;
        String rom_name;
        int declarated_size;
        String platform;

        public SegaRom(byte[] src, int offset, int len)
        {
            rom_type = getRomType(src, offset, len);

            if (rom_type == null)
            {
                loadUnknown(src, offset, len);
            }
            else if (rom_type.Equals(ROM_TYPE_FUS))
            {
                loadFusion(src, offset, len);
            }
            else if (rom_type.Equals(ROM_TYPE_SMD))
            {
                loadSmd(src, offset, len);
            }
            else if (rom_type.Equals(ROM_TYPE_BIN))
            {
                loadBin(src, offset, len);
            }
            else
            {
                throw new Exception("bad rom format: " + rom_type);
            }
        }

        void loadFusion(byte[] src, int offset, int len)
        {
            offset += "MDFUS".Length;
            int config_len = src[offset++] & 0xFF;
            config = new byte[config_len];
            System.Array.Copy(src, offset, config, 0, config_len);
            offset += config_len;
            bin_data = new byte[len - config_len - 1 - "MDFUS".Length];
            System.Array.Copy(src, offset, bin_data, 0, bin_data.Length);
            rom_name = "GAME PACK";
            declarated_size = bin_data.Length;
            platform = Hardware.PlatformList.NAME_SEGA;
        }

        void loadSmd(byte[] src, int offset, int len)
        {
            int blocks = (len - 512) / 16384;
            int e = 0;
            int o = 0;
            byte[] bin_block = new byte[16384];
            byte[] smd_block = new byte[16384];
            bin_data = new byte[blocks * 16384];
            config = new byte[1];
            config[0] = (byte)SELECT_MODE_NO;
            int rom_ptr = 0;

            for (int i = 0; i < blocks; i++)
            {
                e = 0;
                o = 1;
                System.Array.Copy(src, 16384 * i + 512 + offset, smd_block, 0, 16384);

                for (int j = 0; j < 16384; j++)
                {
                    if (j < 8192)
                    {
                        bin_block[o] = smd_block[j];
                        o += 2;
                    }
                    else
                    {
                        bin_block[e] = smd_block[j];
                        e += 2;
                    }
                }

                System.Array.Copy(bin_block, 0, bin_data, rom_ptr, bin_block.Length);
                rom_ptr += bin_block.Length;
            }

            rom_name = getHeaderString(bin_data, 336, 48);
            declarated_size = getInt(bin_data, 420) + 1;
            platform = Hardware.PlatformList.NAME_SEGA;
            
        }

        void loadBin(byte[] src, int offset, int len)
        {
            config = new byte[1];
            config[0] = (byte)SELECT_MODE_NO;
            bin_data = new byte[len];
            System.Array.Copy(src, offset, bin_data, 0, len);
            rom_name =  getHeaderString(bin_data, 336, 48);
            declarated_size = getInt(bin_data, 420) + 1;
            platform = Hardware.PlatformList.NAME_SEGA;
        }

        void loadUnknown(byte[] src, int offset, int len)
        {
            config = new byte[0];
            bin_data = new byte[len];
            System.Array.Copy(src, offset, bin_data, 0, len);
            rom_type = "unknown";
            rom_type = "unknown";
            declarated_size = 0;
            platform = "unknown";
            rom_name = "unknown";
        }

       

        static String getHeaderString(byte[] src, int offset, int len)
        {
            char[] buff = new char[len];
            for (int i = 0; i < len; i++)
            {
                if (src[offset] == 0)
                {
                    len = i;
                    break;
                }
                buff[i] = (char)src[offset++];
            }
            String s = new String(buff, 0, len);
            return s.Trim();
        }

        int getInt(byte[] src, int offset)
        {
            int val = (src[offset++] & 0xFF) << 24;
            val |= (src[offset++] & 0xFF) << 16;
            val |= (src[offset++] & 0xFF) << 8;
            val |= (src[offset++] & 0xFF) << 0;

            return val;
        }

        public static String getRomType(byte[] src, int offset, int len)
        {
            try
            {
                if (src[offset + 0] == 'M' && src[offset + 1] == 'D' && src[offset + 2] == 'F' && src[offset + 3] == 'U' && src[offset + 4] == 'S')
                {
                    return ROM_TYPE_FUS;
                }
                else if ((len - 512) / 16384 == src[offset + 0] && src[offset + 1] == 3)
                {
                    return ROM_TYPE_SMD;
                }
                else if (getHeaderString(src, offset + 256, 16).Contains("SEGA"))
                {
                    return ROM_TYPE_BIN;
                }
            }
            catch (Exception) { }


            return null;
        }

        public byte[] BinData
        {
            get { return bin_data; }
        }

        public byte[] Config
        {
            get { return config; }
        }

        public String Name
        {
            get { return rom_name; }
        }

        public String Platform
        {
            get { return platform; }
        }

       

        public String RomType
        {
            get { return rom_type; }
        }

        public int DeclaratedSize
        {
            get { return declarated_size; }
        }

        public String Region
        {
            get
            {
                return "" + (char)bin_data[0x1F0] + 
                    (char)bin_data[0x1F0 + 1] + 
                    (char)bin_data[0x1F0 + 2] + 
                    (char)bin_data[0x1F0 + 3];
            }
            set {
                if (!value.Equals("default"))
                {
                    char[] str = ((String)value).ToCharArray();
                    bin_data[0x1F0] = (byte)str[0];
                    bin_data[0x1F0 + 1] = 0x20;
                    bin_data[0x1F0 + 2] = 0x20;
                    bin_data[0x1F0 + 3] = 0x20;
                }
            }
        }
    }
}
