#!/opt/dosis2/bin/python3-venv

import os
import readline
import string
import subprocess
from collections import OrderedDict

import enum
import psycopg2
import six.moves
from typing import Optional, Callable, Tuple, TypeVar, List, Union, Type

T = TypeVar('T')
readline.parse_and_bind('tab: complete')


class SystemType(enum.Enum):
    L60 = (10, 'l60', 'l60')
    C60 = (11, 'c60', 'c60')
    U60 = (12, 'u60', 'u60')
    SITE_SERVER = (0, 'site-server', 'ss')


def generate_dafe_tag(system_type, serial_number, suffix=''):
    # type: (SystemType, int) -> str
    if system_type == SystemType.SITE_SERVER:
        return '%s%s%s' % (system_type.value[2].upper(), serial_number, suffix)
    else:
        return '%s_D%s%s' % (system_type.value[2].upper(), serial_number, suffix)


def get_database_add_dafe_function(system_type):
    # type: (SystemType) -> str
    if system_type == SystemType.L60:
        return 'select equipment.add_dafe_l60(%(serialnum)s)'
    elif system_type == SystemType.C60:
        return 'select equipment.add_dafe_c60(%(serialnum)s)'
    elif system_type == SystemType.U60:
        return 'select equipment.add_dafe_u60(%(serialnum)s)'
    else:
        raise Exception('no database function programmed for: %s' % (system_type,))


def convert_u_axis_to_427_lead(dafe_tag):
    # type: (str) -> None
    def __get_script():
        # type: () -> str
        return '/opt/dosis2/database/tools/.mfg_clean/uaxis_conversion_to_427.bash'

    if os.path.exists(__get_script()):
        subprocess.check_call([__get_script(), '-d', dafe_tag])
    else:
        raise OSError('%s not found' % (__get_script(),))


def setup_system(system_type, serial_number, setup_427_lead=False, dafe_tag_suffix=''):
    # type: (SystemType, int, bool, str) -> None
    subprocess.check_call([
        '/usr/share/dosis2/bin/setup-dosis-network',
        system_type.value[1].upper().replace('-', '_'),
        generate_dafe_tag(system_type, serial_number, suffix=dafe_tag_suffix)])

    if system_type != SystemType.SITE_SERVER:
        connection = psycopg2.connect(dbname='dosis2', user='dsuper')
        connection.autocommit = True
        try:
            with connection.cursor() as cursor:
                print(('Adding dafe to database %s' % generate_dafe_tag(
                    system_type,
                    serial_number,
                    suffix=dafe_tag_suffix)))

                cursor.execute(
                    get_database_add_dafe_function(system_type),
                    vars={'serialnum': serial_number})
        finally:
            connection.close()

        if setup_427_lead:
            print('Converting U-Axis to 0.427" lead')
            convert_u_axis_to_427_lead(generate_dafe_tag(system_type, serial_number, suffix=dafe_tag_suffix))

    # this is repeated from step1 to help with a bug that sometimes happens
    # in U60 units where the nic ports don't stay consistent. 
    try:
        print('Generating persistant network rules')
        subprocess.check_call(['/usr/share/dosis2/bin/generate-persistent-networks', '--output', '/etc/udev/rules.d/70-persistent-net.rules'])
    except subprocess.CalledProcessError as err:
        print('Something went wrong while trying to generate persistant network rules')

    try:
        subprocess.check_call(['/usr/share/dosis2/bin/setup-thirdparty'])
    except KeyboardInterrupt:
        pass


def enum_members(e):
    # type: (Type[enum.Enum]) -> OrderedDict
    return e.__members__


def request_value(header=None, prompt='> ', after_header_hook=None, validate_and_cast=lambda v: (True, '', '')):
    # type: (Optional[str], str, Optional[Callable[[], None]], Callable[[str], Tuple[bool, T, str]]) -> T
    while True:
        if header is not None:
            print(header)

        if callable(after_header_hook):
            after_header_hook()

        answer = six.moves.input(prompt)
        if callable(validate_and_cast):
            valid, value, reason = validate_and_cast(answer)
        else:
            valid, value, reason = True, answer, ''

        if valid:
            print('')
            return value
        else:
            print(('Invalid value [%s] %s' % (answer, reason)))
            print('')


def request_integer(header=None, prompt='> '):
    # type: (Optional[str], str) -> int
    def __validate_and_cast(value):
        # type: (str) -> (bool, int, str)
        try:
            return True, int(value), ''
        except ValueError:
            return False, value, 'cannot be interpreted as an integer'

    return request_value(
        header=header,
        prompt=prompt,
        validate_and_cast=__validate_and_cast)


def request_yes_no(header=None, prompt='(yes/no)? '):
    # type: (Optional[str], str) -> bool

    def __validate_and_cast(value):
        # type: (str) -> (bool, bool, str)
        return True, value in ('y', 'yes'), ''

    return request_value(
        header=header,
        prompt=prompt,
        validate_and_cast=__validate_and_cast)


def request_option_input(options, header=None, prompt='> '):
    # type: (List[Tuple[str, T]], Optional[str], str) -> T
    optionslookup = dict(options)

    def __validate_and_cast(value):
        # type: (str) -> (bool, Union[T, str], str)
        if value in optionslookup:
            return True, optionslookup[value], ''
        else:
            return False, value, 'must be one of %s' % ', '.join(list(optionslookup.keys()))

    def __print_options():
        for name, value in options:
            print(('  %s' % name))

    prevdelims = readline.get_completer_delims()
    completer = readline.get_completer()
    readline.set_completer_delims('')
    readline.set_completer(lambda text, state: [op for op, _ in options if op.startswith(text)][state])
    answer = request_value(
        header=header,
        prompt=prompt,
        after_header_hook=__print_options,
        validate_and_cast=__validate_and_cast)
    readline.set_completer(completer)
    readline.set_completer_delims(prevdelims)
    return answer


def run():
    dafe_types = [(v.value[1], v) for v in sorted(list(enum_members(SystemType).values()), key=lambda v: v.value[0])]

    dafe_type = request_option_input(dafe_types, header='Choose A Dafe Type:')  # type: SystemType
    dafe_serial_number = request_integer(prompt='%s\'s serial number? ' % dafe_type.value[1])

    if dafe_type == SystemType.SITE_SERVER:
        def __validate_and_cast_letter(text):
            # type: (str) -> (bool, str, str)
            if len(text) == 1 and text in string.ascii_uppercase:
                return True, text, ''
            else:
                return False, text, 'Must be a single uppercase letter'

        is_redundant_site_server = request_yes_no(prompt='Is this a redundant site server (yes/no)? ')

        if is_redundant_site_server:
            redundant_site_server_letter = request_value(
                prompt='Redundant site server letter (A-Z)? ',
                validate_and_cast=__validate_and_cast_letter)
        else:
            redundant_site_server_letter = ''

        print(('%s Configuration:' % generate_dafe_tag(
            dafe_type,
            dafe_serial_number,
            suffix=redundant_site_server_letter)))
        print(('  redundant=%s' % ('yes' if is_redundant_site_server else 'no')))
        print(('  letter=%s' % (redundant_site_server_letter if redundant_site_server_letter else 'N/A')))
        print('')
        if request_yes_no(prompt='Continue with configuration (yes/no)? '):
            setup_system(
                dafe_type,
                dafe_serial_number,
                setup_427_lead=False,
                dafe_tag_suffix=redundant_site_server_letter)
        else:
            print('Not configuring')
    else:
        use_427_lead = request_yes_no(prompt='Does the U-Axis have a 0.427 lead (yes/no)? ')
        print(('%s Configuration:' % generate_dafe_tag(dafe_type, dafe_serial_number)))
        print(('  u-axis 0.427 lead=%s' % ('yes' if use_427_lead else 'no')))
        print('')
        if request_yes_no(prompt='Continue with configuration (yes/no)? '):
            setup_system(dafe_type, dafe_serial_number, setup_427_lead=use_427_lead)
        else:
            print('Not configuring')


if __name__ == '__main__':
    run()
