#!/usr/bin/env python3
"""Protectli device BIOS flasher.

This tool will flash new BIOS onto the machine that runs this script.
"""

from ast import Constant
from asyncio import constants
import os
import subprocess  # noqa:S404
import sys
import textwrap
import time

from flashli import configurations, hardware

# Set a debug hardware here.
DEBUGMODE = ''

CONFIGURATIONS = configurations.CONFIGURATIONS

displaySize = 89

VERSION = '1.1.19'

if os.geteuid() != 0 and not DEBUGMODE:
    print('Need to be run as root user')
    print('Please run: sudo ./main.py')
    sys.exit()


def get_version() -> str:
    """Functional way to get global VERSION.

    Returns:
        str: Value of global VERSION
    """
    global VERSION
    return VERSION


def get_terminal_width() -> int:
    """Get the width of the current terminal in characters.

    Returns:
        int: Width in characters
    """
    return os.get_terminal_size().columns


def get_image_path(model: str, requested_bios: str) -> str:
    """Get path to BIOS image.

    Args:
        model: Protectli device model name
        requested_bios: The BIOS to be located

    Returns:
        str: Path to the BIOS file requested
    """
    global CONFIGURATIONS
    for bios in CONFIGURATIONS[model]['bios']:
        if bios['vendor'] == requested_bios:
            return 'images/{0}'.format(bios['file'])


def do_flash(model: str, bios: str) -> int:
    """Perform the BIOS flash.

    Args:
        model: Protectli device model name
        bios: The BIOS to be flashed

    Returns:
        Returns exit status from flashrom binary:
            0: Success
            1: General Failure
            2: /dev/mem cannot be opened
            3: mmap() failed
    """
    global DEBUGMODE
    global CONFIGURATIONS
    if DEBUGMODE:
        print('Not actually flashing, script is in debug mode.')
        return 0
    file_path = get_image_path(model, bios)
    completed_process = subprocess.run(CONFIGURATIONS[model]['command'].format(file_path), shell=True)  # noqa:S602,S603

    return completed_process.returncode

def check_mac() -> bool:

    global DEBUGMODE

    str_mac = int("0x646266210000", base=16)
    end_mac = int("0x646266210314", base=16)
    

    device_mac = str(hardware.get_mac(DEBUGMODE))
    device_mac = int("0x" + device_mac.replace(':', ''), base=16)
    mac_match = ''


    if  device_mac >= str_mac and device_mac <= end_mac:
        mac_match = 'vp_vr1'
    
    elif "J4125" in hardware.get_cpu(DEBUGMODE):
        mac_match = 'vp_vr2'
    
    return mac_match

def print_supported_products():
    """Get list of devices from Configurations and prints to STDOUT."""
    global CONFIGURATIONS
    devices = list(map(lambda dev: dev.upper(), list(CONFIGURATIONS)))
    devices.sort()
    print(*devices, sep='\n')


def get_user_selection(device: str) -> str:
    """Get BIOS selection from user based on available images for device.

    Args:
        device: Which device to ask the user about

    Returns:
        str: Vendor name
    """
    global CONFIGURATIONS
    available_options = CONFIGURATIONS[device]['bios']
    while True:
        number = 1
        for available_option in available_options:
            vendor = available_option['vendor']
            filename = available_option['file']
            print('[{0}]: {1} ({2})'.format(str(number), vendor, filename))
            number += 1
        print('\nEnter the [#] of an image file, or [0] to quit. Flahing will not begin yet')
        print('> ', end='')
        try:
            user_input = int(input())
        except Exception:
            user_input = -1
        if (user_input == 0):
            sys.exit('Exiting now.')
        elif (0 < user_input <= len(available_options)):
            return available_options[user_input - 1]['vendor']
        else:
            print('Invalid choice.')
            print('Available BIOS:\n')


def show_debug_info():
    """Collect debug information and tell user how to submit an issue."""
    print('TODO: Collect info and display instructions on how to submit a Github issue.')

def display_logo () :

    logo_str =  ' ______   ______     ______     ______   ______     ______     ______   __         __    \n'
    logo_str += '/\  == \ /\  == \   /\  __ \   /\__  _\ /\  ___\   /\  ___\   /\__  _\ /\ \       /\ \   \n'
    logo_str += '\ \  _-/ \ \  __<   \ \ \/\ \  \/_/\ \/ \ \  __\   \ \ \____  \/_/\ \/ \ \ \____  \ \ \  \n'
    logo_str += ' \ \_\    \ \_\ \_\  \ \_____\    \ \_\  \ \_____\  \ \_____\    \ \_\  \ \_____\  \ \_\ \n'
    logo_str += '  \/_/     \/_/ /_/   \/_____/     \/_/   \/_____/   \/_____/     \/_/   \/_____/   \/_/ \n'
    logo_str += '_________________________________________________________________________________________\n\n'

    return logo_str
def display_warning () :

    display_logo()

    general_warning = '\nFlashing new firmware onto any hardware is potentially dangerous\nin that if the procedure is interrupted or otherwise not able to complete, your hardware may be rendered useless.\nPlease proceed with caution.\nIf there are any questions, please contact Protectli support BEFORE proceeding.\n\n'

    ram_warning = '\nUnless there is a compelling reason to update the BIOS, we recommend to stay with your current known working BIOS version'

    print('\n' + '!'.center(displaySize, '*')) 
    print (textwrap.fill('Are you sure you would like to flash this device?', displaySize) + '\n')
    print (textwrap.fill(general_warning, displaySize) + '\n')
    print (textwrap.fill(ram_warning, displaySize) + '\n')
    print('!'.center(displaySize, '*'))

    user_agrement = str(input('\nAcknowledgement Yes [Y]: ')).lower()

    if user_agrement == 'y':
        print ('')
    
    else :
        sys.exit('**Acknowledgement rejected**\n')

def main():  # noqa:WPS213
    """Main program."""

    os.system('/bin/clear')  # noqa:S607,S605

    print (display_logo())
    #display_warning()

    global DEBUGMODE
    device = hardware.get_protectli_device(DEBUGMODE, check_mac())

    if device == 'Unknown' :
        print ('device is returning unknow')
        return -1
    cpu = hardware.get_cpu(DEBUGMODE)
    print('FlashLi'.center(displaySize, '='))
    print('--Version {0}--\n'.format(get_version()).center(displaySize, ' '))

    print('Device:    Protectli {0}'.format(device))
    print('CPU:       {0}'.format(cpu))
    if not hardware.is_protectli_device(DEBUGMODE) or 'Unknown' == hardware.is_protectli_device(DEBUGMODE):
        print('Sorry, this is an unsupported device.')
        print('This tool is used to flash BIOS onto the following Protectli products:')
        print_supported_products()
        sys.exit()

    bios_mode = hardware.get_bios_mode(DEBUGMODE)
    print('BIOS Mode: {0}'.format(bios_mode))

    if bios_mode == 'EFI' and not str(device) == 'vp2410' and not str(device) == 'vp2410r':
        print(textwrap.fill(
            'This tool must be run in Legacy BIOS mode, not EFI. If you are using this tool to update an existing EFI system, you may experience issues booting into your operating system after flashing a new BIOS. If you are aware of the risks and wish to proceed with flashing a new BIOS image, please reboot your device and configure your current BIOS to boot into Legacy Mode.',
            displaySize,
        ))
        sys.exit()

    print()
    print('Available BIOS:')
    print()

    selection = get_user_selection(device)
    
    display_warning()

    returncode = do_flash(device, selection)
    if returncode == 0:
        print('Flash completed and successful.')
        print('Please restart your device.')
    else:
        print('BIOS Flash failed, is this script running with root permissions?')
        print('Please try again, but if problems persist, please let us know.')
        show_debug_info()


try:
    main()
except KeyboardInterrupt:
    print('\n')
    sys.exit()
