## @file
# This file contained the parser for sections in INF file
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#

'''
InfSectionParser
'''
##
# Import Modules
#
from copy import deepcopy
import re

from edk2basetools.UPT.Library.StringUtils import GetSplitValueList
from edk2basetools.UPT.Library.CommentParsing import ParseHeaderCommentSection
from edk2basetools.UPT.Library.CommentParsing import ParseComment

from edk2basetools.UPT.Library import DataType as DT

import edk2basetools.UPT.Logger.Log as Logger
from edk2basetools.UPT.Logger import StringTable as ST
from edk2basetools.UPT.Logger.ToolError import FORMAT_INVALID

from edk2basetools.UPT.Object.Parser.InfDefineObject import InfDefObject
from edk2basetools.UPT.Object.Parser.InfBuildOptionObject import InfBuildOptionsObject
from edk2basetools.UPT.Object.Parser.InfLibraryClassesObject import InfLibraryClassObject
from edk2basetools.UPT.Object.Parser.InfPackagesObject import InfPackageObject
from edk2basetools.UPT.Object.Parser.InfPcdObject import InfPcdObject
from edk2basetools.UPT.Object.Parser.InfSoucesObject import InfSourcesObject
from edk2basetools.UPT.Object.Parser.InfUserExtensionObject import InfUserExtensionObject
from edk2basetools.UPT.Object.Parser.InfProtocolObject import InfProtocolObject
from edk2basetools.UPT.Object.Parser.InfPpiObject import InfPpiObject
from edk2basetools.UPT.Object.Parser.InfGuidObject import InfGuidObject
from edk2basetools.UPT.Object.Parser.InfDepexObject import InfDepexObject
from edk2basetools.UPT.Object.Parser.InfBinaryObject import InfBinariesObject
from edk2basetools.UPT.Object.Parser.InfHeaderObject import InfHeaderObject
from edk2basetools.UPT.Object.Parser.InfMisc import InfSpecialCommentObject
from edk2basetools.UPT.Object.Parser.InfMisc import InfHobObject
from edk2basetools.UPT.Object.Parser.InfMisc import InfBootModeObject
from edk2basetools.UPT.Object.Parser.InfMisc import InfEventObject
from edk2basetools.UPT.Parser.InfParserMisc import gINF_SECTION_DEF
from edk2basetools.UPT.Parser.InfDefineSectionParser import InfDefinSectionParser
from edk2basetools.UPT.Parser.InfBuildOptionSectionParser import InfBuildOptionSectionParser
from edk2basetools.UPT.Parser.InfSourceSectionParser import InfSourceSectionParser
from edk2basetools.UPT.Parser.InfLibrarySectionParser import InfLibrarySectionParser
from edk2basetools.UPT.Parser.InfPackageSectionParser import InfPackageSectionParser
from edk2basetools.UPT.Parser.InfGuidPpiProtocolSectionParser import InfGuidPpiProtocolSectionParser
from edk2basetools.UPT.Parser.InfBinarySectionParser import InfBinarySectionParser
from edk2basetools.UPT.Parser.InfPcdSectionParser import InfPcdSectionParser
from edk2basetools.UPT.Parser.InfDepexSectionParser import InfDepexSectionParser

## GetSpecialStr2
#
# GetSpecialStr2
#
def GetSpecialStr2(ItemList, FileName, LineNo, SectionString):
    Str2 = ''
    #
    # S2 may be Platform or ModuleType
    #
    if len(ItemList) == 3:
        #
        # Except [LibraryClass], [Depex]
        # section can has more than 2 items in section header string,
        # others should report error.
        #
        if not (ItemList[0].upper() == DT.TAB_LIBRARY_CLASSES.upper() or \
                ItemList[0].upper() == DT.TAB_DEPEX.upper() or \
                ItemList[0].upper() == DT.TAB_USER_EXTENSIONS.upper()):
            if ItemList[2] != '':
                Logger.Error('Parser',
                             FORMAT_INVALID,
                             ST.ERR_INF_PARSER_SOURCE_SECTION_SECTIONNAME_INVALID % (SectionString),
                             File=FileName,
                             Line=LineNo,
                             ExtraData=SectionString)
        Str2 = ItemList[2]
    elif len(ItemList) == 4:
        #
        # Except [UserExtension]
        # section can has 4 items in section header string,
        # others should report error.
        #
        if not ItemList[0].upper() == DT.TAB_USER_EXTENSIONS.upper() or ItemList[0].upper() == DT.TAB_DEPEX.upper():
            if ItemList[3] != '':
                Logger.Error('Parser', FORMAT_INVALID, ST.ERR_INF_PARSER_SOURCE_SECTION_SECTIONNAME_INVALID \
                             % (SectionString), File=FileName, Line=LineNo, ExtraData=SectionString)

        if not ItemList[0].upper() == DT.TAB_USER_EXTENSIONS.upper():
            Str2 = ItemList[2] + ' | ' + ItemList[3]
        else:
            Str2 = ItemList[2]

    elif len(ItemList) > 4:
        Logger.Error('Parser', FORMAT_INVALID, ST.ERR_INF_PARSER_SOURCE_SECTION_SECTIONNAME_INVALID \
                     % (SectionString), File=FileName, Line=LineNo, ExtraData=SectionString)

    return Str2

## ProcessUseExtHeader
#
#
def ProcessUseExtHeader(ItemList):
    NewItemList = []
    AppendContent = ''
    CompleteFlag = False
    for Item in ItemList:
        if Item.startswith('\"') and not Item.endswith('\"'):
            AppendContent = Item
            CompleteFlag = True
        elif Item.endswith('\"') and not Item.startswith('\"'):
            #
            # Should not have an userId or IdString not starts with " before but ends with ".
            #
            if not CompleteFlag:
                return False, []
            AppendContent = AppendContent + "." + Item
            NewItemList.append(AppendContent)
            CompleteFlag = False
            AppendContent = ''
        elif Item.endswith('\"') and Item.startswith('\"'):
            #
            # Common item, not need to combine the information
            #
            NewItemList.append(Item)
        else:
            if not CompleteFlag:
                NewItemList.append(Item)
            else:
                AppendContent = AppendContent + "." + Item

    if len(NewItemList) > 4:
        return False, []

    return True, NewItemList

## GetArch
#
# GetArch
#
def GetArch(ItemList, ArchList, FileName, LineNo, SectionString):
    #
    # S1 is always Arch
    #
    if len(ItemList) > 1:
        Arch = ItemList[1]
    else:
        Arch = 'COMMON'
    ArchList.add(Arch)

    #
    # 'COMMON' must not be used with specific ARCHs at the same section
    #
    if 'COMMON' in ArchList and len(ArchList) > 1:
        Logger.Error('Parser',
                     FORMAT_INVALID,
                     ST.ERR_INF_PARSER_SECTION_ARCH_CONFLICT,
                     File=FileName,
                     Line=LineNo,
                     ExtraData=SectionString)

    return Arch, ArchList

## InfSectionParser
#
# Inherit from object
#
class InfSectionParser(InfDefinSectionParser,
                       InfBuildOptionSectionParser,
                       InfSourceSectionParser,
                       InfLibrarySectionParser,
                       InfPackageSectionParser,
                       InfGuidPpiProtocolSectionParser,
                       InfBinarySectionParser,
                       InfPcdSectionParser,
                       InfDepexSectionParser):
    #
    # Parser objects used to implement singleton
    #
    MetaFiles = {}

    ## Factory method
    #
    # One file, one parser object. This factory method makes sure that there's
    # only one object constructed for one meta file.
    #
    #   @param  Class           class object of real AutoGen class
    #                           (InfParser, DecParser or DscParser)
    #   @param  FilePath        The path of meta file
    #
    def __new__(cls, FilePath, *args, **kwargs):
        if args:
            pass
        if kwargs:
            pass
        if FilePath in cls.MetaFiles:
            return cls.MetaFiles[FilePath]
        else:
            ParserObject = super(InfSectionParser, cls).__new__(cls)
            cls.MetaFiles[FilePath] = ParserObject
            return ParserObject

    def __init__(self):
        InfDefinSectionParser.__init__(self)
        InfBuildOptionSectionParser.__init__(self)
        InfSourceSectionParser.__init__(self)
        InfLibrarySectionParser.__init__(self)
        InfPackageSectionParser.__init__(self)
        InfGuidPpiProtocolSectionParser.__init__(self)
        InfBinarySectionParser.__init__(self)
        InfPcdSectionParser.__init__(self)
        InfDepexSectionParser.__init__(self)
        #
        # Initialize all objects that an INF file will generated.
        #
        self.InfDefSection = InfDefObject()
        self.InfBuildOptionSection = InfBuildOptionsObject()
        self.InfLibraryClassSection = InfLibraryClassObject()
        self.InfPackageSection = InfPackageObject()
        self.InfPcdSection = InfPcdObject(list(self.MetaFiles.keys())[0])
        self.InfSourcesSection = InfSourcesObject()
        self.InfUserExtensionSection = InfUserExtensionObject()
        self.InfProtocolSection = InfProtocolObject()
        self.InfPpiSection = InfPpiObject()
        self.InfGuidSection = InfGuidObject()
        self.InfDepexSection = InfDepexObject()
        self.InfPeiDepexSection = InfDepexObject()
        self.InfDxeDepexSection = InfDepexObject()
        self.InfSmmDepexSection = InfDepexObject()
        self.InfBinariesSection = InfBinariesObject()
        self.InfHeader = InfHeaderObject()
        self.InfBinaryHeader = InfHeaderObject()
        self.InfSpecialCommentSection = InfSpecialCommentObject()

        #
        # A List for store define section content.
        #
        self._PcdNameList = []
        self._SectionName = ''
        self._SectionType = 0
        self.RelaPath = ''
        self.FileName = ''

    #
    # File Header content parser
    #
    def InfHeaderParser(self, Content, InfHeaderObject2, FileName, IsBinaryHeader = False):
        if IsBinaryHeader:
            (Abstract, Description, Copyright, License) = ParseHeaderCommentSection(Content, FileName, True)
            if not Abstract or not Description or not Copyright or not License:
                Logger.Error('Parser',
                             FORMAT_INVALID,
                             ST.ERR_INVALID_BINARYHEADER_FORMAT,
                             File=FileName)
        else:
            (Abstract, Description, Copyright, License) = ParseHeaderCommentSection(Content, FileName)
        #
        # Not process file name now, for later usage.
        #
        if self.FileName:
            pass

        #
        # Insert Abstract, Description, CopyRight, License into header object
        #
        InfHeaderObject2.SetAbstract(Abstract)
        InfHeaderObject2.SetDescription(Description)
        InfHeaderObject2.SetCopyright(Copyright)
        InfHeaderObject2.SetLicense(License)




    ## Section header parser
    #
    #   The section header is always in following format:
    #
    #       [section_name.arch<.platform|module_type>]
    #
    # @param String    A string contained the content need to be parsed.
    #
    def SectionHeaderParser(self, SectionString, FileName, LineNo):
        _Scope = []
        _SectionName = ''
        ArchList = set()
        _ValueList = []
        _PcdNameList = [DT.TAB_INF_FIXED_PCD.upper(),
                             DT.TAB_INF_FEATURE_PCD.upper(),
                             DT.TAB_INF_PATCH_PCD.upper(),
                             DT.TAB_INF_PCD.upper(),
                             DT.TAB_INF_PCD_EX.upper()
                             ]
        SectionString = SectionString.strip()
        for Item in GetSplitValueList(SectionString[1:-1], DT.TAB_COMMA_SPLIT):
            if Item == '':
                Logger.Error('Parser',
                             FORMAT_INVALID,
                             ST.ERR_INF_PARSER_MODULE_SECTION_TYPE_ERROR % (""),
                             File=FileName,
                             Line=LineNo,
                             ExtraData=SectionString)
            ItemList = GetSplitValueList(Item, DT.TAB_SPLIT)
            #
            # different section should not mix in one section
            # Allow different PCD type sections mixed together
            #
            if _SectionName.upper() not in _PcdNameList:
                if _SectionName != '' and _SectionName.upper() != ItemList[0].upper():
                    Logger.Error('Parser',
                                 FORMAT_INVALID,
                                 ST.ERR_INF_PARSER_SECTION_NAME_DUPLICATE,
                                 File=FileName,
                                 Line=LineNo,
                                 ExtraData=SectionString)
            elif _PcdNameList[1] in [_SectionName.upper(), ItemList[0].upper()] and \
                (_SectionName.upper()!= ItemList[0].upper()):
                Logger.Error('Parser',
                             FORMAT_INVALID,
                             ST.ERR_INF_PARSER_MODULE_SECTION_TYPE_ERROR % (""),
                             File=FileName,
                             Line=LineNo,
                             ExtraData=SectionString)

            _SectionName = ItemList[0]
            if _SectionName.upper() in gINF_SECTION_DEF:
                self._SectionType = gINF_SECTION_DEF[_SectionName.upper()]
            else:
                self._SectionType = DT.MODEL_UNKNOWN
                Logger.Error("Parser",
                             FORMAT_INVALID,
                             ST.ERR_INF_PARSER_UNKNOWN_SECTION,
                             File=FileName,
                             Line=LineNo,
                             ExtraData=SectionString)

            #
            # Get Arch
            #
            Str1, ArchList = GetArch(ItemList, ArchList, FileName, LineNo, SectionString)

            #
            # For [Defines] section, do special check.
            #
            if ItemList[0].upper() == DT.TAB_COMMON_DEFINES.upper():
                if len(ItemList) != 1:
                    Logger.Error('Parser',
                                 FORMAT_INVALID,
                                 ST.ERR_INF_PARSER_DEFINE_FROMAT_INVALID % (SectionString),
                                 File=FileName, Line=LineNo, ExtraData=SectionString)

            #
            # For [UserExtension] section, do special check.
            #
            if ItemList[0].upper() == DT.TAB_USER_EXTENSIONS.upper():

                RetValue = ProcessUseExtHeader(ItemList)

                if not RetValue[0]:
                    Logger.Error('Parser',
                                 FORMAT_INVALID,
                                 ST.ERR_INF_PARSER_DEFINE_FROMAT_INVALID % (SectionString),
                                 File=FileName, Line=LineNo, ExtraData=SectionString)
                else:
                    ItemList = RetValue[1]

                if len(ItemList) == 3:
                    ItemList.append('COMMON')

                Str1 = ItemList[1]

            #
            # For Library classes, need to check module type.
            #
            if ItemList[0].upper() == DT.TAB_LIBRARY_CLASSES.upper() and len(ItemList) == 3:
                if ItemList[2] != '':
                    ModuleTypeList = GetSplitValueList(ItemList[2], DT.TAB_VALUE_SPLIT)
                    for Item in ModuleTypeList:
                        if Item.strip() not in DT.MODULE_LIST:
                            Logger.Error('Parser',
                                         FORMAT_INVALID,
                                         ST.ERR_INF_PARSER_DEFINE_MODULETYPE_INVALID % (Item),
                                         File=FileName,
                                         Line=LineNo,
                                         ExtraData=SectionString)
            #
            # GetSpecialStr2
            #
            Str2 = GetSpecialStr2(ItemList, FileName, LineNo, SectionString)

            _Scope.append([Str1, Str2])

            _NewValueList = []
            _AppendFlag = True
            if _SectionName.upper() in _PcdNameList:
                for ValueItem in _ValueList:
                    if _SectionName.upper() == ValueItem[0].upper() and Str1.upper() not in ValueItem[1].split():
                        ValueItem[1] = ValueItem[1] + " " + Str1
                        _AppendFlag = False
                    elif _SectionName.upper() == ValueItem[0].upper() and Str1.upper() in ValueItem[1].split():
                        _AppendFlag = False

                    _NewValueList.append(ValueItem)

                _ValueList = _NewValueList

            if _AppendFlag:
                if not ItemList[0].upper() == DT.TAB_USER_EXTENSIONS.upper():
                    _ValueList.append([_SectionName, Str1, Str2, LineNo])
                else:
                    if len(ItemList) == 4:
                        _ValueList.append([_SectionName, Str1, Str2, ItemList[3], LineNo])

        self.SectionHeaderContent = deepcopy(_ValueList)

    ## GenSpecialSectionList
    #
    #  @param SpecialSectionList: a list of list, of which item's format
    #                             (Comment, LineNum)
    #  @param ContainerFile:      Input value for filename of Inf file
    #
    def InfSpecialCommentParser (self, SpecialSectionList, InfSectionObject, ContainerFile, SectionType):
        ReFindSpecialCommentRe = re.compile(r"""#(?:\s*)\[(.*?)\](?:.*)""", re.DOTALL)
        ReFindHobArchRe = re.compile(r"""[Hh][Oo][Bb]\.([^,]*)""", re.DOTALL)
        if self.FileName:
            pass
        SpecialObjectList = []
        ArchList = []
        if SectionType == DT.TYPE_EVENT_SECTION:
            TokenDict = DT.EVENT_TOKENS
        elif SectionType == DT.TYPE_HOB_SECTION:
            TokenDict = DT.HOB_TOKENS
        else:
            TokenDict = DT.BOOTMODE_TOKENS

        for List in SpecialSectionList:
            #
            # Hob has Arch attribute, need to be handled specially here
            #
            if SectionType == DT.TYPE_HOB_SECTION:

                MatchObject = ReFindSpecialCommentRe.search(List[0][0])
                HobSectionStr = MatchObject.group(1)
                ArchList = []
                for Match in ReFindHobArchRe.finditer(HobSectionStr):
                    Arch = Match.groups(1)[0].upper()
                    ArchList.append(Arch)
            CommentSoFar = ''
            for Index in range(1, len(List)):
                Result = ParseComment(List[Index], DT.ALL_USAGE_TOKENS, TokenDict, [], False)
                Usage = Result[0]
                Type = Result[1]
                HelpText = Result[3]

                if Usage == DT.ITEM_UNDEFINED and Type == DT.ITEM_UNDEFINED:
                    if HelpText is None:
                        HelpText = ''
                    if not HelpText.endswith('\n'):
                        HelpText += '\n'
                    CommentSoFar += HelpText
                else:
                    if HelpText:
                        CommentSoFar += HelpText
                    if SectionType == DT.TYPE_EVENT_SECTION:
                        SpecialObject = InfEventObject()
                        SpecialObject.SetEventType(Type)
                        SpecialObject.SetUsage(Usage)
                        SpecialObject.SetHelpString(CommentSoFar)
                    elif SectionType == DT.TYPE_HOB_SECTION:
                        SpecialObject = InfHobObject()
                        SpecialObject.SetHobType(Type)
                        SpecialObject.SetUsage(Usage)
                        SpecialObject.SetHelpString(CommentSoFar)
                        if len(ArchList) >= 1:
                            SpecialObject.SetSupArchList(ArchList)
                    else:
                        SpecialObject = InfBootModeObject()
                        SpecialObject.SetSupportedBootModes(Type)
                        SpecialObject.SetUsage(Usage)
                        SpecialObject.SetHelpString(CommentSoFar)

                    SpecialObjectList.append(SpecialObject)
                    CommentSoFar = ''
        if not InfSectionObject.SetSpecialComments(SpecialObjectList,
                                                   SectionType):
            Logger.Error('InfParser',
                         FORMAT_INVALID,
                         ST.ERR_INF_PARSER_MODULE_SECTION_TYPE_ERROR % (SectionType),
                         ContainerFile
                         )
