using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace Lomont.Binary.Disassembler.X86
{
    /// <summary>
    /// Internal tables
    /// </summary>
    internal class Tables
    {
       
        string[] CPUs =
        {
            "8086",
            "80186",
            "80286",
            "80386",
            "80486",
            "Pentium I",
            "Pentium II",
            "Pentium with MMX",
            "Pentium Pro",
            "Pentium III",
            "Pentium 4",
            "Core 2",
            "Core i7",
            "Itanium" // todo - remove support?
        };

        // Items	MNENOMIC	Prefix	0F	Opcode	Secondary	Extension	Dst	Src	Ring	CPUs	REX	Next(s)	Lockable	OpMode	FeatureSet
        struct TCode
        {
            // direct reads
            public string Mnenomic;
            public byte? Prefix;
            public byte? OF;
            public byte? PrimaryOpcode;
            public byte? SecondaryOpcode;
            public byte? Extension;
            public string Dst;
            public string Src;
            public int Ring;
            public string Cpus;
            public byte? REX;
            public int Next;
            public bool Lockable;
            public string OpModes;

            // derived items
        }


        Action<string> OnError;
        int errors = 0;

        List<TCode> opcodes;

        void AddOpcode(TCode opcode)
        {
            // todo - clean, check items?
            var cpus = opcode.Cpus.Split('-');
            foreach (var cp in cpus)
                if (!CPUs.Contains(cp.Trim()))
                    OnError("Unknown CPU type " + cp);
            opcodes.Add(opcode);
        }

        void AddOpcodeDefinition(string[] words)
        {
            try
            {
                if (words.Length != 16)
                    OnError("Opcode definition missing fields");
                TCode t = new TCode();
                int index = 1; // start here
                t.Mnenomic = words[index++];

                // parse a byte value, with a given error message
                Func<string,byte?> ParseByte = errMsg =>
                    {
                        try
                        {
                            if (!string.IsNullOrEmpty(words[index++]))
                                return Byte.Parse(words[index - 1], System.Globalization.NumberStyles.AllowHexSpecifier);
                        }
                        catch (System.FormatException e)
                        {
                            OnError("Cannot parse " + errMsg + " byte " + words[index - 1] + " for opcode " + words[1]);
                        }
                        return null;
                    };

                t.Prefix = ParseByte("Prefix");
                t.OF = ParseByte("0F");
                t.PrimaryOpcode = ParseByte("PrimaryOpcode");
                t.SecondaryOpcode = ParseByte("SecondaryOpcode");
                //t.Extension = ParseByte("Extension");
                index++;//todo - skips extension

                t.Dst = words[index++];
                t.Src = words[index++];
                if (!Int32.TryParse(words[index++], out t.Ring))
                    OnError("Cannot parse ring 0-3 " + words[index - 1] + " for opcode " + t.Mnenomic);
                t.Cpus = words[index++];
                t.REX = ParseByte("REX");

                if (!Int32.TryParse(words[index++], out t.Next))
                    OnError("Cannot parse Next " + words[index - 1]);
                
                t.Lockable = words[index++].ToLower() == "true";
                t.OpModes = words[index++];

                AddOpcode(t);



            }
            catch (System.FormatException e)
            {
                OnError("Cannot parse instruction " + words[1]);
                ++errors;
            }

        }

        public void Parse(Action<string> OnError)
        {
            this.OnError = s => { errors++; OnError(s); };
            opcodes = new List<TCode>();

            var lines = File.ReadAllLines(@"..\..\..\OpcodesCSV.csv");
            foreach (var line in lines)
            {
                if (String.IsNullOrEmpty(line))
                    continue;
                var words = line.Split(',');
                if ((words.Length == 0) || (words[0].StartsWith(@"//")) || (words[0].StartsWith("\"")))
                    continue;
                Debug.WriteLine(line);
                AddOpcodeDefinition(words);
            }
            if (errors != 0)
                OnError(errors + " errors parsing opcodes");
        }
    }
}
