#!/usr/bin/env python3
#
# Copyright (c) 2020 Nuvoton Technology Corporation
#
# SPDX-License-Identifier: Apache-2.0

# This file contains general functions for ECST application

import sys
import argparse
import colorama
from colorama import Fore

INVALID_INPUT = -1
EXIT_FAILURE_STATUS = 1

# Header common fields
FW_HDR_CRC_DISABLE = 0x00
FW_HDR_CRC_ENABLE = 0x02
FW_CRC_DISABLE = 0x00
FW_CRC_ENABLE = 0x02

SPI_CLOCK_RATIO_1 = 0x00
SPI_CLOCK_RATIO_2 = 0x08

SPI_UNLIMITED_BURST_DISABLE = 0x00
SPI_UNLIMITED_BURST_ENABLE = 0x08

# Verbose related values
NO_VERBOSE = 0
REG_VERBOSE = 1
SUPER_VERBOSE = 1

# argument default values.
DEFAULT_MODE = "bt"
SPI_MAX_CLOCK_DEFAULT = "20"
FLASH_SIZE_DEFAULT = "16"
SPI_CLOCK_RATIO_DEFAULT = 1
PASTE_FIRMWARE_HEADER_DEFAULT = 0x00000000
DEFAULT_VERBOSE = NO_VERBOSE
SPI_MODE_VAL_DEFAULT = 'normal'
SPI_UNLIMITED_BURST_DEFAULT = SPI_UNLIMITED_BURST_DISABLE
FW_HDR_CRC_DEFAULT = FW_HDR_CRC_ENABLE
FW_CRC_DEFAULT = FW_CRC_ENABLE
FW_CRC_START_OFFSET_DEFAULT = 0x0
POINTER_OFFSET_DEFAULT = 0x0

# Chips: convert from name to index.
CHIPS_INFO = {
    'npcx7m5': {'ram_address': 0x100a8000, 'ram_size': 0x20000},
    'npcx7m6': {'ram_address': 0x10090000, 'ram_size': 0x40000},
    'npcx7m7': {'ram_address': 0x10070000, 'ram_size': 0x60000},
    'npcx9m3': {'ram_address': 0x10080000, 'ram_size': 0x50000},
    'npcx9m6': {'ram_address': 0x10090000, 'ram_size': 0x40000},
    'npcx9m7': {'ram_address': 0x10070000, 'ram_size': 0x60000},
    'npcx9mfp': {'ram_address': 0x10058000, 'ram_size': 0x80000},
    'npcx4m3': {'ram_address': 0x10088000, 'ram_size': 0x50000},
    'npcx4m8': {'ram_address': 0x10060000, 'ram_size': 0x7c800},
}
DEFAULT_CHIP = 'npcx7m6'

# RAM related values
RAM_ADDR = 0x00
RAM_SIZE = 0x01

class EcstArgs:
    """creates an arguments object for the ECST,
    the arguments are taken from the command line and/or
    argument file
    """
    error_args = None

    mode = DEFAULT_MODE
    help = False
    verbose = DEFAULT_VERBOSE
    super_verbose = False
    input = None
    output = None
    args_file = None
    chip_name = DEFAULT_CHIP
    chip_ram_address = CHIPS_INFO[DEFAULT_CHIP]['ram_address']
    chip_ram_size = CHIPS_INFO[DEFAULT_CHIP]['ram_address']
    firmware_header_crc = FW_HDR_CRC_DEFAULT
    firmware_crc = FW_CRC_DEFAULT
    spi_flash_maximum_clock = SPI_MAX_CLOCK_DEFAULT
    spi_flash_clock_ratio = SPI_CLOCK_RATIO_DEFAULT
    unlimited_burst_mode = SPI_UNLIMITED_BURST_DEFAULT
    spi_flash_read_mode = SPI_MODE_VAL_DEFAULT
    firmware_load_address = None
    firmware_entry_point = None
    use_arm_reset = True
    firmware_crc_start = FW_CRC_START_OFFSET_DEFAULT
    firmware_crc_size = None
    firmware_length = None
    flash_size = FLASH_SIZE_DEFAULT
    paste_firmware_header = PASTE_FIRMWARE_HEADER_DEFAULT
    pointer = POINTER_OFFSET_DEFAULT
    bh_offset = None

    def __init__(self):

        arguments = _create_parser("")
        valid_arguments = arguments[0]
        invalid_arguments = arguments[1]
        self.error_args = invalid_arguments

        _populate_args(self, valid_arguments)
        _populate_chip_fields(self)

def _populate_chip_fields(self):
    """populate the chip related fields for the ecst"""
    self.chip_name = self.chip_name
    chip = str(self.chip_name).lower()

    if chip not in CHIPS_INFO:
        self.chip_name = INVALID_INPUT
        return

    self.chip_ram_address = CHIPS_INFO[chip]['ram_address']
    self.chip_ram_size = CHIPS_INFO[chip]['ram_size']
    if self.firmware_load_address is None:
        self.firmware_load_address = self.chip_ram_address

def _populate_args(self, argument_list):
    """populate the ecst arguments according to the command line/ args file"""
    for arg in vars(argument_list):
        if (arg == "input") & (argument_list.input is not None):
            self.input = argument_list.input

        elif (arg == "output") & (argument_list.output is not None):
            self.output = argument_list.output

        elif (arg == "chip") & (argument_list.chip is not None):
            self.chip_name = argument_list.chip
            _populate_chip_fields(self)

        elif (arg == "verbose") & argument_list.verbose:
            self.verbose = REG_VERBOSE

        elif (arg == "super_verbose") & argument_list.super_verbose:
            self.verbose = SUPER_VERBOSE

        elif (arg == "spi_flash_maximum_clock") & \
                (argument_list.spi_flash_maximum_clock is not None):
            self.spi_flash_maximum_clock =\
                argument_list.spi_flash_maximum_clock

        elif (arg == "spi_flash_clock_ratio") & \
                (argument_list.spi_flash_clock_ratio is not None):
            if argument_list.spi_flash_clock_ratio.isdigit():
                self.spi_flash_clock_ratio =\
                    int(argument_list.spi_flash_clock_ratio)
            else:
                self.spi_flash_clock_ratio = INVALID_INPUT

        elif (arg == "firmware_header_crc") &\
                argument_list.firmware_header_crc:
            self.firmware_header_crc = FW_HDR_CRC_DISABLE

        elif (arg == "firmware_crc") & argument_list.firmware_crc:
            self.firmware_crc = FW_CRC_DISABLE

        elif (arg == "spi_read_mode") &\
                (argument_list.spi_read_mode is not None):

            self.spi_flash_read_mode = argument_list.spi_read_mode

        elif (arg == "flash_size") & (argument_list.flash_size is not None):
            self.flash_size = argument_list.flash_size

        elif (arg == "paste_firmware_header") & \
                (argument_list.paste_firmware_header is not None):
            if _is_hex(argument_list.paste_firmware_header):
                self.paste_firmware_header =\
                    int(argument_list.paste_firmware_header, 16)
            else:
                self.paste_firmware_header = INVALID_INPUT

def _create_parser(arg_list):
    """create argument parser according to pre-defined arguments

    :param arg_list: when empty, parses command line arguments,
    else parses the given string
    """

    parser = argparse.ArgumentParser(conflict_handler='resolve', allow_abbrev=False)
    parser.add_argument("-i", nargs='?', dest="input")
    parser.add_argument("-o", nargs='?', dest="output")
    parser.add_argument("-chip", dest="chip")
    parser.add_argument("-v", action="store_true", dest="verbose")
    parser.add_argument("-vv", action="store_true", dest="super_verbose")
    parser.add_argument("-nohcrc", action="store_true",
                        dest="firmware_header_crc")
    parser.add_argument("-nofcrc", action="store_true", dest="firmware_crc")
    parser.add_argument("-spimaxclk", nargs='?',
                        dest="spi_flash_maximum_clock")
    parser.add_argument("-spiclkratio", nargs='?',
                        dest="spi_flash_clock_ratio")
    parser.add_argument("-spireadmode", nargs='?', dest="spi_read_mode")
    parser.add_argument("-flashsize", nargs='?', dest="flash_size")
    parser.add_argument("-ph", nargs='?', dest="paste_firmware_header")

    args = parser.parse_known_args(arg_list.split())

    if arg_list == "":
        args = parser.parse_known_args()

    return args

def _file_to_line(arg_file):
    """helper to convert a text file to one line string
    used to parse the arguments in a given argfile

    :param arg_file: the file to manipulate
    """
    with open(arg_file, "r") as arg_file_to_read:
        data = arg_file_to_read.read().strip()
    arg_file_to_read.close()

    return data

def _is_hex(val):
    """helper to determine whether an input is a hex
    formatted number

    :param val: input to be checked
    """
    if val.startswith("0x") or val.startswith("0X"):
        val = val[2:]
    hex_digits = set("0123456789abcdefABCDEF")
    for char in val:
        if char not in hex_digits:
            return False
    return True

def exit_with_failure(message):
    """formatted failure message printer, prints the
    relevant error message and exits the application.

    :param message: the error message to be printed
    """

    message = '\n' + message
    message += '\n'
    message += '******************************\n'
    message += '***        FAILED          ***\n'
    message += '******************************\n'
    print(Fore.RED + message)

    sys.exit(EXIT_FAILURE_STATUS)

colorama.init()
