#!/usr/bin/python3
import os
import sys
import time
import glob
import random
import math
import argparse
import subprocess


TARGET_DIR = os.path.join(os.path.dirname(sys.argv[0]), 'wordlists')


def clone_wordlists():
    """Clone a copy of the wordlists if needed.  Update the wordlist if it
    hasn't been in a while.
    """
    if os.path.exists(TARGET_DIR):
        try:
            last_modified = os.stat(os.path.join(TARGET_DIR, '.git/FETCH_HEAD')).st_mtime
        except FileNotFoundError:
            last_modified = 0
        if last_modified < time.time() - 7 * 24 * 60 * 60: # Check for updates weekly
            subprocess.check_call(['git', 'pull'], cwd=TARGET_DIR)
    else:
        subprocess.check_call(['git', 'clone', 'https://github.com/imsky/wordlists.git'],
            cwd=os.path.dirname(TARGET_DIR))


def load(part_of_speech):
    """Load all unique words of a given part of speech from the wordlists.
    """
    words = set()
    for filename in glob.glob(os.path.join(TARGET_DIR, part_of_speech, '*.txt')):
        words.update(w.strip() for w in open(filename, encoding='utf-8') if w.strip())
    return words


def main(argv=None):
    """Generate a random-but-memorable username.
    """
    if argv is None:
        argv = sys.argv
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=main.__doc__)
    parser.add_argument("--adjectives", "-a", default=1, type=int,
                        help="number of adjectives")
    parser.add_argument("--divider", default='',
                        help="character or string between words")
    parser.add_argument("--bits", default=0, type=int,
                        help="minimum bits of entropy")
    parser.add_argument("--verbose", action='store_true',
                        help="be chatty")
    args = parser.parse_args(argv[1:])

    clone_wordlists()
    # load() returns a set, but we need a subscriptable type, so convert to
    # lists.  But there's no reason to sort them, since we're only taking
    # random picks from the lists.
    nouns = list(load('nouns'))
    adjectives = list(load('adjectives'))

    adjective_count = args.adjectives
    # Determine the number of bits of randomness given the adjectives count
    bits = math.log2(len(nouns)) + adjective_count * math.log2(len(adjectives))
    # Increase the number of adjectives as needed to achieve the minimum bits
    # of randomness
    while bits < args.bits:
        adjective_count += 1
        bits = math.log2(len(nouns)) + adjective_count * math.log2(len(adjectives))

    if args.verbose:
        sys.stderr.write(f"Found {len(nouns)} nouns and {len(adjectives)} adjectives;"
            f" using {adjective_count} adjective{adjective_count != 1 and 's' or ''} "
            f"and one noun giving {bits :0.2f} bits of entropy.\n")

    # Use the best source of randomness available.
    prng = random.SystemRandom()
    words = []
    for i in range(adjective_count):
        words.append(prng.choice(adjectives))
        # We don't remove used adjectives from the list of choices, so that
        # means it's possible to have repeats, such as "wise-small-wise-ant"
    words.append(prng.choice(nouns))
    result = args.divider.join(words)
    sys.stdout.write(f"{result}\n")
    return 0


if __name__ == '__main__':
    sys.exit(main())
