#!/usr/bin/env python3
'''Collect byte sequences generated by a terminal for various sequences, and generate a report.

'''
# pylint: disable=invalid-name

import argparse
from collections import OrderedDict
import json
import os
import platform
import sys

from terminalOutput import colors, promptColors
from terminalInput import BackException, QuitException, MenuChoice, chooseOne, displayableKey, getKey, \
        queryBasicKeys, rawStdin, readByte, readLine, yesNo
import terminalInput


#DEFAULT_KEYS = 'F12 Delete Backspace'.split()
#DEFAULT_KEYS = 'Home End PgUp PgDn Left Right Up Down'.split()
#DEFAULT_KEYS = 'Up Down'.split()
DEFAULT_KEYS = 'Left Right Up Down Delete Backspace Home End PgUp PgDn F12'.split()


parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('terminal_name', metavar='TERMINAL_NAME', nargs='?', help='the name of the terminal being tested',
                    default=os.getenv('TERM_PROGRAM') or os.getenv('TERM'))
parser.add_argument('-k', '--keys', metavar='KEY', nargs=1, help='the names of keys to test', default=DEFAULT_KEYS)
parser.add_argument('-m', '--modifiers', metavar='MOD', nargs=1, help='the modifier keys to test')
parser.add_argument('-y', '--yes', dest='yesToAll', action='store_true', help='answer "yes" to all yes/no questions')

args = parser.parse_args()


queryBasicKeys()

print(__doc__)

if args.modifiers:
    keyboardChoice = MenuChoice('Unspecified', mods=args.modifiers, name='Unspecified (modifiers from command line)')
else:
    modsByKeyboard = OrderedDict((
        ('Windows', 'Shift Ctrl Alt Windows'.split()),
        ('Windows (international)', 'Shift Ctrl Alt AltGr Windows'.split()),
        ('PC', 'Shift Ctrl Alt'.split()),
        ('PC (international)', 'Shift Ctrl Alt'.split()),
        ('Mac', 'Shift Ctrl Alt Command'.split()),
        ('Mac', 'Shift Ctrl Alt Command'.split()),
        ('Knight', 'Shift Top Ctrl Meta'.split()),
        ('Space-cadet', 'Shift Front Top Ctrl Meta Super Hyper'.split()),
        ('Symbolics', 'Shift Symbol Ctrl Meta Super Hyper'.split()),
        ('Amiga', 'Shift Ctrl Alt A'.split()),
        ('Other', None),
    ))

    def _displayKeyboard(name, mods):
        return '{c.bold}{}{c.reset} {}' \
                .format(name, '({})'.format(', '.join(mods)) if isinstance(mods, list) else '', c=colors)

    keyboardChoice = chooseOne(
        'Please choose your keyboard:',
        [MenuChoice(_displayKeyboard(name, mods), mods=mods, name=name) for name, mods in modsByKeyboard.items()]
    )

modifiers = keyboardChoice.mods
if modifiers is None:
    modifiers = []
    print()
    colors.printHeading('Please enter the names of the modifier keys on your keyboard, one per line:')
    print('''
(please use {c.yellow}Ctrl{c.reset} in place of {c.yellow}Control{c.reset}, and {c.yellow}Alt{c.reset} in place of
{c.yellow}Option{c.reset}, to maintain consistency across different keyboards)

Press {c.yellow}Enter{c.reset} to accept each modifier, and {c.yellow}Enter{c.reset} again on a blank entry when you are finished.
'''.format(c=colors))  # noqa: E501

    modNum = 1
    entry = readLine('{c.bold}Modifier {}:{c.reset} '.format(modNum, c=promptColors))
    while entry != '':
        modifiers.append(entry)
        modNum += 1
        entry = readLine('{c.bold}Modifier {}:{c.reset} '.format(modNum, c=promptColors))

system = platform.system()

env = OrderedDict((
    ('keyboard type', keyboardChoice.name),
    ('modifiers', ', '.join(modifiers)),
    ('TERM variable', os.getenv('TERM')),
    ('terminal program', args.terminal_name),
    ('terminal version', os.getenv('TERM_PROGRAM_VERSION')),
    ('platform machine', platform.machine()),
    ('platform system', system),
    ('platform release', platform.release()),
    ('platform version', platform.version()),
))

if system == 'Java':
    env['Java release'], env['Java vendor'], (env['Java VM name'], env['Java VM release'], env['Java VM vendor']), \
            (env['Java OS name'], env['Java OS version'], env['Java OS arch']) = platform.java_ver()

elif system == 'Linux':
    env['libc library'], env['libc version'] = platform.libc_ver()
    try:
        # pylint: disable=deprecated-method
        env['Linux distribution name'], env['Linux distribution version'], env['Linux distribution ID'] = \
            platform.linux_distribution()
    except AttributeError:
        # platform.linux_distribution() is deprecated since version 3.5, will be removed in version 3.7.
        pass

elif system == 'Windows':
    winver = getattr(platform, 'windows_ver', getattr(platform, 'win32_ver'))
    env['Windows release'], env['Windows version number'], env['Windows service pack'], env['Windows OS type'] = \
        winver()

elif system == 'Darwin':
    env['Mac OS release'], _, env['Mac OS machine'] = platform.mac_ver()

env['Notes'] = None

envKeyWidth = max(len(envKey) for envKey in env) + 2


def _envItemDisplay(envKey, value):
    return (
        '{c.bold}{: <{}}{c.reset} {c.green}{}{c.reset}'.format(envKey + ':', envKeyWidth, value, c=colors)
        if value is not None else
        '{c.bold}{: <{}}{c.reset} {c.dark.gray}{}{c.reset}'.format(envKey + ':', envKeyWidth, value, c=colors)
    )


def _printEnvironment():
    colors.printHeading('Detected environment:' if initialEnvironment else 'Environment:')
    print('\n'.join(' ' + _envItemDisplay(key, value) for key, value in env.items()))


def _confirmEnvironment():
    print()
    _printEnvironment()

    if args.yesToAll:
        return True

    sys.stdout.write('''
{c.bold}Does this look correct?{c.reset} {c.dark.gray}[Y/n]{c.reset} '''.format(c=colors))

    return yesNo(True)


initialEnvironment = True
while not _confirmEnvironment():
    initialEnvironment = False
    print()

    envItemChoice = chooseOne(
        'Choose an item to edit:',
        [MenuChoice(_envItemDisplay(key, value), key=key) for key, value in env.items()]
        + [MenuChoice('{c.bold}Done{c.reset}'.format(c=colors), key=None)]
    )

    while envItemChoice.key is not None:
        env[envItemChoice.key] = readLine(
            '{c.bold}{}:{c.reset} '.format(envItemChoice.key, c=promptColors),
            initialText=env[envItemChoice.key]
        )

        envItemChoice = chooseOne(
            'Choose an item to edit:',
            [MenuChoice(_envItemDisplay(key, value), key=key) for key, value in env.items()]
            + [MenuChoice('{c.bold}Done{c.reset}'.format(c=colors), key=None)]
        )

    print()


modifiers = [mod.strip() for mod in env['modifiers'].split(',')]

combos = []
for key in args.keys:
    combos.append(key)

    for mod in modifiers:
        combos.append('{}+{}'.format(mod, key))

responses = {
    'Up': terminalInput.upChar,
    'Down': terminalInput.downChar,
    'Enter': terminalInput.enterChar,
    'Esc': terminalInput.escChar,
}

# Filter out 'Up', 'Down', 'Enter', and 'Esc' without modifiers, since we already asked for them at the beginning.
combosToQuery = [combo for combo in combos if combo not in ('Up', 'Down', 'Enter', 'Esc')]


def _getKeyAtIndex(index):
    keyName = combosToQuery[index]

    responses[keyName] = getKey(
        '{c.dark.gray}[{}/{}]{c.reset} Please press {c.bold}{c.yellow}{}{c.reset}... '
        .format(index + 1, len(combosToQuery), keyName, c=colors), allowSkip=True
    )


print('''

Testing keys: {keys}
Testing modifiers: {modifiers}

Special keys:
  {c.bold}{c.cyan}Ctrl+C{c.reset} - {c.bold}Quit{c.reset}
  {c.bold}{c.cyan}q{c.reset}      - {c.bold}Quit{c.reset}
  {c.bold}{c.cyan}b{c.reset}      - {c.bold}Back{c.reset} (redo previous question; useful if you pressed the wrong keys)
  {c.bold}{c.cyan}Space{c.reset}  - {c.bold}Skip{c.reset} question (useful if your terminal doesn't respond to a given key)
'''.format(  # noqa: E501
    keys=', '.join('{c.bold}{c.yellow}{key}{c.reset}'.format(key=key, c=colors) for key in args.keys),
    modifiers=', '.join('{c.bold}{c.yellow}{mod}{c.reset}'.format(mod=mod, c=colors) for mod in modifiers),
    c=colors
))

with rawStdin():
    i = 0
    while i < len(combosToQuery):
        try:
            _getKeyAtIndex(i)

        except QuitException:
            print('\r\n{c.dark.gray}Exiting.{c.reset}\r'.format(c=colors))
            sys.exit(1)

        except BackException:
            print('\r\n{c.dark.gray}Repeating previous question.{c.reset}\r'.format(c=colors))
            i -= 1
            continue

        i += 1


comboColWidth = max(len(combo) for combo in combos) + max(len(mod) for mod in modifiers) + 3

_printEnvironment()
print()
colors.printHeading('Results:')
print(
    ' {c.bold}{c.green}{combo: <{comboColWidth}} {val}{c.reset}'
    .format(combo='Key Combination', comboColWidth=comboColWidth, val='Byte sequence', c=colors)
)
for combo in combos:
    val = displayableKey(responses[combo])
    print(
        ' {c.yellow}{combo: <{comboColWidth}}{c.reset} {c.userInput}{val}{c.reset}'
        .format(combo=combo, comboColWidth=comboColWidth, val=val, c=colors)
    )

outObject = {
    'environment': env,
    'results': responses,
}
outFilename = 'term-key-survey-{}-{}-{}-{}.json' \
        .format(env['terminal program'], env['terminal version'], env['platform system'], env['platform release'])
print()
print('Writing results to {c.cyan}{}{c.reset} ...'.format(outFilename, c=colors))
with open(outFilename, 'w') as f:
    json.dump(outObject, f, indent=4)  # Pretty-printed
    #json.dump(outObject, f, separators=(',', ':'))  # Compact representation

print()
print('Please press any key to exit...')
with rawStdin():
    readByte()
