## @file
# Contains several utilitities shared by migration tools.
#
# Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution.  The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#

##
# Import Modules
#
import Common.LongFilePathOs as os
import re
import EdkLogger
from optparse import OptionParser
from Common.BuildToolError import *
from XmlRoutines import *
from CommonDataClass.CommonClass import *
from Common.LongFilePathSupport import OpenLongFilePath as open

## Set all fields of CommonClass object.
#
# Set all attributes of CommonClass object from XML Dom object of XmlCommon.
#
# @param  Common     The destine CommonClass object.
# @param  XmlCommon  The source XML Dom object.
#
def SetCommon(Common, XmlCommon):
    XmlTag = "Usage"
    Common.Usage = XmlAttribute(XmlCommon, XmlTag).split()

    XmlTag = "FeatureFlag"
    Common.FeatureFlag = XmlAttribute(XmlCommon, XmlTag)
    
    XmlTag = "SupArchList"
    Common.SupArchList = XmlAttribute(XmlCommon, XmlTag).split()
    
    XmlTag = XmlNodeName(XmlCommon) + "/" + "HelpText"
    Common.HelpText = XmlElement(XmlCommon, XmlTag)


## Set some fields of CommonHeaderClass object.
#
# Set Name, Guid, FileName and FullPath fields of CommonHeaderClass object from
# XML Dom object of XmlCommonHeader, NameTag and FileName.
#
# @param  CommonHeader       The destine CommonClass object.
# @param  XmlCommonHeader    The source XML Dom object.
# @param  NameTag            The name tag in XML Dom object.
# @param  FileName           The file name of the XML file.
#
def SetIdentification(CommonHeader, XmlCommonHeader, NameTag, FileName):
    XmlParentTag = XmlNodeName(XmlCommonHeader)
    
    XmlTag = XmlParentTag + "/" + NameTag
    CommonHeader.Name = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParentTag + "/" + "GuidValue"
    CommonHeader.Guid = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParentTag + "/" + "Version"
    CommonHeader.Version = XmlElement(XmlCommonHeader, XmlTag)

    CommonHeader.FileName = os.path.basename(FileName)
    CommonHeader.FullPath = os.path.abspath(FileName)


## Regular expression to match specification and value.
mReSpecification = re.compile(r"(?P<Specification>\w+)\s+(?P<Value>\w*)")

## Add specification to specification dictionary.
#
# Abstract specification name, value pair from Specification String and add them
# to specification dictionary.
#
# @param  SpecificationDict   The destine Specification dictionary.
# @param  SpecificationString The source Specification String from which the
#                             specification name and value pair is abstracted.
#
def AddToSpecificationDict(SpecificationDict, SpecificationString):
    """Abstract specification name, value pair from Specification String"""
    for SpecificationMatch in mReSpecification.finditer(SpecificationString):
        Specification = SpecificationMatch.group("Specification")
        Value = SpecificationMatch.group("Value")
        SpecificationDict[Specification] = Value

## Set all fields of CommonHeaderClass object.
#
# Set all attributes of CommonHeaderClass object from XML Dom object of
# XmlCommonHeader, NameTag and FileName.
#
# @param  CommonHeader       The destine CommonClass object.
# @param  XmlCommonHeader    The source XML Dom object.
# @param  NameTag            The name tag in XML Dom object.
# @param  FileName           The file name of the XML file.
#
def SetCommonHeader(CommonHeader, XmlCommonHeader):
    """Set all attributes of CommonHeaderClass object from XmlCommonHeader"""
    XmlParent = XmlNodeName(XmlCommonHeader)
    
    XmlTag = XmlParent + "/" + "Abstract"
    CommonHeader.Abstract = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParent + "/" + "Description"
    CommonHeader.Description = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParent + "/" + "Copyright"
    CommonHeader.Copyright = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParent + "/" + "License"
    CommonHeader.License = XmlElement(XmlCommonHeader, XmlTag)

    XmlTag = XmlParent + "/" + "Specification"
    Specification = XmlElement(XmlCommonHeader, XmlTag)

    AddToSpecificationDict(CommonHeader.Specification, Specification)

    XmlTag = XmlParent + "/" + "ModuleType"
    CommonHeader.ModuleType = XmlElement(XmlCommonHeader, XmlTag)


## Load a new Cloned Record class object.
#
# Read an input XML ClonedRecord DOM object and return an object of Cloned Record
# contained in the DOM object.
#
# @param  XmlCloned            A child XML DOM object in a Common XML DOM.
#
# @retvel ClonedRecord         A new Cloned Record object created by XmlCloned.
#
def LoadClonedRecord(XmlCloned):
    ClonedRecord = ClonedRecordClass()

    XmlTag = "Id"
    ClonedRecord.Id = int(XmlAttribute(XmlCloned, XmlTag))

    XmlTag = "FarGuid"
    ClonedRecord.FarGuid = XmlAttribute(XmlCloned, XmlTag)

    XmlTag = "Cloned/PackageGuid"
    ClonedRecord.PackageGuid = XmlElement(XmlCloned, XmlTag)
    
    XmlTag = "Cloned/PackageVersion"
    ClonedRecord.PackageVersion = XmlElement(XmlCloned, XmlTag)
    
    XmlTag = "Cloned/ModuleGuid"
    ClonedRecord.ModuleGuid = XmlElement(XmlCloned, XmlTag)
    
    XmlTag = "Cloned/ModuleVersion"
    ClonedRecord.ModuleVersion = XmlElement(XmlCloned, XmlTag)
    
    return ClonedRecord


## Load a new Guid/Protocol/Ppi common class object.
#
# Read an input XML Guid/Protocol/Ppi DOM object and return an object of
# Guid/Protocol/Ppi contained in the DOM object.
#
# @param  XmlGuidProtocolPpiCommon A child XML DOM object in a Common XML DOM.
#
# @retvel GuidProtocolPpiCommon    A new GuidProtocolPpiCommon class object
#                                  created by XmlGuidProtocolPpiCommon.
#
def LoadGuidProtocolPpiCommon(XmlGuidProtocolPpiCommon):
    GuidProtocolPpiCommon = GuidProtocolPpiCommonClass()
    
    XmlTag = "Name"
    GuidProtocolPpiCommon.Name = XmlAttribute(XmlGuidProtocolPpiCommon, XmlTag)

    XmlParent = XmlNodeName(XmlGuidProtocolPpiCommon)
    if XmlParent == "Entry":
        XmlTag = "%s/C_Name" % XmlParent
    elif XmlParent == "GuidCNames":
        XmlTag = "%s/GuidCName" % XmlParent
    else:
        XmlTag = "%s/%sCName" % (XmlParent, XmlParent)
        
    GuidProtocolPpiCommon.CName = XmlElement(XmlGuidProtocolPpiCommon, XmlTag)
    
    XmlTag = XmlParent + "/" + "GuidValue"
    GuidProtocolPpiCommon.Guid = XmlElement(XmlGuidProtocolPpiCommon, XmlTag)
    
    if XmlParent.endswith("Notify"):
        GuidProtocolPpiCommon.Notify = True

    XmlTag = "GuidTypeList"
    GuidTypes = XmlAttribute(XmlGuidProtocolPpiCommon, XmlTag)
    GuidProtocolPpiCommon.GuidTypeList = GuidTypes.split()
    
    XmlTag = "SupModuleList"
    SupModules = XmlAttribute(XmlGuidProtocolPpiCommon, XmlTag)
    GuidProtocolPpiCommon.SupModuleList = SupModules.split()

    SetCommon(GuidProtocolPpiCommon, XmlGuidProtocolPpiCommon)

    return GuidProtocolPpiCommon


## Load a new Pcd class object.
#
# Read an input XML Pcd DOM object and return an object of Pcd
# contained in the DOM object.
#
# @param  XmlPcd               A child XML DOM object in a Common XML DOM.
#
# @retvel Pcd                  A new Pcd object created by XmlPcd.
#
def LoadPcd(XmlPcd):
    """Return a new PcdClass object equivalent to XmlPcd"""
    Pcd = PcdClass()

    XmlTag = "PcdEntry/C_Name"
    Pcd.CName = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/Token"
    Pcd.Token = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/TokenSpaceGuidCName"
    Pcd.TokenSpaceGuidCName = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/DatumType"
    Pcd.DatumType = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/MaxDatumSize"
    Pcd.MaxDatumSize = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/DefaultValue"
    Pcd.DefaultValue = XmlElement(XmlPcd, XmlTag)

    XmlTag = "PcdItemType"
    Pcd.ItemType = XmlAttribute(XmlPcd, XmlTag)

    XmlTag = "PcdEntry/ValidUsage"
    Pcd.ValidUsage = XmlElement(XmlPcd, XmlTag).split()

    XmlTag = "SupModuleList"
    Pcd.SupModuleList = XmlAttribute(XmlPcd, XmlTag).split()

    SetCommon(Pcd, XmlPcd)

    return Pcd


## Load a new LibraryClass class object.
#
# Read an input XML LibraryClass DOM object and return an object of LibraryClass
# contained in the DOM object.
#
# @param  XmlLibraryClass    A child XML DOM object in a Common XML DOM.
#
# @retvel LibraryClass       A new LibraryClass object created by XmlLibraryClass.
#
def LoadLibraryClass(XmlLibraryClass):
    LibraryClass = LibraryClassClass()

    XmlTag = "LibraryClass/Keyword"
    LibraryClass.LibraryClass = XmlElement(XmlLibraryClass, XmlTag)
    if LibraryClass.LibraryClass == "":
        XmlTag = "Name"
        LibraryClass.LibraryClass = XmlAttribute(XmlLibraryClass, XmlTag)
    
    XmlTag = "LibraryClass/IncludeHeader"
    LibraryClass.IncludeHeader = XmlElement(XmlLibraryClass, XmlTag)
    
    XmlTag = "RecommendedInstanceVersion"
    RecommendedInstanceVersion = XmlAttribute(XmlLibraryClass, XmlTag)
    LibraryClass.RecommendedInstanceVersion = RecommendedInstanceVersion
    
    XmlTag = "RecommendedInstanceGuid"
    RecommendedInstanceGuid = XmlAttribute(XmlLibraryClass, XmlTag)
    LibraryClass.RecommendedInstanceGuid = RecommendedInstanceGuid
    
    XmlTag = "SupModuleList"
    SupModules = XmlAttribute(XmlLibraryClass, XmlTag)
    LibraryClass.SupModuleList = SupModules.split()
    
    SetCommon(LibraryClass, XmlLibraryClass)
    
    return LibraryClass


## Load a new Build Option class object.
#
# Read an input XML BuildOption DOM object and return an object of Build Option
# contained in the DOM object.
#
# @param  XmlBuildOption       A child XML DOM object in a Common XML DOM.
#
# @retvel BuildOption          A new Build Option object created by XmlBuildOption.
#
def LoadBuildOption(XmlBuildOption):
    """Return a new BuildOptionClass object equivalent to XmlBuildOption"""
    BuildOption = BuildOptionClass()
    
    BuildOption.Option = XmlElementData(XmlBuildOption)

    XmlTag = "BuildTargets"
    BuildOption.BuildTargetList = XmlAttribute(XmlBuildOption, XmlTag).split()
    
    XmlTag = "ToolChainFamily"
    BuildOption.ToolChainFamily = XmlAttribute(XmlBuildOption, XmlTag)
    
    XmlTag = "TagName"
    BuildOption.TagName = XmlAttribute(XmlBuildOption, XmlTag)
    
    XmlTag = "ToolCode"
    BuildOption.ToolCode = XmlAttribute(XmlBuildOption, XmlTag)
    
    XmlTag = "SupArchList"
    BuildOption.SupArchList = XmlAttribute(XmlBuildOption, XmlTag).split()
    
    return BuildOption


## Load a new User Extensions class object.
#
# Read an input XML UserExtensions DOM object and return an object of User
# Extensions contained in the DOM object.
#
# @param  XmlUserExtensions    A child XML DOM object in a Common XML DOM.
#
# @retvel UserExtensions       A new User Extensions object created by
#                              XmlUserExtensions.
#
def LoadUserExtensions(XmlUserExtensions):
    UserExtensions = UserExtensionsClass()
    
    XmlTag = "UserID"
    UserExtensions.UserID = XmlAttribute(XmlUserExtensions, XmlTag)
    
    XmlTag = "Identifier"
    UserExtensions.Identifier = XmlAttribute(XmlUserExtensions, XmlTag)
    
    UserExtensions.Content = XmlElementData(XmlUserExtensions)
    
    return UserExtensions


## Store content to a text file object.
#
# Write some text file content to a text file object. The contents may echo
# in screen in a verbose way.
#
# @param  TextFile           The text file object.
# @param  Content            The string object to be written to a text file.
#
def StoreTextFile(TextFile, Content):
    EdkLogger.verbose(Content)
    TextFile.write(Content)


## Add item to a section.
#
# Add an Item with specific CPU architecture to section dictionary.
# The possible duplication is ensured to be removed.
#
# @param  Section            Section dictionary indexed by CPU architecture.
# @param  Arch               CPU architecture: Ia32, X64, Ipf, ARM, AARCH64, Ebc or Common.
# @param  Item               The Item to be added to section dictionary.
#
def AddToSection(Section, Arch, Item):
    SectionArch = Section.get(Arch, [])
    if Item not in SectionArch:
        SectionArch.append(Item)
        Section[Arch] = SectionArch


## Get section contents.
#
# Return the content of section named SectionName.
# the contents is based on Methods and ObjectLists.
#
# @param  SectionName        The name of the section.
# @param  Method             A function returning a string item of an object.
# @param  ObjectList         The list of object.
#
# @retval Section            The string content of a section.
#
def GetSection(SectionName, Method, ObjectList):
    SupportedArches = ["common", "Ia32", "X64", "Ipf", "Ebc", "ARM", "AARCH64"]
    SectionDict = {}
    for Object in ObjectList:
        Item = Method(Object)
        if Item == "":
            continue
        Item = "  %s" % Item
        Arches = Object.SupArchList
        if len(Arches) == 0:
            AddToSection(SectionDict, "common", Item)
        else:
            for Arch in SupportedArches:
                if Arch.upper() in Arches:
                    AddToSection(SectionDict, Arch, Item)

    Section = ""
    for Arch in SupportedArches:
        SectionArch = "\n".join(SectionDict.get(Arch, []))
        if SectionArch != "":
            Section += "[%s.%s]\n%s\n" % (SectionName, Arch, SectionArch)
            Section += "\n"
    if Section != "":
        Section += "\n"
    return Section


## Store file header to a text file.
#
# Write standard file header to a text file. The content includes copyright,
# abstract, description and license extracted from CommonHeader class object.
#
# @param  TextFile           The text file object.
# @param  CommonHeader       The source CommonHeader class object.
#
def StoreHeader(TextFile, CommonHeader):
    CopyRight = CommonHeader.Copyright
    Abstract = CommonHeader.Abstract
    Description = CommonHeader.Description
    License = CommonHeader.License

    Header = "#/** @file\n#\n"
    Header += "# " + Abstract + "\n#\n"
    Header += "# " + Description.strip().replace("\n", "\n# ") + "\n"
    Header += "# " + CopyRight + "\n#\n"
    Header += "#  " + License.replace("\n", "\n# ").replace("  ", " ")
    Header += "\n#\n#**/\n\n"

    StoreTextFile(TextFile, Header)

## Store file header to a text file.
#
# Write Defines section to a text file. DefinesTupleList determines the content.
#
# @param  TextFile           The text file object.
# @param  DefinesTupleList   The list of (Tag, Value) to be added as one item.
#
def StoreDefinesSection(TextFile, DefinesTupleList):
    Section = "[Defines]\n"
    for DefineItem in DefinesTupleList:
        Section += "  %-30s = %s\n" % DefineItem

    Section += "\n\n"
    StoreTextFile(TextFile, Section)


## Return one User Extension section.
#
# Read the input UserExtentsions class object and return one section.
#
# @param  UserExtensions       An input UserExtensions class object.
#
# @retval UserExtensionSection A section representing UserExtensions object.
#
def GetUserExtensions(UserExtensions):
    UserId = UserExtensions.UserID
    Identifier = UserExtensions.Identifier
    Content = UserExtensions.Content

    return "[UserExtensions.%s.%s]\n  %s\n\n" % (UserId, Identifier, Content)

## Regular expression to match an equation.
mReEquation = re.compile(r"\s*(\S+)\s*=\s*(\S*)\s*")

## Return a value tuple matching information in a text fle.
#
# Parse the text file and return a value tuple corresponding to an input tag
# tuple. In case of any error, an tuple of empty strings is returned.
#
# @param  FileName           The file name of the text file.
# @param  TagTuple           A tuple of tags as the key to the value.
#
# @param  ValueTupe          The returned tuple corresponding to the tag tuple.
#
def GetTextFileInfo(FileName, TagTuple):
    ValueTuple = [""] * len(TagTuple)
    try:
        for Line in open(FileName):
            Line = Line.split("#", 1)[0]
            MatchEquation = mReEquation.match(Line)
            if MatchEquation:
                Tag = MatchEquation.group(1).upper()
                Value = MatchEquation.group(2)
                for Index in range(len(TagTuple)):
                    if TagTuple[Index] == Tag:
                        ValueTuple[Index] = Value
    except:
        EdkLogger.info("IO Error in reading file %s" % FileName)
        
    return ValueTuple


## Return a value tuple matching information in an XML fle.
#
# Parse the XML file and return a value tuple corresponding to an input tag
# tuple. In case of any error, an tuple of empty strings is returned.
#
# @param  FileName           The file name of the XML file.
# @param  TagTuple           A tuple of tags as the key to the value.
#
# @param  ValueTupe          The returned tuple corresponding to the tag tuple.
#
def GetXmlFileInfo(FileName, TagTuple):
    XmlDom = XmlParseFile(FileName)
    return tuple([XmlElement(XmlDom, XmlTag) for XmlTag in TagTuple])


## Parse migration command line options
#
# Use standard Python module optparse to parse command line option of this tool.
#
# @param  Source             The source file type.
# @param  Destinate          The destinate file type.
#
# @retval Options            A optparse object containing the parsed options.
# @retval InputFile          Path of an source file to be migrated.
#
def MigrationOptionParser(Source, Destinate, ToolName, VersionNumber=1.0):
    # use clearer usage to override default usage message
    UsageString = "%s [-a] [-v|-q] [-o <output_file>] <input_file>" % ToolName
    Version = "%s Version %.2f" % (ToolName, VersionNumber)
    Copyright = "Copyright (c) 2007, Intel Corporation. All rights reserved."
    
    Parser = OptionParser(description=Copyright, version=Version, usage=UsageString)
    Parser.add_option("-o", "--output", dest="OutputFile", help="The name of the %s file to be created." % Destinate)
    Parser.add_option("-a", "--auto", dest="AutoWrite", action="store_true", default=False, help="Automatically create the %s file using the name of the %s file and replacing file extension" % (Source, Destinate))
    Parser.add_option("-q", "--quiet", action="store_true", type=None, help="Disable all messages except FATAL ERRORS.")
    Parser.add_option("-v", "--verbose", action="store_true", type=None, help="Turn on verbose output with informational messages printed.")

    Options, Args = Parser.parse_args()

    # Set logging level
    if Options.verbose:
        EdkLogger.setLevel(EdkLogger.VERBOSE)
    elif Options.quiet:
        EdkLogger.setLevel(EdkLogger.QUIET)
    else:
        EdkLogger.setLevel(EdkLogger.INFO)
        
    # error check
    if len(Args) == 0:
        raise MigrationError(PARAMETER_MISSING, name="Input file", usage=Parser.get_usage())
    if len(Args) > 1:
        raise MigrationError(PARAMETER_INVALID, name="Too many input files", usage=Parser.get_usage())

    InputFile = Args[0]
    if not os.path.exists(InputFile):
        raise MigrationError(FILE_NOT_FOUND, name=InputFile)

    if Options.OutputFile:
        if Options.AutoWrite:
            raise MigrationError(OPTION_CONFLICT, arg1="-o", arg2="-a", usage=Parser.get_usage())
    else:
        if Options.AutoWrite:
            Options.OutputFile = os.path.splitext(InputFile)[0] + "." + Destinate.lower()
        else:
            raise MigrationError(OPTION_MISSING, name="-o", usage=Parser.get_usage())

    return Options, InputFile

# This acts like the main() function for the script, unless it is 'import'ed
# into another script.
if __name__ == '__main__':
    pass
