﻿using System.Collections.Generic;
using System.Text;

namespace VigenereCipherCryptanalysis
{
    /// <summary>
    /// FreqAnalisator can be used for frequency cryptanalysis of any texts consisting
    /// of letters of the alphabet and frequencies from symbolsFrequency 
    /// </summary>
    class FreqAnalisator
    {
        //Using ring Z31: АБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЫЬЭЮЯ_, where А = 0, _=space=30
        private const int ringSize = 31;
        private Dictionary<char, float> symbolsFrequency = new Dictionary<char, float>()
        {
            {'А', 0.07998f }, {'Б', 0.01592f }, {'В', 0.04533f }, {'Г', 0.01687f }, {'Д', 0.02977f }, {'Е', 0.08496f }, {'Ж', 0.0094f },
            {'З', 0.01641f }, {'И', 0.08575f }, {'К', 0.03486f }, {'Л', 0.04343f }, {'М', 0.03203f }, {'Н', 0.067f }, {'О', 0.10983f },
            {'П', 0.02804f }, {'Р', 0.04746f }, {'С', 0.05473f }, {'Т', 0.06318f }, {'У', 0.02615f }, {'Ф', 0.00267f }, {'Х', 0.00966f },
            {'Ц', 0.00486f }, {'Ч', 0.0145f }, {'Ш', 0.00718f }, {'Щ', 0.00361f }, {'Ь', 0.01735f }, {'Ы', 0.01898f }, {'Э', 0.00331f },
            {'Ю', 0.00639f }, {'Я', 0.02001f }, {'_', 0.175f },
        };

        //compare for sort probability
        private static int ComparePair (KeyValuePair<int,float> a, KeyValuePair<int, float> b)
        {
            return a.Value.CompareTo(b.Value);
        }

        /// <summary>
        /// Return list of shifts for cipherText in descending order of probability of a correct shift
        /// </summary>
        public List<int> TextAnalysis(string cipherText)
        {
            List<KeyValuePair<int, float>> results = new List<KeyValuePair<int, float>>();
            List<int> returnValue = new List<int>();
            string newText;

            for (int shift = 0; shift < ringSize; shift++)
            {
                float pirsonTestResults;
                Dictionary<char, float> symbolsNumber = new Dictionary<char, float>()
                {
                {'А', 0f }, {'Б', 0f }, {'В', 0f }, {'Г', 0f }, {'Д', 0f }, {'Е', 0f }, {'Ж', 0f },
                {'З', 0f }, {'И', 0f }, {'К', 0f }, {'Л', 0f }, {'М', 0f }, {'Н', 0f }, {'О', 0f },
                {'П', 0f }, {'Р', 0f }, {'С', 0f }, {'Т', 0f }, {'У', 0f }, {'Ф', 0f }, {'Х', 0f },
                {'Ц', 0f }, {'Ч', 0f }, {'Ш', 0f }, {'Щ', 0f }, {'Ь', 0f }, {'Ы', 0f }, {'Э', 0f },
                {'Ю', 0f }, {'Я', 0f }, {'_', 0f },
                };
                Dictionary<char, float> symbolsFrequency = new Dictionary<char, float>();

                //try shift
                newText = Decrypt(cipherText, shift);

                //count number each symbol in text
                for (int i = 0; i < newText.Length; i++)
                {
                        symbolsNumber[newText[i]]++;
                }

                //calculate frequency each symbol in text 
                foreach (KeyValuePair<char, float> kvp in symbolsNumber)
                {
                    symbolsFrequency.Add(kvp.Key, kvp.Value / newText.Length);
                }

                pirsonTestResults = PirsonTest(symbolsFrequency, newText.Length);

                results.Add(new KeyValuePair<int, float>(shift, pirsonTestResults));
            }

            results.Sort(ComparePair);

            foreach (KeyValuePair<int, float> kvp in results)
            {
                returnValue.Add(kvp.Key);
            }

            return returnValue;
        }

        //Pirson's chi-squared test
        private float PirsonTest(Dictionary<char, float> symbols, int cipherTextSize)
        {
            float sum = 0;
            //А - 1040, _ - 95 in unicode
            for (int i=1040; i<1072; i++)
            {
                if (i!=1049 && i!=1066) //alphabet don't contain "Й" and "Ъ", "Й"-code in Unicode is 1049, "Ъ"-code in unicode is 1066
                {
                    sum += ((symbols[(char)i] - symbolsFrequency[(char)i]) * (symbols[(char)i] - symbolsFrequency[(char)i])) / symbolsFrequency[(char)i];
                }
            }
            sum += ((symbols[(char)95] - symbolsFrequency[(char)95]) * (symbols[(char)95] - symbolsFrequency[(char)95])) / symbolsFrequency[(char)95]; // _ - 95 in unicode
            sum *= cipherTextSize;

            return sum;
        }

        public string Decrypt(string chiperText, int encryptShift)
        {
            StringBuilder strBilder = new StringBuilder();

            //DecryptShift allow use addition instead subtraction in the conculations in the ring
            int decryptShift = ringSize - encryptShift;

            for (int i=0; i<chiperText.Length; i++)
            {
                strBilder.Append(ShiftChar(chiperText[i], decryptShift));
            }
            return strBilder.ToString();
        }

        //shift symbol in the ring Z31 (А(0)....Я(30), _(31), where _ - space)
        //А - 1040, _ - 95 in unicode
        public char ShiftChar(char a, int shift)
        {
            int ringElement;

            if (a == 95)
                ringElement = 30;
            else if (a < 1049) //alphabet don't contain "Й", "Й"-code in Unicode is 1049
                ringElement = a - 1040;
            else if (a < 1066) //alphabet don't contain "Ъ", "Ъ"-code in Unicode is 1066
                ringElement = a - 1041;
            else
                ringElement = a - 1042;

            ringElement = (ringElement + shift) % ringSize;

            if (ringElement == 30) //space
                a = (char)95;

            else if (ringElement < 9) //alphabet don't contain "Й", "Й"-code in Unicode is 1049
                a = (char)(ringElement + 1040);
            else if (ringElement < 25) //alphabet don't contain "Ъ", "Ъ"-code in Unicode is 1066
                a = (char)(ringElement + 1041);
            else
                a = (char)(ringElement + 1042);
            return a;
        }
    }
}
