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

namespace Lomont.CLD
    {
    /// <summary>
    /// A class to output the XML representation of the analysis
    /// </summary>
    sealed class XMLScannerOutput
        {
        // xml file:
        // root
        //  - files
        //    - each file...
        //  - totals
        //  - suffixes
        //  - opcode definitions
        //  - misc info


        /// <summary>
        /// Root name for the generated XML file
        /// </summary>
        string rootName = "LomontAssemblyScanner";
        /// <summary>
        /// The section to place files under
        /// </summary>
        string filesName = "files";

        /// <summary>
        /// the file to save under
        /// </summary>
        string outfileName;

        /// <summary>
        /// Write the string to the global filename
        /// </summary>
        /// <param name="message">The message to output</param>
        void WriteStringToFile(string message)
            {
            File.AppendAllText(outfileName, message);
            }

        /// <summary>
        /// Create the XML file output, using the given filename
        /// </summary>
        /// <param name="outFileName">The output filename</param>
        public XMLScannerOutput(string outFileName)
            {
            outfileName = outFileName;
            if (File.Exists(outFileName))
                File.Delete(outFileName);
            WriteStringToFile("<?xml version=\"1.0\"?>" + Environment.NewLine);
            WriteStringToFile("<" + rootName + ">" + Environment.NewLine);

            WriteStringToFile("  <" + filesName + ">" + Environment.NewLine);
            }

        /// <summary>
        /// Write stats to the file, flush and close each time
        /// </summary>
        /// <param name="fileStat">The stat to write</param>
        /// <param name="fileNode">The optional XML file node to add to. If null, write to file</param>
        public void WriteStatToFile(Scanner.FileStats fileStat, XElement fileNode = null)
            {
            bool showAll = fileNode == null;
            if (showAll)
                fileNode = new XElement("file");

            var s = fileStat;
            Action<string, string> Add = (name, value) => fileNode.Add(new XAttribute(name, value));
            if (!showAll)
                Add("Filename", s.Filename);
            Add("CodeBytesRead", s.CodeBytesRead.ToString());
            Add("CodeGapBytes", s.CodeGapBytes.ToString());
            Add("DisassembleTime", s.DisassembleTime.ToString());
            Add("InstructionBytesDecoded", s.InstructionBytesDecoded.ToString());
            Add("InstructionsDecoded", s.InstructionsDecoded.ToString());
            if (showAll)
                FormatInstructionStats(s.InstructionStats, fileNode);
            Add("OverlappedInstructionBytes", s.OverlappedInstructionBytes.ToString());
            if (showAll)
                WriteStringToFile(fileNode.ToString() + Environment.NewLine);
            }

        /// <summary>
        /// Finish the output, close tags, and cloe the file
        /// </summary>
        /// <param name="suffixes">The file suffixes found to save</param>
        /// <param name="totals">The totals of all instructions decoded</param>
        public void Finish(HashSet<string> suffixes, Scanner.TotalStats totals)
            {
            // todo save
            // 1. opcode list for unique index
            // 2. per file stats - should write as we go.
            // 3. aggregate stats
            // 4. suffixes

            WriteStringToFile("  </" + filesName + ">" + Environment.NewLine);
            WriteTotals(totals);
            WriteSuffixes(suffixes);
            WriteOpcodeDefinitions();

            // close xml file
            WriteStringToFile("</" + rootName + ">" + Environment.NewLine);

            }

        /// <summary>
        /// Write the opcode definitions
        /// </summary>
        void WriteOpcodeDefinitions()
            { // todo - implement
            XElement tbl = new XElement("opcodes");
            tbl.Add(new XAttribute("TODO", "Not Implemented"));
            WriteStringToFile(tbl.ToString());
            }


        /// <summary>
        /// Write all suffixes out with counts
        /// </summary>
        /// <param name="suffixes">file Suffixes found with counts</param>
        void WriteSuffixes(HashSet<string> suffixes)
            {
            XElement suffixXML = new XElement("suffixes");
            suffixXML.Add(new XAttribute("Count", suffixes.Count.ToString()));
            foreach (var suff in suffixes)
                {
                suffixXML.Add(new XElement("suffix", suff));
                }
            WriteStringToFile(suffixXML.ToString() + Environment.NewLine);
            }

        /// <summary>
        /// Write out totals of all info
        /// </summary>
        /// <param name="totals">the totals structure</param>
        void WriteTotals(Scanner.TotalStats totals)
            {
            var t = totals;
            XElement totalXML = new XElement("Totals");

            totalXML.Add(new XAttribute("FileBytes", t.FileBytes.ToString()));
            totalXML.Add(new XAttribute("FilesTouched", t.FilesTouched.ToString()));

            // write totals as a child:
            WriteStatToFile(t.FileTotals, totalXML);

            totalXML.Add(new XAttribute("PEFileBytes", t.PEFileBytes.ToString()));
            totalXML.Add(new XAttribute("PEFiles", t.PEFiles.ToString()));
            totalXML.Add(new XAttribute("StartTime", t.StartTime.ToString()));
            totalXML.Add(new XAttribute("EndTime", t.EndTime.ToString()));
            totalXML.Add(new XAttribute("ElapsedTime", (t.EndTime - t.StartTime).ToString()));
            FormatInstructionStats(t.FileTotals.InstructionStats, totalXML);

            WriteStringToFile(totalXML.ToString() + Environment.NewLine);
            }


        /// <summary>
        /// Format a set of instruction stats and write out
        /// </summary>
        /// <param name="stats"></param>
        /// <param name="parent"></param>
        void FormatInstructionStats(Dictionary<Scanner.InstructionStat, ulong> stats, XElement parent)
            {
            XElement instructionStats = new XElement("instructionStats");
            parent.Add(instructionStats);
            instructionStats.Add(new XAttribute("types", stats.Count.ToString()));
            var s = stats;
            foreach (var i in s.OrderBy(a => -(long)a.Value))
                {
                XElement instr = new XElement("stat");
                instr.Add(new XAttribute("DisplacementLength", i.Key.DisplacementLength.ToString()));
                instr.Add(new XAttribute("ImmediateLength", i.Key.ImmediateLength.ToString()));
                instr.Add(new XAttribute("InstructionIndex", i.Key.InstructionIndex.ToString()));
                instr.Add(new XAttribute("Mnemonic", i.Key.Mnemonic.ToString()));
                instr.Add(new XAttribute("Modifier", i.Key.Modifier.ToString()));
                instr.Add(new XAttribute("OpcodeLength", i.Key.OpcodeLength.ToString()));
                instr.Add(new XAttribute("Prefix", i.Key.Prefix.ToString()));
                instr.Add(new XAttribute("Count", i.Value.ToString()));
                instructionStats.Add(instr);
                }
            }
        }
    }
