## @file
# Trim files preprocessed by compiler
#
# Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

##
# Import Modules
#
import os
import sys
import time
import logging
import struct
import datetime
import argparse
import platform
from collections import OrderedDict
try:
    from configparser import ConfigParser
except:
    from ConfigParser import ConfigParser

# Config message
_BIOS_Signature = "$IBIOSI$"

_ConfigItem = {
    "BOARD_ID": {'Value': '', 'Length': 7},
    "BOARD_REV": {'Value': '', 'Length': 1},
    "BOARD_EXT": {'Value': '', 'Length': 3},
    "BUILD_TYPE": {'Value': '', 'Length': 1},
    "VERSION_MAJOR": {'Value': '0000', 'Length': 4},
    "VERSION_MINOR": {'Value': '00', 'Length': 2},
}

# Version message
__prog__ = 'GenBiosld'
__description__ = 'Trim files preprocessed by compiler'
__copyright__ = 'Copyright (c) 2019, Intel Corporation. All rights reserved.<BR> '
__version__ = '%s Version %s' % (__prog__, '0.1 ')

# ExtraData message
_Usage = "Usage: GenBiosId -i Configfile -o OutputFile [-ot OutputTextFile]"
_ConfigSectionNotDefine = "Not support the config file format, need config section"
_ErrorMessageTemplate = '\n\n%(tool)s...\n : error: %(msg)s\n\t%(extra)s'
_ErrorLogger = logging.getLogger("tool_error")
_ErrorFormatter = logging.Formatter("%(message)s")
_ConfigLenInvalid = "Config item %s length is invalid"
_ConfigItemInvalid = "Item %s is invalid"

# Error message
INFO = 20
ERRORCODE = 50
OPTION_MISSING = 'Missing option'
FORMAT_INVALID = 'Invalid syntax/format'
FILE_NOT_FOUND = 'File/directory not found in workspace'
FORMAT_UNKNOWN_ERROR = 'Unknown error in syntax/format'
FORMAT_NOT_SUPPORTED = 'Not supported syntax/format'


def SetEdkLogger():
    _ErrorLogger.setLevel(INFO)
    _ErrorCh = logging.StreamHandler(sys.stderr)
    _ErrorCh.setFormatter(_ErrorFormatter)
    _ErrorLogger.addHandler(_ErrorCh)
    return _ErrorLogger


# Output the error message and exit the tool
def EdkLogger(ToolName, Message, ExtraData):
    _ErrorLogger = SetEdkLogger()
    TemplateDict = {"tool": ToolName, "msg": Message, "extra": ExtraData}
    LogText = _ErrorMessageTemplate % TemplateDict
    _ErrorLogger.log(ERRORCODE, LogText)
    sys.exit(1)


# Open the file in the correct way
def FileOpen(FileName, Mode, Buffer=-1):
    def LongFilePath(FileName):
        FileName = os.path.normpath(FileName)
        if platform.system() == 'Windows':
            if FileName.startswith('\\\\?\\'):
                return FileName
            if FileName.startswith('\\\\'):
                return '\\\\?\\UNC\\' + FileName[2:]
            if os.path.isabs(FileName):
                return '\\\\?\\' + FileName
        return FileName

    return open(LongFilePath(FileName), Mode, Buffer)


# Parse command line options
def MyOptionParser():
    parser = argparse.ArgumentParser(prog=__prog__,
                                     description=__description__ + __copyright__ + _Usage,
                                     conflict_handler='resolve')
    parser.add_argument('-v', '--version', action='version', version=__version__,
                        help="show program's version number and exit")
    parser.add_argument('-i', '--int', metavar='FILENAME', dest='InputFile', help="Input Config file")
    parser.add_argument('-o', '--out', metavar='FILENAME', dest='OutputFile', help="Output file")
    parser.add_argument('-ot', '--text', metavar='FILENAME', dest='OutputTextFile', help="Output Text file")
    parser.add_argument('-nt', '--notimestamp', dest='NoTimestamp', action='store_true', default=False, help="Set timestamp to zero")
    Options = parser.parse_args()
    return Options


# Check the Tool for missing variables
def CheckOptions(Options):
    if len(sys.argv) not in [5,6] and not (len(sys.argv) in [7,8] and Options.OutputTextFile):
        EdkLogger("GenBiosId", OPTION_MISSING, ExtraData=_Usage)
    elif not Options.InputFile or not Options.OutputFile:
        EdkLogger("GenBiosId", OPTION_MISSING, ExtraData=_Usage)
    InputFile = Options.InputFile
    OutputFile = Options.OutputFile
    OutputTextFile = Options.OutputTextFile
    NoTimestamp = Options.NoTimestamp
    if not os.path.exists(InputFile):
        EdkLogger("GenBiosId", FILE_NOT_FOUND, ExtraData="Input file not found")
    return InputFile, OutputFile, OutputTextFile, NoTimestamp

# Read input file and get config
def ReadInputFile(InputFile):
    InputDict = OrderedDict()
    with open(InputFile) as File:
        FileLines = File.readlines()
    for Line in FileLines:
        if Line.strip().startswith('#'):
            continue
        if '=' in Line:
            Key, Value = Line.split('=')
            InputDict[Key.strip()] = Value.strip()
    return InputDict


# Parse the input file and extract the information
def ParserInputFile(InputDict, NoTimestamp):
    for Item in InputDict:
        if Item not in _ConfigItem:
            EdkLogger("GenBiosId", FORMAT_INVALID, ExtraData=_ConfigItemInvalid % Item)
        _ConfigItem[Item]['Value'] = InputDict[Item]
        if len(_ConfigItem[Item]['Value']) != _ConfigItem[Item]['Length']:
            # The length of the Board ID is being updated based on the BOARD_ID string
            #If the PCH_TYPE is empty space/single quotes(''), removing the empty space/single quotes('') and concatenating the TARGET_PLATFORM_SHORT and BUILD flag strings
            if(_ConfigItem["BOARD_ID"]['Value'][3:5] == "\'\'"):
                _ConfigItem["BOARD_ID"]['Value']=_ConfigItem["BOARD_ID"]['Value'][0:3]+_ConfigItem["BOARD_ID"]['Value'][5:len(_ConfigItem["BOARD_ID"]['Value'])]
                _ConfigItem["BOARD_ID"]['Length']=len(_ConfigItem["BOARD_ID"]['Value'])
            else:
                EdkLogger("GenBiosId", FORMAT_INVALID, ExtraData=_ConfigLenInvalid % Item)
    for Item in _ConfigItem:
        if not _ConfigItem[Item]['Value']:
            EdkLogger("GenBiosId", FORMAT_UNKNOWN_ERROR, ExtraData="Item %s is missing" % Item)
    utcnow = datetime.datetime.utcnow()
    if NoTimestamp:
        TimeStamp = "\0\0\0\0\0\0\0\0\0\0"
    else:
        TimeStamp = time.strftime("%y%m%d%H%M", utcnow.timetuple())
    Id_Str = _ConfigItem['BOARD_ID']['Value'] + _ConfigItem['BOARD_REV']['Value'] + '.' + _ConfigItem['BOARD_EXT'][
        'Value'] + '.' + _ConfigItem['VERSION_MAJOR']['Value'] + \
             '.' + _ConfigItem["BUILD_TYPE"]['Value'] + _ConfigItem['VERSION_MINOR']['Value'] + '.' + TimeStamp
    return Id_Str


# Output information to a file
def PrintOutputFile(OutputFile, OutputTextFile, Id_Str):
    with FileOpen(OutputFile, 'wb') as FdOut:
        for i in _BIOS_Signature:
            FdOut.write(struct.pack('B', ord(i)))

        for i in Id_Str:
            FdOut.write(struct.pack('H', ord(i)))

        FdOut.write(struct.pack('H', 0x00))
    if OutputTextFile:
        with FileOpen(OutputTextFile, 'w') as FdOut:
            FdOut.write(Id_Str)


# Tool entrance method
def Main():
    Options = MyOptionParser()
    InputFile, OutputFile, OutputTextFile, NoTimestamp = CheckOptions(Options)
    InputDict = ReadInputFile(InputFile)
    Id_Str = ParserInputFile(InputDict, NoTimestamp)
    PrintOutputFile(OutputFile, OutputTextFile, Id_Str)
    return 0


if __name__ == '__main__':
    r = Main()
    ## 0-127 is a safe return range, and 1 is a standard default error
    if r < 0 or r > 127: r = 1
    sys.exit(r)
