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

namespace Anyanka{
    public class AnyankaKeys{
        
        public static readonly char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".ToCharArray();
        public static readonly byte[] PRIVATE_KEY = Enumerable.Range(0, 0x100).Select<int, byte>(i => (byte)i).ToArray<byte>();
        const int MULTIPLIER = 1;
        const byte MULTIPLIER_JUMPS = 3;
        const byte MULTIPLIER_JUMP = 7;
        public static readonly uint[] PRIMES = new uint[]{
            0x3FFFFFFB, 0x3FFFFF29, 0x3FFFFF0B, 
            0x2FFFFFFF, 0x2FFFFF5B, 0x1FFFFFFF, 
            0x279DB13D, 0x279DB113, 
            0x2AAAAAAB, 0x1555556B, 0x27D4EB2F, 0x165667B1, 
            0x1000007F, 0x3B9ACA07, 0x01000193, 0x1EADBEEF, 0x0AFEBABE, 0x0BADC0DE, 0x3B9AC9C1, 0x1D295C4D, 0x990BF9F, 0x1CFAA2DB, 
            0xDEADBEEF, 0xCAFEBABF, 0xBAADF00D
        };
        public static readonly double LOGARITHM_256 = Math.Log(0x100);

        public static readonly Regex RE_KEY = new Regex(@"^[a-z_][a-z0-9_]*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        public delegate byte ChangeBaseInputHandler<T>(T value);
        public delegate void ChangeBaseOutputHandler(byte value);

        private char[] alphabet;
        private Dictionary<char, int> dictionary;
        private byte[] private_key;
        private int multiplier;
        private byte multiplier_jumps;
        private byte multiplier_jump;
        private uint[] primes;
        private int _base;
        private int encrypt_i = 0;
        private byte[] password = new byte[]{0};

        public AnyankaKeys(object? inputs = null){
            
            alphabet = unique<char>(get_array<char>(get<object>("alphabet", inputs, ALPHABET) ?? ALPHABET));
            dictionary = new Dictionary<char, int>();
            private_key = get_numbers<byte>(get<object>("private_key", inputs, PRIVATE_KEY) ?? PRIVATE_KEY);
            multiplier = get_number<int>(get<object>("multiplier", inputs, MULTIPLIER) ?? MULTIPLIER);
            multiplier_jumps = get_number<byte>(get<object>("multiplier_jumps", inputs, MULTIPLIER_JUMPS) ?? MULTIPLIER_JUMPS);
            multiplier_jump = get_number<byte>(get<object>("multiplier_jump", inputs, MULTIPLIER_JUMP) ?? MULTIPLIER_JUMP);
            primes = get_numbers<uint>(get<object>("primes", inputs, PRIMES) ?? PRIMES);
            _base = get_number<int>(get<object>("base", inputs, alphabet.Length) ?? alphabet.Length);

            for(int i = 0; i < alphabet.Length; i++)
                dictionary[alphabet[i]] = i;

            set_password(get<string>("password", inputs, "") ?? "");

            if(_base < alphabet.Length)
                _base = alphabet.Length;

        }

        public void set_password(string password){

            List<byte> changed = new List<byte>();

            if(!string.IsNullOrEmpty(password))
                change_base<char>(password, _base, 0x100, (char value) => (byte)value, (byte value) => changed.Add(value));

            if(changed.Count == 0)
                changed.Add(0);

            this.password = changed.ToArray<byte>();

        }

        private uint get_multiplier(int i){
            
            uint value = (uint)(i + multiplier + primes[
                i = (i + multiplier_jump) % primes.Length
            ]) & 0x3FFFFFFF;

            for (int j = 0; j < multiplier_jumps; j++){

                int shift = 13 + i % 8;

                value = ((value ^ (
                    (i & 1) == 1 ? value >> shift : value << shift
                )) + primes[
                    i = (i + multiplier_jump) % primes.Length
                ]) & 0x3FFFFFFF;

            }

            return value;
        }

        public string encrypt(string data){
            
            int i = (int)(get_multiplier(encrypt_i + data.Length + (int)(DateTime.Now.Ticks % 1000)) % _base);
            string summatory = alphabet[i].ToString();
            byte[] bytes = Encoding.UTF8.GetBytes(data);
            int j = (int)Math.Ceiling(bytes.Length * LOGARITHM_256 / Math.Log(_base)) + 2;
            char[] encrypted = new char[j];
            
            encrypt_i = (encrypt_i + i) % _base;

            change_base<byte>(bytes, _base, 0x100, (byte value) => value, (byte value) => {

                uint multiplier = get_multiplier(i ++);

                encrypted[-- j] = alphabet[(multiplier + value + private_key[
                    multiplier / _base % private_key.Length
                ] + password[multiplier % password.Length]) % _base];
                
            });

            return summatory + new string(encrypted, j, encrypted.Length - j);
        }

        public string decrypt(string data){
            
            int i = dictionary[data[0]] + data.Length - 1;
            int j = (int)Math.Ceiling(data.Length * Math.Log(_base) / LOGARITHM_256) + 2;
            byte[] decrypted = new byte[j];

            change_base<char>(data.Substring(1), 0x100, _base, (char value) => {

                uint multiplier = get_multiplier(-- i);

                return (byte)(((dictionary[value] - private_key[
                    multiplier / _base % private_key.Length
                ] - multiplier - password[multiplier % password.Length]) % _base + _base) % _base);
            }, (byte value) => {
                decrypted[-- j] = value;
            });

            return Encoding.UTF8.GetString(decrypted, j, decrypted.Length - j);
        }

        public static List<string> get_keys(object? items){

            List<string> keys = new List<string>();
            
            if(items is string item_string){
                if(RE_KEY.IsMatch(item_string))
                    keys.Add(item_string);
            }else if(items is IEnumerable<string> strings){
                foreach(string item_i in strings)
                    if(!keys.Contains(item_i) && RE_KEY.IsMatch(item_i))
                        keys.Add(item_i);
            }else if(items is IEnumerable<object?> list)
                foreach(object? item in list){
                    if(item == null)
                        continue;
                    if(item is string string_item){
                        if(!keys.Contains(string_item) && RE_KEY.IsMatch(string_item))
                            keys.Add(string_item);
                    }else 
                        foreach(string key in get_keys(item))
                            if(!keys.Contains(key) && RE_KEY.IsMatch(key))
                                keys.Add(key);
                }

            return keys;
        }
        
        public static List<Dictionary<string, object?>> get_dictionaries(object? items){

            List<Dictionary<string, object?>> dictionaries = new List<Dictionary<string, object?>>();

            if(items is Dictionary<string, object?> dictionary)
                dictionaries.Add(dictionary);
            else if(items is IEnumerable<object?> list)
                foreach(object? item in list)
                    dictionaries.AddRange(get_dictionaries(item));

            return dictionaries;
        }

        public static T? get<T>(object keys, object? dictionaries, T? _default = default(T?)){
            
            List<string> keys_list = get_keys(keys);

            if(keys_list.Count != 0)
                foreach(Dictionary<string, object?> dictionary in get_dictionaries(dictionaries))
                    foreach(string key in keys_list)
                        if(dictionary.TryGetValue(key, out object? value) && value is T typed)
                            return typed;
            return _default;
        }

        public static T[] unique<T>(IEnumerable<T> items){
            return items.Distinct<T>().ToArray<T>();
        }

        public static void change_base<T>(
            IEnumerable<T> data, 
            int to_base, 
            int from_base = 0x100, 
            ChangeBaseInputHandler<T>? input_handler = null, 
            ChangeBaseOutputHandler? output_handler = null
        ){

            int stack = 0;
            bool has = false;
            
            if(input_handler == null)
                input_handler = (T value) => Convert.ToByte(value);
            if(output_handler == null)
                output_handler = (byte value) => { };

            foreach(T value in data){
                stack = stack * from_base + input_handler(value);
                while(stack >= to_base){
                    has = true;
                    output_handler((byte)(stack % to_base));
                    stack /= to_base;
                }
            }

            if(!has || stack != 0)
                output_handler((byte)stack);

        }

        public static T[] get_array<T>(object data){
            if(data is T item_t)
                return new T[]{item_t};
            if(data is string text && typeof(T) == typeof(char))
                return text.ToCharArray() as T[] ?? new T[]{};
            if(data is IEnumerable<T> list_t)
                return list_t.ToArray();
            if(data is IEnumerable list){

                List<T> results = new List<T>();

                foreach(object? item in list)
                    if(item is T typed)
                        results.Add(typed);

                return results.ToArray();
            }
            return new T[]{};
        }

        public static T get_number<T>(object data){
            if(data is T item)
                return item;
            return (T)Convert.ChangeType(data, typeof(T));
        }

        public static T[] get_numbers<T>(object data){
            if(data is T item_t)
                return new T[]{item_t};
            if(data is T[] item_set)
                return item_set;
            if(data is IEnumerable list){

                List<T> results = new List<T>();

                foreach(object? item in list)
                    if(item != null)
                        results.Add(get_number<T>(item));

                return results.ToArray();
            }
            return new T[]{};
        }

    }
}