using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lomont.Binary.X86;
using Lomont.Binary;
using Lomont.Utils;

namespace Lomont.CLD
    {
    /// <summary>
    /// Class to test the X86 disassembler, with output to the console
    /// </summary>
    class ConsoleDisassemblerTester
        {

        /// <summary>
        /// Create a console disassembler
        /// </summary>
        public ConsoleDisassemblerTester()
            {
            X86ProcessorMode mode = new X86ProcessorMode();
            mode.OperationMode = X86ProcessorMode.OperationModes.Protected; // standard 32-bit disassembly
            Messager messager = new Messager();
            //messager.MessageEvent += (o, e) => Console.WriteLine(e.Message);
            context = new X86DecodeContext(messager, mode, null);
            }

        /// <summary>
        /// do the given number of tests, 0 for all, -n for only test n
        /// </summary>
        /// <param name="testCount"></param>
        /// <param name="showErrorsOnly">true to only show errors, else show all disassembly messages</param>
        public void Test(int testCount, bool showOnlyErrors)
            {
            failCount = 0; // count those with errors

            // we queue messages on each item tested, and based on if it failed, display the messages
            messageQueue = new List<string>();

            // what to do on disassembler event messages
            context.Messager.MessageEvent += (o, e) => messageQueue.Add(e.Message);

            List<int> errorIndices = new List<int>();

            // determine test range
            int max = data.TestCount; // do all
            if (testCount > 0)
                max = Math.Min(testCount, max);
            int min = 0;
            if (testCount < 0)
                { // only test 1 item
                min = -testCount;
                max = min + 1;
                }

            // test each one in range
            for (int i = min; i < max; ++i)
                TestSingle(i, showOnlyErrors);

            if (failCount > 0)
                ColorMessage(Environment.NewLine + "ERROR: {0} of {1} failed", failCount, max - min);
            } // Test

        /// <summary>
        /// Test a single test case. Output messages if required.
        /// </summary>
        /// <param name="testIndex">The test index to try</param>
        /// <param name="showOnlyErrors">whether to show all messages or only errors</param>
        /// <returns>true on success</returns>
        bool TestSingle(int testIndex, bool showOnlyErrors)
            {
            string answer;
            List<byte> testData;
            data.GetTest(testIndex, out testData, out answer);
            messageQueue.Clear(); // reset this
            messageQueue.Add(String.Format(Environment.NewLine + "Test {0}: {1}: {2}", testIndex, BitUtils.FormatHex(testData, 0, testData.Count), answer));
            context.Data = new ByteStream(testData, 0);
            bool failed = false;
            try
                {
                var instruction = decoder.DecodeInstruction(context);
                if (instruction != null)
                    {
                    if (instruction.Length != testData.Count)
                        messageQueue.Add(String.Format("ERROR: wrong length {0} instead of {1}", instruction.Length, testData.Count));
                    messageQueue.Add(String.Format("Answer {0}", instruction));
                    }
                else
                    messageQueue.Add("ERROR: Answer: NULL");
                }
            catch (Exception e)
                {
                messageQueue.Add(String.Format("ERROR: Exception {0}", e));
                failed = true;
                }

            // walk messages, see if failed
            foreach (var message in messageQueue)
                failed |= message.StartsWith("ERROR:");
            if (failed)
                ++failCount;

            // show messages (if necessary)
            if ((!showOnlyErrors) || (failed))
                {
                foreach (var message in messageQueue)
                    ColorMessage(message);
                }

            return failed;
            }

        TestData data = new TestData();
        X86InstructionDecoder decoder = new X86InstructionDecoder();
        int failCount = 0; // count those with errors
        List<string> messageQueue = new List<string>();

        /// <summary>
        /// A decoding context to work with.
        /// </summary>
        X86DecodeContext context;

        /// <summary>
        /// Draw a message, colored based on first word.
        /// Return int value denoting message type:
        /// 0 = INFO
        /// 1 = Warning
        /// 2 = Error
        /// </summary>
        /// <param name="formatString">The formatting string</param>
        /// <param name="items">The items to format</param>
        /// <returns>A code denoting the type of message displayed</returns>
        int ColorMessage(string formatString, params object[] items)
            {
            int retVal = 0;
            ConsoleColor c = Console.ForegroundColor;
            if (formatString.StartsWith("ERROR"))
                {
                Console.ForegroundColor = ConsoleColor.Red;
                retVal = 2;
                }
            else if (formatString.StartsWith("WARNING"))
                {
                Console.ForegroundColor = ConsoleColor.Yellow;
                retVal = 1;
                }
            Console.WriteLine(formatString, items);
            Console.ForegroundColor = c;
            return retVal;
            }

        }
    }
