using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
namespace AniNIX.Crypto {
    public class Analysis : Cipher {
        private Substitution _sb;
        public override String Description() { return "These are analysis tools to help understand ciphers."; }
        public override String Command() { return "analysis"; }
        public Analysis(Workbench w) : base (w) {
            //Analysis needs to be able to read the EngCommon array from the Substition class
            this._sb = new Substitution(w);
        }
        /// 
        /// Decode the user's input and relate to an existing function.
        /// 
        /// The working copy of the cipher
        /// The original cipher
        /// The user input
        /// The modified workspace string
        public override String RunCommand(String workSpace, String inputText, String[] line) {
            if (workSpace == null || inputText == null || line == null || line.Length < 2) {
                Console.Error.WriteLine("Malformed!");
                return workSpace;
            }
            switch (line[1]) {
                case "freq":
                    Frequency(workSpace);
                    break;
                case "freqinfo":
                    FrequencyInfo();
                    break;
                case "one-to-one":
                    OneToOneQuery(workSpace,inputText);
                    break;
                case "diff":
                    Diff(line);
                    break;
                case "charinfo":
                    CharInfo(line);
                    break;
                case "hexprint": 
                    HexPrint(workSpace);
                    break;
                case "help":
                case "":
                    GetHelp();
                    break;
                default: 
                    Console.Error.WriteLine("Invalid command. Type 'analysis help' for more.");
                    break;
            }
            return workSpace;
        }
        /// 
        /// Show this help text
        /// 
        public override void GetHelp() {
            Console.WriteLine("Analysis tools help:\nanalysis freq         -- Get frequency of characters.\nanalysis freqinfo     -- Return the most common English frequencies.\nanalysis one-to-one   -- See if there is a direct correspondence of characters between cipher and workspace.\nanalysis diff a b     -- get the difference between two characters\nanalysis charinfo c   -- get the info about a character c.\nanalysis hexprint -- print the hex and decimal value of each character in the workspace.");
        }
        /// 
        /// Return a dictionary of letters mapped to the number of letters found in the workSpace
        /// 
        /// the current workSpace
        /// The dictionary of frequencies
        public static Dictionary FindFrequencies(String workSpace) { 
            Dictionary frequencies = new Dictionary();
            // For each letter in the workSpace,...
            for (int i = 0; i < workSpace.Length; i++) {
                if (!Char.IsLetter(workSpace[i])) { continue; }
                String charStr = String.Format("{0}",workSpace[i]);
                // If the letter already exists, increment its frequency.
                if (frequencies.ContainsKey(charStr)) {
                    frequencies[charStr] = frequencies[charStr] + 1;
                } else {
                    // Otherwise add it with a frequency of one.
                    frequencies.Add(charStr,1);
                }
            }
            return frequencies;
        }
        /// 
        /// Return an ordered list of the most common letters in the workSpace
        /// 
        /// The user workSpace
        /// An ordered list
        public static List GetMostCommonLetters(String workSpace) {
            // Find the frequencies.
            List> freqList = FindFrequencies(workSpace).ToList();
            // Sort the frequencies
            freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value));
            // Pull out the letters in sorted order and return.
            List returnL = new List();
            foreach (var item in freqList) {
                returnL.Add(item.Key);
            }
            return returnL;
        }
        ///  Find the doubles in a string 
        ///  the string to analyze 
        ///  a list of doubles
        public static List GetDoubles(String workSpace) {
            List theList = new List();
            // For each character in the input, if the previous character is the same, it's a double. Add it to the list.
            for (int i=1; i
        /// Find the substrings of a given length in the workSpace.
        /// 
        /// the workSpace to analyze
        ///  the length of the substrings to look for
        /// the dictionary of substrings by frequency
        public static Dictionary GetSubstrings(String workSpace, int length) {
            Dictionary theList = new Dictionary();
            // Start at the beginning of the string, and advance the substring window by one until the substring segment would be outside the workSpace.
            for (int i=1; i
        /// find words of a given length. 
        ///  
        ///  The length to look for 
        ///  A string broken down by spaces
        ///  the words of the length with frequencies 
        public static Dictionary FindWordsOfLength(int length,String[] bySpace) {
            Dictionary wordsFreq = new Dictionary();
            // TODO Replace this with whitespace and punctuation removal
            // If the word ends in punctuation and is longer than the length or is equal to the length, add it to the list and track frequency.
            for (int i = 0; i < bySpace.Length; i++) {
                if (bySpace[i].Length == length || (bySpace[i].Length == length+1 && Char.IsPunctuation(bySpace[i][length]))) {
                    if (Char.IsPunctuation(bySpace[i][bySpace[i].Length-1])) {
                        bySpace[i] = bySpace[i].Substring(0,bySpace[i].Length-1);
                    }
                    if (wordsFreq.ContainsKey(bySpace[i])) {
                        wordsFreq[bySpace[i]] += 1;
                    } else {
                        wordsFreq.Add(bySpace[i],1);
                    }
                }
            }
            return wordsFreq;
        }
        /// 
        /// Get the top entries in a frequency map
        /// 
        /// Frequency map
        /// A list of a given length with the top entries
        public static List Top(Dictionary theList) {
            List> freqList = theList.ToList();
            List returnL = new List();
            freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value));
            for (int i = 0; i < 5 && i < freqList.Count; i++) {
                returnL.Add(freqList[i].Key);
            }
            return returnL;
        }
        ///  
        /// Print an ordered list with the frequency
        /// 
        ///  the frequency map 
        ///  String header 
        public static void PrintOrdered(Dictionary theList,String header) {
            List> freqList = theList.ToList();
            freqList.Sort((firstPair,nextPair)=>nextPair.Value.CompareTo(firstPair.Value));
            Console.Write(header);
            for (int i = 0; i < 5 && i < freqList.Count; i++) {
                Console.Write(String.Format("({0}){1} ",freqList[i].Key,freqList[i].Value));
            }
            Console.WriteLine("");
        }
        /// 
        /// Print an ordered list
        /// 
        ///  the frequency map 
        ///  String header 
        public static void PrintOrdered(List theList,String header) {
            Console.Write(header);
            foreach (String str in theList) {
                Console.Write(str);
                Console.Write(" ");
            }
            Console.WriteLine();
        }
        ///
        /// Analyze a workspace
        /// 
        /// workSpace to analyze
        public void Frequency(String workSpace) {
            //Show the individual letter frequeuncy.
            Console.ForegroundColor = ConsoleColor.Cyan;
            PrintOrdered(FindFrequencies(workSpace),"Top letters by frequency: "); 
            //Show the doubled letters
            Console.ForegroundColor = ConsoleColor.Green;
            PrintOrdered(GetDoubles(workSpace),"The following letters are doubled in the workspace: ");
            Console.ForegroundColor = ConsoleColor.Yellow;
            PrintOrdered(GetSubstrings(workSpace,2),"Top substrings of length 2: ");
            Console.ForegroundColor = ConsoleColor.Magenta;
            PrintOrdered(GetSubstrings(workSpace,3),"Top substrings of length 3: ");
            String[] bySpace = workSpace.Split(' '); 
            //Find the words of a given length
            Console.ForegroundColor = ConsoleColor.White;
            PrintOrdered(FindWordsOfLength(1,bySpace),"Words of length 1: ");
            Console.ForegroundColor = ConsoleColor.Yellow;
            PrintOrdered(FindWordsOfLength(2,bySpace),"Words of length 2: ");
            Console.ForegroundColor = ConsoleColor.Magenta;
            PrintOrdered(FindWordsOfLength(3,bySpace),"Words of length 3: ");
            Console.ResetColor();
        }
        /// 
        /// Show the statistical frequencies.
        /// 
        public void FrequencyInfo() {
            // Thanks to http://norvig.com/mayzner.html for this info.
            // By letter
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.WriteLine("Letters by frequency:");
            for (int i=0; i < this._sb.EngCommon.Length; i++) {
                Console.Write(this._sb.EngCommon[i]);
                Console.Write(" ");
            }
            Console.Write('\n');
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Possible doubles: ll ee ss oo tt ff rr nn pp cc bb mm gg uu zz aa");
            // By Substring 2,3 characters in length
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Top sequences of N characters where N=...");
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("2: th he in er an re on at en nd");
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("3: the and ing ion tio end ati for her ter");
            // By word 1,2,3 chars in length
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Top words of length...");
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("1: I a");
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("2: of to in is it as be by on he");
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("3: the and for was not are his but had you");
            Console.ResetColor();
        }
        /// 
        /// Find out if the workSpace has a one-to-one relationship with the input.
        ///  
        /// the workSpace
        /// the user input
        public bool OneToOneQuery(String workSpace, String inputText) {
            Dictionary relation = new Dictionary();
            //Seed the keys so that we print efficiently.
            StringBuilder subKey = new StringBuilder(); 
            StringBuilder encKey = new StringBuilder();
            subKey.Append("True. These are one-to-one.\n");
            subKey.Append("\nFinal-to-input key:\n");
            subKey.Append("sub decrypt ");
            encKey.Append("sub encrypt ");
            for (int i = 0; i < workSpace.Length; i++) {
                // For each non-whitespace character, if the relation is  known, ... 
                if (!Char.IsWhiteSpace(workSpace[i])) { 
                    if (relation.ContainsKey(workSpace[i])) {
                        // if the relation doesn't match up, we found the mismatch and should return false.
                        if (relation[workSpace[i]] != inputText[i]) {
                            Console.Error.WriteLine(String.Format("Character {0} repeated. These are not one-to-one.",workSpace[i]));
                            return false;
                        }
                        // Otherwise add the new relation pairing.
                    } else {
                        if ( workSpace[i] != inputText[i] ) {
                            relation.Add(workSpace[i],inputText[i]);
                            encKey.Append(String.Format("{0}={1} ",inputText[i],workSpace[i]));
                            subKey.Append(String.Format("{0}={1} ",workSpace[i],inputText[i]));
                        }
                    }
                }
            }
            // Print the keys and return true.
            subKey.Append("\nInput-to-final key:");
            Console.WriteLine(subKey.ToString());
            Console.WriteLine(encKey.ToString());
            return true;
        }
        /// 
        /// Show the numeric difference between two characters -- useful for identifying Caesarian ciphers
        ///  
        /// The user's input
        /// -99 if malformated or the difference between characters.
        public int Diff(String[] line) {
            // If the number of arguments or format is wrong, return -99
            if (line.Length != 4 || line[2].Length != 1 || line[3].Length != 1) {
                Console.Error.WriteLine("Bad formatting");
                return -99;
            }
            //Otherwise return -99
            char first = line[2][0];
            char second = line[3][0];
            Console.WriteLine(String.Format("These are different by {0}.",first-second));
            return (first-second);
        }
        /// 
        /// Printout the info of a character
        /// 
        /// line to analyze
        public void CharInfo(String[] line) {
            if (line == null || line.Length != 3 || line[2].Length != 1) { 
                Console.Error.WriteLine("Malformed");
                return;
            }
            // Print the ascii value of a character.
            Console.WriteLine(String.Format("Character: {0}\nASCII Value: {1}",line[2][0],(int)line[2][0]));
            if (Char.IsLetter(line[2][0])) {
                // If the character is a letter, include the alphabet index
                Console.WriteLine(String.Format("Alphabet index: {0}",(Char.IsUpper(line[2][0])) ? (int)line[2][0] - (int)'A' : (int)line[2][0] - (int)'a'));
            }
        }
        public void HexPrint(String line) {
            Console.WriteLine("Char - Dec - Hex");
            foreach (char i in line.ToCharArray()) {
                Console.WriteLine("{0} -- {1} -- {2}",i,(int)i,Convert.ToByte(i));
            }
        }
        
        //Analysis doesn't handle encryption or decryption, but we want to use the same code for subscribing.
        public override String Encrypt(string workSpace,String ciphetText,String[] line) { return workSpace; }
        public override String Decrypt(string workSpace,String ciphetText,String[] line) { return workSpace; }
    }
}