import json
import hashlib
import string
import random
import utils.objectUtils as objUtils
import string_format as format 

def hash(value):
    '''
        Generates a sha256 hash from the string provided.

        ----------
        Arguments
        -----------------
        `value` {str}
            The string to calculate the hash on.

        Return
        ----------
        `return` {str}
            The sha256 hash
    '''
    json_str = json.dumps(value).encode('utf-8')
    hex_dig = hashlib.sha256(json_str).hexdigest()
    return hex_dig


def rand(length=12, **kwargs):
    '''
        Generates a cryptographically secure random string.


        ----------
        Arguments
        -----------------
        `length`=12 {int}
            The number of characters that the string should contain.

        Keyword Arguments
        -----------------
        `upper_case`=True {bool}
            If True, uppercase letters are included.
            ABCDEFGHIJKLMNOPQRSTUVWXYZ

        `lower_case`=True {bool}
            If True, lowercase letters are included.
            abcdefghijklmnopqrstuvwxyz

        `digits`=True {bool}
            If True, digits are included.
            0123456789

        `symbols`=False {bool}
            If True, symbols are included.
            !"#$%&'()*+,-./:;<=>?@[]^_`{|}~

        `exclude`=[] {string|list}
            Characters to exclude from the random string.

        Return
        ----------
        `return` {str}
            A random string of N length.
    '''

    uppercase = objUtils.get_kwarg(['upper case', 'upper'], True, bool, **kwargs)
    lowercase = objUtils.get_kwarg(['lower case', 'lower'], True, bool, **kwargs)
    digits = objUtils.get_kwarg(['digits', 'numbers', 'numeric', 'number'], True, bool, **kwargs)
    symbols = objUtils.get_kwarg(['symbols', 'punctuation'], False, bool, **kwargs)
    exclude = objUtils.get_kwarg(['exclude'], [], (list, string), **kwargs)

    choices = ''
    if uppercase is True:
        choices += string.ascii_uppercase
    if lowercase is True:
        choices += string.ascii_lowercase
    if digits is True:
        choices += string.digits
    if symbols is True:
        choices += string.punctuation

    if len(exclude) > 0:
        if isinstance(exclude, str):
            exclude = list(exclude)
        for e in exclude:
            choices = choices.replace(e, '')

    return ''.join(random.SystemRandom().choice(choices) for _ in range(length))


def variations(value,**kwargs):
    '''
        Generates simple variations of the string provided.

        ----------
        Arguments
        -----------------
        `string` {str}
            The string to generate variations of

        Keyword Arguments
        -----------------
        `typos`=True {bool}
            if True typos are generated:
            missed keys, wrong keys, transposed keys and double characters.
        `case`=True {bool}
            if True case variations are generated:
            snake case, screaming snake case, title case, reverse title case.
            
            This will apply to all typos as well.

        Return
        ----------
        `return` {str}
            A list of variations.

        Example
        ----------
        BeepBoop => ['BEEPBOOPBLEEPBLORP','beepboopbleepblorp','beep_boop','BEEP_BOOP']
    '''
    typos = objUtils.get_kwarg(['typos'], True, bool, **kwargs)
    case_variations = objUtils.get_kwarg(['case'], True, bool, **kwargs)
    
    value = str(value)
    varis = []
    if typos is True:
        varis.extend(generate_typos(value))
    if case_variations is True:

        varis.append(format.to_snake_case(value))
        varis.append(format.to_screaming_snake(value))
        varis.extend(format.title_case(varis))
        varis.extend(format.title_case(varis,True))
    varis = list(set(varis))
    return varis

TYPO_PROXIMITY_KEYBOARD = {
        '1': "2q",
        '2': "1qw3",
        '3': "2we4",
        '4': "3er5",
        '5': "4rt6",
        '6': "5ty7",
        '7': "6yu8",
        '8': "7ui9",
        '9': "8io0",
        '0': "9op-",
        '-': "0p",
        'q': "12wa",
        'w': "qase32",
        'e': "wsdr43",
        'r': "edft54",
        't': "rfgy65",
        'y': "tghu76",
        'u': "yhji87",
        'i': "ujko98",
        'o': "iklp09",
        'p': "ol-0",
        'a': "zswq",
        's': "azxdew",
        'd': "sxcfre",
        'f': "dcvgtr",
        'g': "fvbhyt",
        'h': "gbnjuy",
        'j': "hnmkiu",
        'k': "jmloi",
        'l': "kpo",
        'z': "xsa",
        'x': "zcds",
        'c': "xvfd",
        'v': "cbgf",
        'b': "vnhg",
        'n': "bmjh",
        'm': "nkj"
    }


def generate_typos(text):
    if len(text) == 0:
        return []
    typos = []
    typos.extend(missed_key_typos(text))
    typos.extend(wrong_key_typos(text))
    typos.extend(transposed_chars(text))
    typos.extend(double_char_typos(text))
    if len(typos) > 1:
        typos = list(set(typos))
    return typos

def missed_key_typos(word):
    word = word.lower()
    typos = []
    # length = len(word)
    
    for idx,letter in enumerate(word):
        tempword = replace_at(word,'',idx)            
        typos.append(tempword)
        
    if len(typos) > 1:
        typos = list(set(typos))
    return typos

def wrong_key_typos(word,keyboard=TYPO_PROXIMITY_KEYBOARD):
    word = word.lower()
    typos = []
    
    
    for letter in word:
        if letter in keyboard:
            temp_word = word
            for char in keyboard[letter]:
                typos.append(temp_word.replace(letter,char).strip())
                
    # print(f"typos: ",typos)
    if len(typos) > 1:
        typos = list(set(typos))
    return typos

def transposed_chars(word):
    word = word.lower()
    typos = []
    
    for idx,letter in enumerate(word):
        tempword = word
        tempchar = tempword[idx]
        if idx + 1 != len(tempword):
            tempword = replace_at(tempword,tempword[idx + 1],idx)
            tempword = replace_at(tempword,tempchar,idx + 1)
            typos.append(tempword)
    # print(f"typos: ",typos)
    if len(typos) > 1:
        typos = list(set(typos))    
    return typos

def double_char_typos(word):
    word = word.lower()
    typos = []
    
    for idx,letter in enumerate(word):
        tempword = word[0:idx]
        tempword += word[idx-1:]
        # if idx != len(word) - 1:
            # tempword += word[idx + 1]
        if len(tempword) == len(word) + 1:
            typos.append(tempword)
        
    if len(typos) > 1:
        typos = list(set(typos))
    return typos

def replace_at(s, newstring, index, nofail=False):
    # raise an error if index is outside of the string
    if not nofail and index not in range(len(s)):
        raise ValueError("index outside given string")

    # if not erroring, but the index is still not in the correct range..
    if index < 0:  # add it to the beginning
        return newstring + s
    if index > len(s):  # add it to the end
        return s + newstring

    # insert the new string between "slices" of the original
    return s[:index] + newstring + s[index + 1:]



print(variations("colemen",case=False))
# print(missed_key_typos("colemen"))
