"""Search seed such that:
sha512(seed + string).startswith(prefix)

Run like:

time for i in $(seq 2 7) ; do hyperfine --show-output "python perf.py --version $i AFPy 00000" > hyperfine-AFPy.$i; done
"""

import argparse
import sys
from itertools import product
from random import choices, choice
from types import SimpleNamespace
from string import ascii_uppercase, ascii_lowercase
from hashlib import sha512

POOL = ascii_uppercase + "[\\]^_`" + ascii_lowercase


def main_v1():
    args = parse_args()
    already_checked = []
    while True:
        c = "".join(choice(POOL) for _ in range(10))
        if c in already_checked:
            continue
        already_checked.append(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)
        print("Searching")


def main_v2():
    args = parse_args()
    already_checked = set()
    while True:
        c = "".join(choice(POOL) for _ in range(10))
        if c in already_checked:
            continue
        already_checked.add(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)
        print("Searching")


# Use choices
def main_v3():
    args = parse_args()
    already_checked = set()
    while True:
        c = "".join(choices(POOL, k=10))
        if c in already_checked:
            continue
        already_checked.add(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)
        print("Searching")


# Use product
def main_v4():
    args = parse_args()
    already_checked = set()
    for c in product(POOL, repeat=10):
        c = "".join(c)
        if c in already_checked:
            continue
        already_checked.add(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)
        print("Searching")


# Drop print
def main_v5():
    args = parse_args()
    already_checked = set()
    for c in product(POOL, repeat=10):
        c = "".join(c)
        if c in already_checked:
            continue
        already_checked.add(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)


# Drop useless lines
def main_v6():
    args = parse_args()
    for c in product(POOL, repeat=10):
        c = "".join(c)
        digest = sha512((c + args.string).encode("UTF-8")).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({c} + {args.string}) = {digest}")
            sys.exit(0)


# Bytes only
def main_v7():
    args = parse_args()
    string = args.string.encode("UTF-8")
    pool = POOL.encode("UTF-8")
    for c in product(pool, repeat=10):
        digest = sha512(bytes(c) + string).hexdigest()
        if digest.startswith(args.sha_prefix):
            print(f"sha512({bytes(c)} + {args.string}) = {digest}")
            sys.exit(0)


def parse_args():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("string")
    parser.add_argument("sha_prefix")
    parser.add_argument("--version", default="1")
    return parser.parse_args()


if __name__ == "__main__":
    globals()["main_v" + parse_args().version]()
