#!/usr/bin/python3
## -*- coding: utf-8 -*- vim:shiftwidth=4:expandtab:
##
## pwgen(1) clone: Passwords generator
## Copyright (c) 2018-2024 SATOH Fumiyasu @ OSSTech Corp., Japan
##               <https://www.OSSTech.co.jp/>
##
## SPDX-License-Identifier: GPL-3.0+
##

import sys
import re
## FIXME: Migrate to optparse
## https://click.palletsprojects.com/en/stable/why/#why-not-argparse
## https://qiita.com/rana_kualu/items/2ec43820e70cc5885e00
import argparse
import secrets

def seed2re(seed):
    return re.compile('[' + re.escape(seed) + ']')

seed_digits = '0123456789';
re_digits = seed2re(seed_digits)
seed_uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
re_uppers = seed2re(seed_uppers)
seed_lowers = 'abcdefghijklmnopqrstuvwxyz';
re_lowers = seed2re(seed_lowers)
seed_symbols = """#%&'()*+,-./:;<=>?@[]^_{|}~"""; ## Except '"', '$', '\', '`'
re_symbols = seed2re(seed_symbols)
seed_ambiguous = '0DOQ1lI2Z5S6G8B9q';
re_ambiguous = seed2re(seed_ambiguous)
seed_vowels = '01aeiouyAEIOUY';
re_vowels = seed2re(seed_vowels)

argp = argparse.ArgumentParser(prog=sys.argv[0])
argp.add_argument(
    'length',
    metavar='LENGTH',
    help='Password length',
    type=int,
    default=32,
    nargs='?',
)
argp.add_argument(
    'number',
    metavar='NUMBER',
    help='Number of passwords',
    type=int,
    default=8 if sys.stdout.isatty() else 1,
    nargs='?',
)
argp.add_argument(
    '-N', '--num-passwords',
    dest='num',
    metavar='NUMBER',
    help='NUMBER of passwords',
    type=int,
)
argp.add_argument(
    '-c', '--capitalize',
    help='Include at least one capital letter (default)',
    action='store_true',
    default=True,
)
argp.add_argument(
    '-A', '--no-capitalize',
    dest='capitalize',
    help='Do NOT include any capital letters',
    action='store_false',
)
argp.add_argument(
    '-n', '--numerals',
    help='Include at least one number (default)',
    action='store_true',
    default=True,
)
argp.add_argument(
    '-0', '--no-numerals',
    dest='numerals',
    help='Do NOT include any numerals',
    action='store_false',
)
argp.add_argument(
    '-y', '--symbols',
    help='Include at least one symbol',
    action='store_true',
    default=False,
)
argp.add_argument(
    '-B', '--ambiguous',
    help='Do NOT include ambiguous characters',
    action='store_false',
)
argp.add_argument(
    '-v', '--no-vowels',
    dest='vowels',
    help='Do NOT contain vowels or numbers that might be mistaken for vowels',
    action='store_false',
    default=True,
)
args = argp.parse_args(sys.argv[1:])

seed = seed_lowers
if args.numerals:
    seed += seed_digits
if args.capitalize:
    seed += seed_uppers
if args.symbols:
    seed += seed_symbols
if not args.ambiguous:
    seed = re_ambiguous.sub('', seed)
if not args.vowels:
    seed = re_vowels.sub('', seed)

for _ in range(args.number):
    while True:
        pw = ''.join([secrets.choice(seed) for _ in range(args.length)])
        if args.capitalize and re_uppers.search(pw) is None:
            continue
        if args.numerals and re_digits.search(pw) is None:
            continue
        if args.symbols and re_symbols.search(pw) is None:
            continue
        print(pw)
        break
