import itertools

HASH_MULT_CONSTANT = 31


def java_hash(inputstring, hash_table_size):
    """Java string hash implementation"""
    result = 0
    n = len(inputstring)
    for i in range(0, n):
        result += HASH_MULT_CONSTANT ** (n-1-i) * ord(inputstring[i])
    result %= 2**32
    return result % hash_table_size


def generate_java_hash_strings(generator, length, hash_table_size):
    """Generates a dictionary of (hash value, string) pairs with strings of the specified length."""
    keys = {}
    characters = generator.get_char_set()
    for s in itertools.product(characters, repeat=length):
        hash_val = java_hash(s, hash_table_size)
        keys[hash_val] = ''.join(s)
    return keys


def generate_via_half_string_merge(generator, target, collisions, hash_table_size, forbidden_string):
    """Returns a list of strings that meet the required hash target, excluding the forbidden string. Uses the approach
       of generating half of the string and then finding a potential match based on the additive nature of the hash
       function.  Incrementally increases the length of the string to favor small strings.  Can suffer from exponential
       time and memory usage."""
    length = 1  # We could start higher but the first lengths go quickly so why not?
    pre_keys = {0: ''}
    post_keys = generate_java_hash_strings(generator, 1, hash_table_size)
    # multfactor should always equal HASH_MULT_CONSTANT ** length of post_strings in post_keys
    multfactor = HASH_MULT_CONSTANT
    ret = []

    while True:
        for pre_hash in pre_keys:
            effective_target = (target - (pre_hash * multfactor)) % hash_table_size
            if effective_target in post_keys:
                pre_string = pre_keys[pre_hash]
                post_string = post_keys[effective_target]
                if pre_string + post_string != forbidden_string:
                    ret.append(pre_string + post_string)

                if len(ret) >= collisions:
                    ret.sort()
                    return ret

        # Update variables for next loop
        length += 1
        pre_keys = post_keys  # pre_keys follows 1 step behind post_keys
        if length % 2 != 0:
            # we update to a longer post_keys at every odd length
            post_keys = generate_java_hash_strings(generator, 1 + length // 2, hash_table_size)
            multfactor *= HASH_MULT_CONSTANT
