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

namespace Lomont.CLD
    {

    /// <summary>
    /// Represent a simple console GUI for doing file scanning
    /// </summary>
    sealed class ConsoleScanner
        {

        /// <summary>
        /// Create a new file scanner
        /// </summary>
        Scanner scanner = new Scanner();

        /// <summary>
        /// A XML output object to store results
        /// </summary>
        XMLScannerOutput xmlOutput;


        /// <summary>
        /// Scan the files recursively from here, tag those as PE files that are
        /// Show output to nice console.
        /// Write results to file.
        /// </summary>
        /// <param name="startPath">The path to start</param>
        /// <param name="outFileName">The output file name</param>
        public void Scan(string startPath, string outFileName)
            {
            xmlOutput = new XMLScannerOutput(outFileName);
            scanner.ScanEvent += ScanEvent;
            scanner.Scan(startPath);
            }

        /// <summary>
        /// Handle scanner messages
        /// </summary>
        /// <param name="args">The scanner arguments</param>
        void ShowFileList(Scanner.ScanEventArgs args)
            {
            if (args.Reason == Scanner.ScanEventArgs.Type.FileFailed)
                {
                WriteC(1, 50, ConsoleColor.Red, args.Message);
                }
            else
                {
                var message = "        " + args.Filename + " " + args.Message;
                if (message.Length > 70)
                    { // chop middle to fit, add ellipsis
                    int extra = message.Length - 70; // remove this many from middle
                    extra += 5; // ellipsis point space
                    int half = (message.Length - extra) / 2; // size of each half
                    message = message.Substring(0, half) + "....." +
                        message.Substring(message.Length - half);
                    }
                Scroll(1, 50, 79, 67, message);
                }
            }

        /// <summary>
        /// Process scanner events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void ScanEvent(object sender, Scanner.ScanEventArgs args)
            {
            switch (args.Reason)
                {
                case Scanner.ScanEventArgs.Type.Start:
                    Start();
                    break;
                case Scanner.ScanEventArgs.Type.End:
                    End();
                    break;
                case Scanner.ScanEventArgs.Type.FileDone:
                    FileUpdate(args.Stats, args.Filename);
                    break;
                case Scanner.ScanEventArgs.Type.Message:
                    ShowMessage(args.Message);
                    break;
                case Scanner.ScanEventArgs.Type.Directory:
                    ShowDirectoryChange(args.Filename);
                    break;
                case Scanner.ScanEventArgs.Type.FileBegin:
                    ShowFileList(args);
                    break;
                case Scanner.ScanEventArgs.Type.FileFailed:
                    ShowFileList(args);
                    break;
                }
            }

        /// <summary>
        /// Scroll region down, write message to top, in the given box
        /// </summary>
        /// <param name="x1">Upper left x coordinate</param>
        /// <param name="y1">Upper left y coordinate</param>
        /// <param name="x2">Lower right x coordinate</param>
        /// <param name="y2">Lower right y coordinate</param>
        void Scroll(int x1, int y1, int x2, int y2, string message)
            {
            Console.MoveBufferArea(x1, y1, x2 - x1, y2 - y1 - 1, x1, y1 + 1);
            if (message.Length > (x2 - x1 - 1))
                message = message.Substring(x2 - x1 - 1);
            WriteC(x1, y1, ConsoleColor.White, message);
            }

        /// <summary>
        /// Send a change directory message
        /// </summary>
        /// <param name="path"></param>
        void ShowDirectoryChange(string path)
            {
            Scroll(1, 12, 78, 24, path);
            }


        /// <summary>
        /// Draw a box on the given coordinates
        /// </summary>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        void Box(int x1, int y1, int x2, int y2)
            {
            var top = "+" + new string('=', x2 - x1 - 1) + "+";
            Console.SetCursorPosition(x1, y1);
            Console.Write(top);
            Console.SetCursorPosition(x1, y2);
            Console.Write(top);
            for (int j = y1 + 1; j < y2; ++j)
                {
                Console.SetCursorPosition(x1, j);
                Console.Write('|');
                Console.SetCursorPosition(x2, j);
                Console.Write('|');
                }
            }

        /// <summary>
        /// Show a scanner message
        /// </summary>
        /// <param name="message">The message to show</param>
        void ShowMessage(string message)
            {
            // messages bottom three lines and counter
            // todo - clamp, make fit in box, etc.
            Console.SetCursorPosition(1, 26);
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(message);
            }

        /// <summary>
        /// Draw the UI boxes
        /// </summary>
        void DrawInterfaceBoxes()
            {
            Console.ForegroundColor = ConsoleColor.DarkGray;
            Box(0, 0, 39, 11);  // last file stats
            Box(40, 0, 79, 11); // total file stats
            Box(0, 11, 79, 25); // directories
            Box(0, 25, 79, 40); // messages
            Box(0, 25, 79, 49); // distributions
            Box(0, 49, 79, 68); // files tried
            Console.ForegroundColor = ConsoleColor.White;
            }

        /// <summary>
        /// Called on scanner start. Set up console.
        /// </summary>
        void Start()
            {
            Console.Clear();
            Console.SetWindowSize(80, 70);
            Console.CursorVisible = false;
            DrawInterfaceBoxes();
            }

        /// <summary>
        /// Called on scanner end. Clean up, save data
        /// </summary>
        void End()
            {
            scanner.Totals.EndTime = DateTime.Now;
            xmlOutput.Finish(suffixes, scanner.Totals);
            Console.CursorVisible = true;
            Console.SetCursorPosition(1, 69);
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Finished");
            }


        /// <summary>
        /// Called on each file done. Update screen
        /// </summary>
        /// <param name="stats">The file stats</param>
        /// <param name="filename">The filename</param>
        void FileUpdate(Scanner.FileStats stats, string filename)
            {
            if (stats != null)
                {
                suffixes.Add(Path.GetExtension(stats.Filename).ToLower());
                var t = scanner.Totals;
                var s = stats;

                // show totals
                int x = 56;
                WriteC(x, 1, ConsoleColor.White, "Files  :");
                Write(x, 2, "Bytes  :");
                Write(x, 3, "PEFiles:");
                Write(x, 4, "Elapsed:");

                int w = x + 8;
                var elapsed = DateTime.Now - t.StartTime;
                WriteC(w, 1, ConsoleColor.DarkGreen, t.FilesTouched.ToString());
                Write(w, 2, t.FileBytes.ToString());
                Write(w, 3, t.PEFiles.ToString());
                Write(w, 4, elapsed.ToString());

                DumpStats(1, 1, s);
                DumpStats(41, 1, t.FileTotals);

                xmlOutput.WriteStatToFile(stats);

                // save this
                filename = stats.Filename;

                //Console.Write("File {0}: {1}  ", t.FileTotals, s.Filename);

                //Console.WriteLine("Decoded {0} bytes {1} missed {2} %{3} ips {4} total ips {5}",
                //    s.InstructionsDecoded,
                //    s.InstructionBytesDecoded,
                //    s.CodeBytesRead - s.InstructionBytesDecoded,
                //    100 * s.InstructionBytesDecoded / s.CodeBytesRead,
                //    1000 * s.InstructionsDecoded / s.DisassembleTime,
                //    1000 * t.FileTotals.InstructionsDecoded / t.FileTotals.DisassembleTime
                //    );

                }
            if (filename != null)
                {    // todo - show file?
                }
            DrawInterfaceBoxes();
            }

        /// <summary>
        /// Action to write to screen at given position in given color
        /// </summary>
        Action<int, int, ConsoleColor, string> WriteC = (x, y, color, str) =>
        {
            Console.SetCursorPosition(x, y);
            Console.ForegroundColor = color;
            Console.Write(str + "  ");
        };

        /// <summary>
        /// Action to write to screen at given position in current color
        /// </summary>
        Action<int, int, string> Write = (x, y, str) =>
        {
            Console.SetCursorPosition(x, y);
            Console.Write(str + "  ");
        };

        /// <summary>
        /// Dump stats to the screen
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="stats"></param>
        void DumpStats(int x, int y, Scanner.FileStats stats)
            {
            Console.SetCursorPosition(x, y);


            WriteC(x + 0, y + 0, ConsoleColor.White, "Name  :");
            Write(x + 0, y + 1, "Instr :"); // number of instructions
            Write(x + 0, y + 2, "Time  :"); // time  in ms
            Write(x + 0, y + 3, "IPS   :"); // instructions per second
            Write(x + 0, y + 4, "Code  :"); // code bytes
            Write(x + 0, y + 5, "Instr :"); // instruction bytes
            Write(x + 0, y + 6, "% Cov :"); // % coverage of decoded to total code
            Write(x + 0, y + 7, "Over  :"); // overlap bytes
            Write(x + 0, y + 8, "Gap   :"); // gap bytes
            Write(x + 0, y + 9, "StLen :"); // number of unique stats

            int w = 7;
            WriteC(x + w, y + 0, ConsoleColor.Yellow, Path.GetFileName(stats.Filename) + "         ");
            WriteC(x + w, y + 1, ConsoleColor.Green, stats.InstructionsDecoded.ToString());
            Write(x + w, y + 2, stats.DisassembleTime.ToString());
            Write(x + w, y + 4, stats.CodeBytesRead.ToString());
            Write(x + w, y + 5, stats.InstructionBytesDecoded.ToString());
            if (stats.CodeBytesRead != 0)
                Write(x + w, y + 6, (100 * stats.InstructionBytesDecoded / stats.CodeBytesRead).ToString());
            Write(x + w, y + 7, stats.OverlappedInstructionBytes.ToString());
            Write(x + w, y + 8, stats.CodeGapBytes.ToString());
            Write(x + w, y + 9, stats.InstructionStats.Count.ToString());

            if (stats.DisassembleTime != 0)
                WriteC(x + w, y + 3, ConsoleColor.Blue, (100 * stats.InstructionsDecoded / stats.DisassembleTime).ToString());
            }


        /// <summary>
        /// Track files suffixes that passed as PE files
        /// </summary>
        public HashSet<string> suffixes = new HashSet<string>();
        }
    }
