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

namespace Lomont.Binary.X86
    {

    // TODO - notes on some weirdness seen while implementing
    //if (op.Cpus.Contains("8086 - 8086"))
    //	continue; // skips undocumented 0x0F = POP CS, which is now the two byte opcode prefix
    // try matching two byte opcodes of form 0xDn 0xyy, where n is 8-F, and yy is >= 0xC0
    // (for example, F2XM1 = 0xD9 0xF0, note this also matches FNSTENV = 0xD9 /6 without special rule on values >= 0xC0)
    // todo - weird case: WAIT/FWAIT is opcode 0x9B, but 9B is also a prefix
    // for items such as FCLEX = 9B DB E2, FNCLEX = DB E2
    // we resolve matching 2: FCLEX and FWAIT/WAIT as FCLEX
    // it seems the Intel decoder decodes the longest possible opcode 
    // in this case. Are there others? If so, make this a heuristic?
    // todo - new heuristic - take longest matching opcode. If tie for longest, then keep all.
    // this next case should never happen, but can if table is incorrect
    // todo- can happen, in case like STOSW, STOSD, STOSQ for sizes. How to adjust? return first match?
    // LZCNT on AMD is 0xF3 (prefix) and 0xBD (BSR), which has different meaning on Intel - AMD special cased this http://software.intel.com/en-us/forums/showthread.php?t=68998
    // AMX (see AAM) is listed by Intel has having no mnemonic.
    // ADX (see AAD) is listed by Intel as having no mnemonic
    // SETALC = 0xD6 is missing from Intel manuals?


    /// <summary>
    /// Internal tables. These store all opcode tables for lookup.
    /// TODO - make a table parser that converts the CSV to a table code used in here.
    /// TODO - make based on an instruction type context specifying mode (64 bit, real, etc) and address sizes, etc.
    /// TODO - clean up messaging - make instruction decoding use context messages, not constructor ones
    /// </summary>
    public class X86DefinitionTables
        {
        /// <summary>
        /// CPU types known to the decode tables.
        /// TODO - unify this information and types of instructions allowed in each.
        /// </summary>
        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?
        };

        /// <summary>
        /// The list of all instruction definitions
        /// </summary>
        public List<X86InstructionDefinition> Definitions { get; set; }

        /// <summary>
        /// Object to send info, warning, and error messages to.
        /// </summary>
        IMessager Messager;

        /// <summary>
        /// Given an instruction with all prefixes matched, find all possible matches.
        /// Returns null if no matches. Returns the top match if multiple and warns user.
        /// </summary>
        /// <param name="instruction"></param>
        /// <param name="nextByte"></param>
        /// <returns></returns>
        public X86InstructionDefinition MatchOpcode(X86DecodeContext context)
            {
            TableEntry entry = rootEntry;
            int index = 0;
            int left = context.Data.BytesLeft;
            while ((entry != null) && (entry.Definitions == null) && (index < left))
                {
                entry = entry.Table[context.Data.DataByte(index++)];
                }
            if (entry == null)
                return null; // no match
            if (entry.Definitions == null)
                return null; // no match

            var op = entry.Definitions[0]; // todo - multiple matches warning here

            int opcodeLength = op.OpcodeLength;
            //if ((op.Prefix.HasValue) && (op.Prefix != 0x9B))
            //	opcodeLength--; // we already ate the prefix - todo - make cleaner

            while (opcodeLength > 0)
                {
                context.Instruction.Data.Add(context.Data.NextByte());
                opcodeLength--;
                }

            if (entry.Definitions.Count > 1)
                {
                StringBuilder sb = new StringBuilder("Multiple matches: ");
                foreach (var name in entry.Definitions)
                    sb.Append(name.Mnenomic + " ");
                context.OnWarning(sb.ToString());
                }

            // todo - select best one from all possible? use context and prefixes
            return entry.Definitions[0];
            }

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


        /// <summary>
        /// Clean operands of form Rv/Mb to only be Rv
        /// </summary>
        /// <param name="op"></param>
        /// <returns></returns>
        string CleanOperand(string op)
            {
            if (op.Contains('/'))
                {
                return op.Substring(0, op.IndexOf('/'));
                }
            return op;
            }
        /// <summary>
        /// Add the instruction definition to the parsing tables
        /// </summary>
        /// <param name="words">a list of definition tokens</param>
        void AddOpcodeDefinition(string[] words)
            {
            try
                {
                if (words.Length != 20)
                    Messager.OnError("Opcode definition missing fields");
                X86InstructionDefinition t = new X86InstructionDefinition();
                int index = 1; // start here
                t.Mnenomic = (X86Mnemonic)Enum.Parse(typeof(X86Mnemonic), words[index++]);

                // parse a byte value, with a given error disasm
                Func<string,byte?> ParseByte = errMsg =>
                    {
                        try
                            {
                            if (!string.IsNullOrEmpty(words[index++]))
                                return Byte.Parse(words[index - 1], System.Globalization.NumberStyles.AllowHexSpecifier);
                            }
                        catch (System.FormatException)
                            {
                            Messager.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");
                var mod3 = words[index++];
                if (mod3 == "Y")
                    t.Mod3 = true;
                else if (mod3 == "N")
                    t.Mod3 = false;
                t.RM = ParseByte("RM");
                t.Dst = words[index++];
                t.Src = words[index++];
                t.Op3 = words[index++];
                string ring = words[index++];
                if (ring == "f")
                    t.Ring = Int32.MinValue; // todo - mark 
                else if (!Int32.TryParse(ring, out t.Ring))
                    Messager.OnError("Cannot parse ring 0-3 " + words[index - 1] + " for opcode " + t.Mnenomic);
                t.REX = ParseByte("REX");

                int nextLocation = 0;
                if (!Int32.TryParse(words[index++], out nextLocation))
                    Messager.OnError("Cannot parse Next " + words[index - 1]);
                ///    1 if next address is legal, 
                ///    2 for conditional branches and CALL, meaning next and target are legit.
                ///   -1 for jumps (no next instruction perhaps)
                ///    0 for returns (no jump, perhaps no next instruction)
                switch (nextLocation)
                    {
                    case -1:
                        t.Next = X86InstructionDefinition.BranchType.Jump;
                        break;
                    case 0:
                        t.Next = X86InstructionDefinition.BranchType.Return;
                        break;
                    case 1:
                        t.Next = X86InstructionDefinition.BranchType.Normal;
                        break;
                    case 2:
                        t.Next = X86InstructionDefinition.BranchType.Split;
                        break;
                    default:
                        Messager.OnError("Invalid next field {0} in instruction definition", nextLocation);
                        break;
                    }

                t.Lockable = words[index++].ToLower() == "true";

                t.Cpus = words[index++];
                t.FeatureSet = words[index++];

                t.OpModes = words[index++];
                t.IsDocumented = !words[index++].ToLower().StartsWith("undoc");

                // compute opcode length
                t.OpcodeLength = 1; // primary byte
                if (t.SecondaryOpcode.HasValue)
                    t.OpcodeLength++;
                if (t.OF.HasValue)
                    t.OpcodeLength++;
                if (t.Prefix.HasValue)
                    t.OpcodeLength++;

                // todo - make later stages handle multiple definitions
                // example: SLDT has Rv/Mw operand, but this splits only Rv out
                t.Src = CleanOperand(t.Src);
                t.Dst = CleanOperand(t.Dst);
                t.Op3 = CleanOperand(t.Op3);

                AddOpcode(t);
                }
            catch (System.FormatException)
                {
                Messager.OnError("Cannot parse instruction " + words[1]);
                }

            }

        /// <summary>
        /// Represent a node for table based parsing
        /// </summary>
        class TableEntry
            {

            public TableEntry()
                {
                entryCount++;
                }

            /// <summary>
            /// Opcode definitions at this location.
            /// </summary>
            public List<X86InstructionDefinition> Definitions;
            /// <summary>
            /// The parse table at this location
            /// </summary>
            public ParseTable Table;
            }

        /// <summary>
        /// Represent a 256 entry table for byte based parsing
        /// </summary>
        class ParseTable
            {
            public ParseTable()
                {
                tableCount++;
                for (int i = 0; i < 256; ++i)
                    Table.Add(null);
                }

            List<TableEntry> Table = new List<TableEntry>();

            /// <summary>
            /// Get or set a table value
            /// </summary>
            /// <param name="index"></param>
            /// <returns></returns>
            public TableEntry this[int index]
                {
                get { return Table[index & 255]; }
                set { Table[index & 255] = value; }

                }
            }

        /// <summary>
        /// Number of tables allocated, count stored for debugging
        /// </summary>
        static int tableCount = 0;

        /// <summary>
        /// Number of entries allocated for debugging
        /// </summary>
        static int entryCount = 0;

        /// <summary>
        /// start each parsing operation here.
        /// </summary>
        TableEntry rootEntry = new TableEntry();

        /// <summary>
        /// Add an opcode to the main parsing tables. 
        /// Sends out error and info messages on the class messager.
        /// </summary>
        /// <param name="opcode"></param>
        void AddOpcodeToParseTable(X86InstructionDefinition opcode)
            {
            // skip invalid CPUs - todo - make user selectable
            if (opcode.Cpus == "8086 - 8086")
                return; // skips undocumented 0x0F = POP CS, which is now the two byte opcode prefix

            opcode.Index = Definitions.Count; // save the unique index
            Definitions.Add(opcode); // save it for external listings

            // add each byte field to the list of bytes to switch tables on
            List<byte> data = new List<byte>();
            Action<byte?> test = (b) => { if (b.HasValue) data.Add(b.Value); };
            test(opcode.Prefix);
            test(opcode.OF);
            data.Add(opcode.PrimaryOpcode.Value);
            test(opcode.SecondaryOpcode);

            // walk tables to this byte specified location, 
            // then add opcode. In case of extensions, etc., might add many entries

            var curEntry = rootEntry;
            // walk through bytes of instruction, adding tables as needed
            foreach (var b in data)
                {
                if (curEntry.Table == null)
                    curEntry.Table = new ParseTable(); // ensure table exists
                if (curEntry.Table[b] == null)
                    curEntry.Table[b] = new TableEntry(); // ensure entry exists
                // NOTE: some opcodes are subsets in the table of others. For example, FADD = D8 xx where xx in 00-BF, and FADD (another type) is D8 yy where yy > BF. 
                // The second form is stored as a child as the first type.
                // These can be culled by marking the first type as not having Mod3 set to Y
                if ((curEntry.Table[b].Definitions != null) && (curEntry.Table[b].Definitions.Count > 0))
                    Messager.OnError("Opcode table collision {0} and {1}", opcode, curEntry.Table[b].Definitions[0]);

                // jump to next level
                curEntry = curEntry.Table[b];
                }

            // see if there is modRM stuff
            bool hasModRM = opcode.Extension.HasValue || opcode.Mod3.HasValue || opcode.RM.HasValue;
            if (!hasModRM)
                { // place entry here
                if (curEntry.Table != null)
                    Messager.OnError("Opcode table split while inserting {0}", opcode);
                if (curEntry.Definitions == null)
                    curEntry.Definitions = new List<X86InstructionDefinition>();
                curEntry.Definitions.Add(opcode);
                }
            else
                { // insert all matching ModR/M fields
                if (curEntry.Table == null)
                    curEntry.Table = new ParseTable(); // add another level
                var table = curEntry.Table; // the table to put entries into
                int RMMask = 7;
                int ExtMask = 7 << 3;
                int ModMask = 128 + 64;
                for (int index = 0; index < 256; ++index)
                    {
                    if ((opcode.Extension.HasValue) && ((index & ExtMask) != (opcode.Extension.Value << 3)))
                        continue;
                    if ((opcode.Mod3 == true) && ((index & ModMask) != (3 << 6)))
                        continue;
                    if ((opcode.Mod3 == false) && ((index & ModMask) == (3 << 6)))
                        continue;
                    if ((opcode.RM.HasValue) && ((index & RMMask) != (opcode.RM)))
                        continue;
                    // have an entry. See if exists, then add it
                    if (table[index] == null)
                        table[index] = new TableEntry();
                    if (table[index].Table != null)
                        Messager.OnError("Opcode table split while inserting ModRM part for {0}", opcode);
                    if (table[index].Definitions == null)
                        table[index].Definitions = new List<X86InstructionDefinition>();
                    table[index].Definitions.Add(opcode);
                    }
                }
            }

        /// <summary>
        /// Call this to parse external tables and create internal opcode definitions.
        /// </summary>
        void Initialize()
            {
            // TODO - make tables internal. 
            string path = @"OpcodesCSV.csv";
            if (!File.Exists(path))
                throw new FileNotFoundException(path);

            var lines = File.ReadAllLines(path);

            Definitions = new List<X86InstructionDefinition>();

            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;
                AddOpcodeDefinition(words);
                }
            }

        /// <summary>
        /// Store the ProcessorMode used to create this table.
        /// Changing this variable does not rebuild the table.
        /// </summary>
        X86ProcessorMode ProcessorMode { get; set; }

        /// <summary>
        /// Create tables prepared to parse for the given processor mode.
        /// Info/Warning/Error messages will be sent to the messager.
        /// </summary>
        /// <param name="messager"></param>
        /// <param name="mode"></param>
        public X86DefinitionTables(IMessager messager, X86ProcessorMode mode)
            {
            this.Messager = messager;
            this.ProcessorMode = mode;
            Initialize();
            }
        }
    }
