#!/usr/bin/env python
# -*- coding: utf-8 -*-

from typing import Any, Self, Callable, Optional, TypeVar, Iterable
from time import time
from re import compile as re_compile, Pattern as REPattern, I as RE_IgnoreCase

T = TypeVar("T", int, float, str, bytes)

class AnyankaKeys:

    __slots__:tuple[str] = (
        '__alphabet', '__dictionary', '__private_key', '__private_key_l',
        '__multiplier', '__primes', '__primes_l', '__multiplier_jumps',
        '__multiplier_jump', '__base', '__encrypt_i', '__password', '__password_l'
    )

    ALPHABET:tuple[str] = tuple("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
    PRIVATE_KEY:tuple[int] = tuple(range(0, 0x100))
    MULTIPLIER:int = 1
    MULTIPLIER_JUMPS:int = 3
    MULTIPLIER_JUMP:int = 7
    PRIMES:tuple[int] = (
        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
    )

    RE_KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IgnoreCase)

    def __init__(self:Self, inputs:Optional[dict[str, Any|None]] = None) -> None:
        
        self.__alphabet:tuple[str] = tuple(self.unique(self.get_value("alphabet", inputs, self.ALPHABET)))
        self.__dictionary:dict[str, int] = {value : index for index, value in enumerate(self.__alphabet)}
        self.__private_key:tuple[int] = tuple(self.get_value("private_key", inputs, self.PRIVATE_KEY))
        self.__private_key_l:int = len(self.__private_key)
        self.__multiplier:int = self.get_value("multiplier", inputs, self.MULTIPLIER)
        self.__primes:tuple[int] = tuple(self.get_value("primes", inputs, self.PRIMES))
        self.__primes_l:int = len(self.__primes)
        self.__multiplier_jumps:int = self.get_value("multiplier_jumps", inputs, self.MULTIPLIER_JUMPS)
        self.__multiplier_jump:int = self.get_value("multiplier_jump", inputs, self.MULTIPLIER_JUMP)
        l:int = len(self.__alphabet)
        self.__base:int = self.get_value("base", inputs, l)
        self.__encrypt_i:int = 0
        self.__password:tuple[int] = (0,)
        self.__password_l:int = 1

        self.set_password(self.get_value("password", inputs, ""))

        if self.__base > l:
            self.__base = l

    def set_password(self:Self, password:str) -> None:

        if password:

            password_changed:list[int] = []

            self.change_base(password, self.__base, input_handler = ord, output_handler = password_changed.append)
            self.__password = tuple(password_changed)

        else:
            self.__password = (0,)
            
        self.__password_l = len(self.__password)

    def __get_multiplier(self:Self, i:int) -> int:
        
        value:int = (i + self.__multiplier + self.__primes[
            i := (i + self.__multiplier_jump) % self.__primes_l
        ]) & 0x3FFFFFFF

        for _ in range(self.__multiplier_jumps):

            shift:int = 13 + i % 8

            value = ((value ^ (
                value >> shift if i & 1 else value << shift
            )) + self.__primes[
                i := (i + self.__multiplier_jump) % self.__primes_l
            ]) & 0x3FFFFFFF

        return value

    def encrypt(self:Self, data:str) -> str:

        i:int = self.__get_multiplier(self.__encrypt_i + len(data) + int(time() % 1 * 1000)) % self.__base
        summatory:str = self.__alphabet[i]
        encrypted:list[str] = []
        
        self.__encrypt_i = (self.__encrypt_i + i) % self.__base
        i -= 1

        def output_handler(value:int) -> None:
            nonlocal encrypted, i

            multiplier:int = self.__get_multiplier(i := i + 1)

            encrypted.append(self.__alphabet[(multiplier + value + self.__private_key[
                multiplier // self.__base % self.__private_key_l
            ] + self.__password[multiplier % self.__password_l]) % self.__base])

        self.change_base(data, self.__base, 0x100, 
            input_handler = ord, 
            output_handler = output_handler
        )

        return summatory + "".join(reversed(encrypted))

    def decrypt(self:Self, data:str) -> str:

        i:int = len(data[1:]) + self.__dictionary[data[0]]
        decrypted:list[str] = []

        def output_handler(value:int) -> None:
            nonlocal decrypted
            decrypted.append(chr(value))
        
        def input_handler(value:str) -> int:
            nonlocal i

            multiplier:int = self.__get_multiplier(i := i - 1)

            return (self.__dictionary[value] - self.__private_key[
                multiplier // self.__base % self.__private_key_l
            ] - multiplier - self.__password[multiplier % self.__password_l]) % self.__base
        
        self.change_base(data[1:], 0x100, self.__base, 
            input_handler = input_handler, 
            output_handler = output_handler
        )

        return "".join(reversed(decrypted))

    @classmethod
    def get_keys(cls:type[Self], *inputs:Iterable[Any|None]) -> list[str]:
        
        keys:list[str] = []

        for item in inputs:
            if isinstance(item, str):
                cls.RE_KEY.match(item) and item in keys or keys.append(item)
            elif isinstance(item, (list, tuple)):
                keys.extend(cls.get_keys(*item))

        return keys
    
    @classmethod
    def get_dictionaries(cls:type[Self], *inputs:Iterable[Any|None]) -> list[dict[str, Any|None]]:
        
        dictionaries:list[dict[str, Any|None]] = []

        for item in inputs:
            if isinstance(item, dict):
                dictionaries.append(item)
            elif isinstance(item, (list, tuple)):
                dictionaries.extend(cls.get_dictionaries(*item))

        return dictionaries

    @classmethod
    def get_value(
        cls:type[Self], 
        keys:str|list[str]|tuple[str, ...], 
        inputs:dict[str, Any|None]|Iterable[Any|None], 
        default:Optional[Any] = None
    ) -> Any|None:
        if len(keys := cls.get_keys(keys)):
            for dictionary in cls.get_dictionaries(inputs):
                for key in keys:
                    if key in dictionary:
                        return dictionary[key]
        return default
    
    @staticmethod
    def unique(items:Iterable[Any|None]) -> list[Any|None]:
        return list(dict.fromkeys(items))

    @staticmethod
    def change_base(
        data:Iterable[T], 
        to_base:int, 
        from_base:int = 0x100, 
        input_handler:Optional[Callable[[T], int]] = None, 
        output_handler:Optional[Callable[[int], None]] = None
    ) -> None:

        stack:int = 0
        value:int
        has:bool = False

        if input_handler is None:input_handler = lambda value:value
        if output_handler is None:output_handler = lambda value:value

        for value in data:
            stack = stack * from_base + input_handler(value)
            while stack >= to_base:
                has = True
                stack, value = divmod(stack, to_base)
                output_handler(value)

        (not has or stack) and output_handler(stack)