## @file
#
# This file produce action class to generate doxygen document for edk2 codebase.
# The action classes are shared by GUI and command line tools.
#
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent

"""This file produce action class to generate doxygen document for edk2 codebase.
   The action classes are shared by GUI and command line tools.
"""
from plugins.EdkPlugins.basemodel import doxygen
import os
try:
    import wx
    gInGui = True
except:
    gInGui = False
import re
from plugins.EdkPlugins.edk2.model import inf
from plugins.EdkPlugins.edk2.model import dec
from plugins.EdkPlugins.basemodel.message import *

_ignore_dir = ['.svn', '_svn', 'cvs']
_inf_key_description_mapping_table = {
  'INF_VERSION':'Version of INF file specification',
  #'BASE_NAME':'Module Name',
  'FILE_GUID':'Module Guid',
  'MODULE_TYPE': 'Module Type',
  'VERSION_STRING': 'Module Version',
  'LIBRARY_CLASS': 'Produced Library Class',
  'EFI_SPECIFICATION_VERSION': 'UEFI Specification Version',
  'PI_SPECIFICATION_VERSION': 'PI Specification Version',
  'ENTRY_POINT': 'Module Entry Point Function',
  'CONSTRUCTOR': 'Library Constructor Function'
}

_dec_key_description_mapping_table = {
  'DEC_SPECIFICATION': 'Version of DEC file specification',
  'PACKAGE_GUID': 'Package Guid'
}
class DoxygenAction:
    """This is base class for all doxygen action.
    """

    def __init__(self, doxPath, chmPath, outputPath, projname, mode='html', log=None, verbose=False):
        """Constructor function.
        @param  doxPath         the obosolution path of doxygen execute file.
        @param  outputPath      the obosolution output path.
        @param  log             log function for output message
        """
        self._doxPath       = doxPath
        self._chmPath       = chmPath
        self._outputPath    = outputPath
        self._projname      = projname
        self._configFile    = None          # doxygen config file is used by doxygen exe file
        self._indexPageFile = None          # doxygen page file for index page.
        self._log           = log
        self._mode          = mode
        self._verbose       = verbose
        self._doxygenCallback = None
        self._chmCallback     = None

    def Log(self, message, level='info'):
        if self._log is not None:
            self._log(message, level)

    def IsVerbose(self):
        return self._verbose

    def Generate(self):
        """Generate interface called by outer directly"""
        self.Log(">>>>>> Start generate doxygen document for %s... Zzz....\n" % self._projname)

        # create doxygen config file at first
        self._configFile = doxygen.DoxygenConfigFile()
        self._configFile.SetOutputDir(self._outputPath)

        self._configFile.SetWarningFilePath(os.path.join(self._outputPath, 'warning.txt'))
        if self._mode.lower() == 'html':
            self._configFile.SetHtmlMode()
        else:
            self._configFile.SetChmMode()

        self.Log("    >>>>>> Initialize doxygen config file...Zzz...\n")
        self.InitializeConfigFile()

        self.Log("    >>>>>> Generate doxygen index page file...Zzz...\n")
        indexPagePath = self.GenerateIndexPage()
        if indexPagePath is None:
            self.Log("Fail to generate index page!\n", 'error')
            return False
        else:
            self.Log("Success to create doxygen index page file %s \n" % indexPagePath)

        # Add index page doxygen file to file list.
        self._configFile.AddFile(indexPagePath)

        # save config file to output path
        configFilePath = os.path.join(self._outputPath, self._projname + '.doxygen_config')
        self._configFile.Generate(configFilePath)
        self.Log("    <<<<<< Success Save doxygen config file to %s...\n" % configFilePath)

        # launch doxygen tool to generate document
        if self._doxygenCallback is not None:
            self.Log("    >>>>>> Start doxygen process...Zzz...\n")
            if not self._doxygenCallback(self._doxPath, configFilePath):
                return False
        else:
            self.Log("Fail to create doxygen process!", 'error')
            return False

        return True

    def InitializeConfigFile(self):
        """Initialize config setting for doxygen project. It will be invoked after config file
           object is created. Inherited class should implement it.
        """

    def GenerateIndexPage(self):
        """Generate doxygen index page. Inherited class should implement it."""
        return None

    def RegisterCallbackDoxygenProcess(self, callback):
        self._doxygenCallback = callback

    def RegisterCallbackCHMProcess(self, callback):
        self._chmCallback = callback

class PlatformDocumentAction(DoxygenAction):
    """Generate platform doxygen document, will be implement at future."""

class PackageDocumentAction(DoxygenAction):
    """Generate package reference document"""

    def __init__(self, doxPath, chmPath, outputPath, pObj, mode='html', log=None, arch=None, tooltag=None,
                  onlyInclude=False, verbose=False):
        DoxygenAction.__init__(self, doxPath, chmPath, outputPath, pObj.GetName(), mode, log, verbose)
        self._pObj   = pObj
        self._arch   = arch
        self._tooltag = tooltag
        self._onlyIncludeDocument = onlyInclude

    def InitializeConfigFile(self):
        if self._arch == 'IA32':
            self._configFile.AddPreDefined('MDE_CPU_IA32')
        elif self._arch == 'X64':
            self._configFile.AddPreDefined('MDE_CPU_X64')
        elif self._arch == 'IPF':
            self._configFile.AddPreDefined('MDE_CPU_IPF')
        elif self._arch == 'EBC':
            self._configFile.AddPreDefined('MDE_CPU_EBC')
        else:
            self._arch = None
            self._configFile.AddPreDefined('MDE_CPU_IA32')
            self._configFile.AddPreDefined('MDE_CPU_X64')
            self._configFile.AddPreDefined('MDE_CPU_IPF')
            self._configFile.AddPreDefined('MDE_CPU_EBC')
            self._configFile.AddPreDefined('MDE_CPU_ARM')

        namestr = self._pObj.GetName()
        if self._arch is not None:
            namestr += '[%s]' % self._arch
        if self._tooltag is not None:
            namestr += '[%s]' % self._tooltag
        self._configFile.SetProjectName(namestr)
        self._configFile.SetStripPath(self._pObj.GetWorkspace())
        self._configFile.SetProjectVersion(self._pObj.GetFileObj().GetVersion())
        self._configFile.AddPattern('*.decdoxygen')

        if self._tooltag.lower() == 'msft':
            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
        elif self._tooltag.lower() == 'gnu':
            self._configFile.AddPreDefined('__GNUC__')
        elif self._tooltag.lower() == 'intel':
            self._configFile.AddPreDefined('__INTEL_COMPILER')
        else:
            self._tooltag = None
            self._configFile.AddPreDefined('_MSC_EXTENSIONS')
            self._configFile.AddPreDefined('__GNUC__')
            self._configFile.AddPreDefined('__INTEL_COMPILER')

        self._configFile.AddPreDefined('ASM_PFX= ')
        self._configFile.AddPreDefined('OPTIONAL= ')

    def GenerateIndexPage(self):
        """Generate doxygen index page. Inherited class should implement it."""
        fObj   = self._pObj.GetFileObj()
        pdObj  = doxygen.DoxygenFile('%s Package Document' % self._pObj.GetName(),
                                     '%s.decdoxygen' % self._pObj.GetFilename())
        self._configFile.AddFile(pdObj.GetFilename())
        pdObj.AddDescription(fObj.GetFileHeader())

        defSection = fObj.GetSectionByName('defines')[0]
        baseSection = doxygen.Section('PackageBasicInformation', 'Package Basic Information')
        descr = '<TABLE>'
        for obj in defSection.GetObjects():
            if obj.GetKey() in _dec_key_description_mapping_table.keys():
                descr += '<TR>'
                descr += '<TD><B>%s</B></TD>' % _dec_key_description_mapping_table[obj.GetKey()]
                descr += '<TD>%s</TD>' % obj.GetValue()
                descr += '</TR>'
        descr += '</TABLE><br>'
        baseSection.AddDescription(descr)
        pdObj.AddSection(baseSection)

        knownIssueSection = doxygen.Section('Known_Issue_section', 'Known Issue')
        knownIssueSection.AddDescription('<ul>')
        knownIssueSection.AddDescription('<li> OPTIONAL macro for function parameter can not be dealed with doxygen, so it disapear in this document! </li>')
        knownIssueSection.AddDescription('</ul>')
        pdObj.AddSection(knownIssueSection)

        self.AddAllIncludeFiles(self._pObj, self._configFile)
        pages = self.GenerateIncludesSubPage(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateLibraryClassesSubPage(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GeneratePcdSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateGuidSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GeneratePpiSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        pages = self.GenerateProtocolSubPages(self._pObj, self._configFile)
        if len(pages) != 0:
            pdObj.AddPages(pages)
        if not self._onlyIncludeDocument:
            pdObj.AddPages(self.GenerateModulePages(self._pObj, self._configFile))

        pdObj.Save()
        return pdObj.GetFilename()

    def GenerateIncludesSubPage(self, pObj, configFile):
        # by default add following path as include path to config file
        pkpath = pObj.GetFileObj().GetPackageRootPath()
        configFile.AddIncludePath(os.path.join(pkpath, 'Include'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Library'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Protocol'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Ppi'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'Guid'))
        configFile.AddIncludePath(os.path.join(pkpath, 'Include', 'IndustryStandard'))

        rootArray = []
        pageRoot = doxygen.Page("Public Includes", "%s_public_includes" % pObj.GetName())
        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
        if len(objs) == 0: return []

        for obj in objs:
            # Add path to include path
            path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
            configFile.AddIncludePath(path)

            # only list common folder's include file
            if obj.GetArch().lower() != 'common':
                continue

            bNeedAddIncludePage = False
            topPage = doxygen.Page(self._ConvertPathToDoxygen(path, pObj), 'public_include_top')

            topPage.AddDescription('<ul>\n')
            for file in os.listdir(path):
                if file.lower() in _ignore_dir: continue
                fullpath = os.path.join(path, file)
                if os.path.isfile(fullpath):
                    self.ProcessSourceFileForInclude(fullpath, pObj, configFile)
                    topPage.AddDescription('<li> \link %s\endlink </li>\n' % self._ConvertPathToDoxygen(fullpath, pObj))
                else:
                    if file.lower() in ['library', 'protocol', 'guid', 'ppi', 'ia32', 'x64', 'ipf', 'ebc', 'arm', 'pi', 'uefi', 'aarch64']:
                        continue
                    bNeedAddSubPage = False
                    subpage = doxygen.Page(self._ConvertPathToDoxygen(fullpath, pObj), 'public_include_%s' % file)
                    subpage.AddDescription('<ul>\n')
                    for subfile in os.listdir(fullpath):
                        if subfile.lower() in _ignore_dir: continue
                        bNeedAddSubPage = True
                        subfullpath = os.path.join(fullpath, subfile)
                        self.ProcessSourceFileForInclude(subfullpath, pObj, configFile)
                        subpage.AddDescription('<li> \link %s \endlink </li>\n' % self._ConvertPathToDoxygen(subfullpath, pObj))
                    subpage.AddDescription('</ul>\n')
                    if bNeedAddSubPage:
                        bNeedAddIncludePage = True
                        pageRoot.AddPage(subpage)
            topPage.AddDescription('</ul>\n')
            if bNeedAddIncludePage:
                pageRoot.AddPage(topPage)

        if pageRoot.GetSubpageCount() != 0:
            return [pageRoot]
        else:
            return []

    def GenerateLibraryClassesSubPage(self, pObj, configFile):
        """
        Generate sub page for library class for package.
        One DEC file maybe contains many library class sections
        for different architecture.

        @param  fObj DEC file object.
        """
        rootArray = []
        pageRoot = doxygen.Page("Library Class", "%s_libraryclass" % pObj.GetName())
        objs = pObj.GetFileObj().GetSectionObjectsByName('libraryclass', self._arch)
        if len(objs) == 0: return []

        if self._arch is not None:
            for obj in objs:
                classPage = doxygen.Page(obj.GetClassName(),
                                         "lc_%s" % obj.GetClassName())
                comments = obj.GetComment()
                if len(comments) != 0:
                    classPage.AddDescription('<br>\n'.join(comments) + '<br>\n')
                pageRoot.AddPage(classPage)
                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                path = path[len(pObj.GetWorkspace()) + 1:]
                if len(comments) == 0:
                    classPage.AddDescription('\copydoc %s<p>' % obj.GetHeaderFile())
                section = doxygen.Section('ref', 'Refer to Header File')
                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
                section.AddDescription(' \endlink<p>\n')
                classPage.AddSection(section)
                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
        else:
            archPageDict = {}
            for obj in objs:
                if obj.GetArch() not in archPageDict.keys():
                    archPageDict[obj.GetArch()] = doxygen.Page(obj.GetArch(),
                                                               'lc_%s' % obj.GetArch())
                    pageRoot.AddPage(archPageDict[obj.GetArch()])
                subArchRoot = archPageDict[obj.GetArch()]
                classPage = doxygen.Page(obj.GetClassName(),
                                         "lc_%s" % obj.GetClassName())
                comments = obj.GetComment()
                if len(comments) != 0:
                    classPage.AddDescription('<br>\n'.join(comments) + '<br>\n')
                subArchRoot.AddPage(classPage)
                path = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())
                path = path[len(pObj.GetWorkspace()) + 1:]
                if len(comments) == 0:
                    classPage.AddDescription('\copydoc %s<p>' % obj.GetHeaderFile())
                section = doxygen.Section('ref', 'Refer to Header File')
                section.AddDescription('\link %s\n' % obj.GetHeaderFile())
                section.AddDescription(' \endlink<p>\n')
                classPage.AddSection(section)
                fullPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetHeaderFile())

                self.ProcessSourceFileForInclude(fullPath, pObj, configFile)
        rootArray.append(pageRoot)
        return rootArray

    def ProcessSourceFileForInclude(self, path, pObj, configFile, infObj=None):
        """
        @param path        the analysising file full path
        @param pObj        package object
        @param configFile  doxygen config file.
        """
        if gInGui:
            wx.Yield()
        if not os.path.exists(path):
            ErrorMsg('Source file path %s does not exist!' % path)
            return

        if configFile.FileExists(path):
            return

        try:
            with open(path, 'r') as f:
                lines = f.readlines()
        except UnicodeDecodeError:
            return
        except IOError:
            ErrorMsg('Fail to open file %s' % path)
            return

        configFile.AddFile(path)

        no = 0
        for no in range(len(lines)):
            if len(lines[no].strip()) == 0:
                continue
            if lines[no].strip()[:2] in ['##', '//', '/*', '*/']:
                continue
            index = lines[no].lower().find('include')
            #mo = IncludePattern.finditer(lines[no].lower())
            mo = re.match(r"^#\s*include\s+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip().lower())
            if not mo:
                continue
            mo = re.match(r"^[#\w\s]+[<\"]([\\/\w.]+)[>\"]$", lines[no].strip())
            filePath = mo.groups()[0]

            if filePath is None or len(filePath) == 0:
                continue

            # find header file in module's path firstly.
            fullPath = None

            if os.path.exists(os.path.join(os.path.dirname(path), filePath)):
                # Find the file in current directory
                fullPath = os.path.join(os.path.dirname(path), filePath).replace('\\', '/')
            else:
                # find in depedent package's include path
                incObjs = pObj.GetFileObj().GetSectionObjectsByName('includes')
                for incObj in incObjs:
                    incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), incObj.GetPath()).strip()
                    incPath = os.path.realpath(os.path.join(incPath, filePath))
                    if os.path.exists(incPath):
                        fullPath = incPath
                        break
                if infObj is not None:
                    pkgInfObjs = infObj.GetSectionObjectsByName('packages')
                    for obj in  pkgInfObjs:
                        decObj = dec.DECFile(os.path.join(pObj.GetWorkspace(), obj.GetPath()))
                        if not decObj:
                            ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
                            continue
                        if not decObj.Parse():
                            ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
                            continue
                        incObjs = decObj.GetSectionObjectsByName('includes')
                        for incObj in incObjs:
                            incPath = os.path.join(decObj.GetPackageRootPath(), incObj.GetPath()).replace('\\', '/')
                            if os.path.exists(os.path.join(incPath, filePath)):
                                fullPath = os.path.join(os.path.join(incPath, filePath))
                                break
                        if fullPath is not None:
                            break

            if fullPath is None and self.IsVerbose():
                self.Log('Can not resolve header file %s for file %s in package %s\n' % (filePath, path, pObj.GetFileObj().GetFilename()), 'error')
                return
            else:
                fullPath = fullPath.replace('\\', '/')
                if self.IsVerbose():
                    self.Log('Preprocessing: Add include file %s for file %s\n' % (fullPath, path))
                #LogMsg ('Preprocessing: Add include file %s for file %s' % (fullPath, path))
                self.ProcessSourceFileForInclude(fullPath, pObj, configFile, infObj)

    def AddAllIncludeFiles(self, pObj, configFile):
        objs = pObj.GetFileObj().GetSectionObjectsByName('includes')
        for obj in objs:
            incPath = os.path.join(pObj.GetFileObj().GetPackageRootPath(), obj.GetPath())
            for root, dirs, files in os.walk(incPath):
                for dir in dirs:
                    if dir.lower() in _ignore_dir:
                        dirs.remove(dir)
                for file in files:
                    path = os.path.normpath(os.path.join(root, file))
                    configFile.AddFile(path.replace('/', '\\'))

    def GeneratePcdSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's PCD definition.
        @param pObj         package object
        @param configFile   config file object
        """
        rootArray = []
        objs = pObj.GetFileObj().GetSectionObjectsByName('pcd')
        if len(objs) == 0:
            return []

        pcdRootPage = doxygen.Page('PCD', 'pcd_root_page')
        typeRootPageDict = {}
        typeArchRootPageDict = {}
        for obj in objs:
            if obj.GetPcdType() not in typeRootPageDict.keys():
                typeRootPageDict[obj.GetPcdType()] = doxygen.Page(obj.GetPcdType(), 'pcd_%s_root_page' % obj.GetPcdType())
                pcdRootPage.AddPage(typeRootPageDict[obj.GetPcdType()])
            typeRoot = typeRootPageDict[obj.GetPcdType()]
            if self._arch is not None:
                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
                pcdPage.AddDescription('<br>\n'.join(obj.GetComment()) + '<br>\n')
                section = doxygen.Section('PCDinformation', 'PCD Information')
                desc  = '<TABLE>'
                desc += '<TR>'
                desc += '<TD><CAPTION>Name</CAPTION></TD>'
                desc += '<TD><CAPTION>Token Space</CAPTION></TD>'
                desc += '<TD><CAPTION>Token number</CAPTION></TD>'
                desc += '<TD><CAPTION>Data Type</CAPTION></TD>'
                desc += '<TD><CAPTION>Default Value</CAPTION></TD>'
                desc += '</TR>'
                desc += '<TR>'
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[1]
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[0]
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdToken()
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdDataType()
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdValue()
                desc += '</TR>'
                desc += '</TABLE>'
                section.AddDescription(desc)
                pcdPage.AddSection(section)
                typeRoot.AddPage(pcdPage)
            else:
                keystr = obj.GetPcdType() + obj.GetArch()
                if keystr not in typeArchRootPageDict.keys():
                    typeArchRootPage = doxygen.Page(obj.GetArch(), 'pcd_%s_%s_root_page' % (obj.GetPcdType(), obj.GetArch()))
                    typeArchRootPageDict[keystr] = typeArchRootPage
                    typeRoot.AddPage(typeArchRootPage)
                typeArchRoot = typeArchRootPageDict[keystr]
                pcdPage = doxygen.Page('%s' % obj.GetPcdName(),
                                        'pcd_%s_%s_%s' % (obj.GetPcdType(), obj.GetArch(), obj.GetPcdName().split('.')[1]))
                pcdPage.AddDescription('<br>\n'.join(obj.GetComment()) + '<br>\n')
                section = doxygen.Section('PCDinformation', 'PCD Information')
                desc  = '<TABLE>'
                desc += '<TR>'
                desc += '<TD><CAPTION>Name</CAPTION></TD>'
                desc += '<TD><CAPTION>Token Space</CAPTION></TD>'
                desc += '<TD><CAPTION>Token number</CAPTION></TD>'
                desc += '<TD><CAPTION>Data Type</CAPTION></TD>'
                desc += '<TD><CAPTION>Default Value</CAPTION></TD>'
                desc += '</TR>'
                desc += '<TR>'
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[1]
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdName().split('.')[0]
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdToken()
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdDataType()
                desc += '<TD><CAPTION>%s</CAPTION></TD>' % obj.GetPcdValue()
                desc += '</TR>'
                desc += '</TABLE>'
                section.AddDescription(desc)
                pcdPage.AddSection(section)
                typeArchRoot.AddPage(pcdPage)
        return [pcdRootPage]

    def _GenerateGuidSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page('%s' % obj.GetName(),
                                'guid_%s_%s' % (obj.GetArch(), obj.GetName()))
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
        section = doxygen.Section('BasicGuidInfo', 'GUID Information')
        desc  = '<TABLE>'
        desc += '<TR>'
        desc += '<TD><CAPTION>GUID\'s Guid Name</CAPTION></TD><TD><CAPTION>GUID\'s Guid</CAPTION></TD>'
        desc += '</TR>'
        desc += '<TR>'
        desc += '<TD>%s</TD>' % obj.GetName()
        desc += '<TD>%s</TD>' % obj.GetGuid()
        desc += '</TR>'
        desc += '</TABLE>'
        section.AddDescription(desc)
        guidPage.AddSection(section)
        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)

            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)
        return guidPage

    def GenerateGuidSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('GUID', 'guid_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('guids', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'guid_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GenerateGuidSubPage(pObj, obj, configFile))
        return [pageRoot]

    def _GeneratePpiSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page(obj.GetName(), 'ppi_page_%s' % obj.GetName())
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
        section = doxygen.Section('BasicPpiInfo', 'PPI Information')
        desc  = '<TABLE>'
        desc += '<TR>'
        desc += '<TD><CAPTION>PPI\'s Guid Name</CAPTION></TD><TD><CAPTION>PPI\'s Guid</CAPTION></TD>'
        desc += '</TR>'
        desc += '<TR>'
        desc += '<TD>%s</TD>' % obj.GetName()
        desc += '<TD>%s</TD>' % obj.GetGuid()
        desc += '</TR>'
        desc += '</TABLE>'
        section.AddDescription(desc)
        guidPage.AddSection(section)
        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)
            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)

        return guidPage

    def GeneratePpiSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('PPI', 'ppi_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('ppis', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'ppi_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GeneratePpiSubPage(pObj, obj, configFile))
        return [pageRoot]

    def _GenerateProtocolSubPage(self, pObj, obj, configFile):
        guidPage = doxygen.Page(obj.GetName(), 'protocol_page_%s' % obj.GetName())
        comments = obj.GetComment()
        if len(comments) != 0:
            guidPage.AddDescription('<br>'.join(obj.GetComment()) + '<br>')
        section = doxygen.Section('BasicProtocolInfo', 'PROTOCOL Information')
        desc  = '<TABLE>'
        desc += '<TR>'
        desc += '<TD><CAPTION>PROTOCOL\'s Guid Name</CAPTION></TD><TD><CAPTION>PROTOCOL\'s Guid</CAPTION></TD>'
        desc += '</TR>'
        desc += '<TR>'
        desc += '<TD>%s</TD>' % obj.GetName()
        desc += '<TD>%s</TD>' % obj.GetGuid()
        desc += '</TR>'
        desc += '</TABLE>'
        section.AddDescription(desc)
        guidPage.AddSection(section)

        refFile = self.FindHeaderFileForGuid(pObj, obj.GetName(), configFile)
        if refFile:
            relPath = refFile[len(pObj.GetWorkspace()) + 1:]
            if len(comments) == 0:
                guidPage.AddDescription(' \\copydoc %s <br>' % relPath)
            section = doxygen.Section('ref', 'Refer to Header File')
            section.AddDescription('\link %s\n' % relPath)
            section.AddDescription('\endlink\n')
            self.ProcessSourceFileForInclude(refFile, pObj, configFile)
            guidPage.AddSection(section)

        return guidPage

    def GenerateProtocolSubPages(self, pObj, configFile):
        """
        Generate sub pages for package's GUID definition.
        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        pageRoot = doxygen.Page('PROTOCOL', 'protocol_root_page')
        objs = pObj.GetFileObj().GetSectionObjectsByName('protocols', self._arch)
        if len(objs) == 0: return []
        if self._arch is not None:
            for obj in objs:
                pageRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
        else:
            guidArchRootPageDict = {}
            for obj in objs:
                if obj.GetArch() not in guidArchRootPageDict.keys():
                    guidArchRoot = doxygen.Page(obj.GetArch(), 'protocol_arch_root_%s' % obj.GetArch())
                    pageRoot.AddPage(guidArchRoot)
                    guidArchRootPageDict[obj.GetArch()] = guidArchRoot
                guidArchRoot = guidArchRootPageDict[obj.GetArch()]
                guidArchRoot.AddPage(self._GenerateProtocolSubPage(pObj, obj, configFile))
        return [pageRoot]

    def FindHeaderFileForGuid(self, pObj, name, configFile):
        """
        For declaration header file for GUID/PPI/Protocol.

        @param pObj         package object
        @param name         guid/ppi/protocol's name
        @param configFile   config file object

        @return full path of header file and None if not found.
        """
        startPath  = pObj.GetFileObj().GetPackageRootPath()
        incPath    = os.path.join(startPath, 'Include').replace('\\', '/')
        # if <PackagePath>/include exist, then search header under it.
        if os.path.exists(incPath):
            startPath = incPath

        for root, dirs, files in os.walk(startPath):
            for dir in dirs:
                if dir.lower() in _ignore_dir:
                    dirs.remove(dir)
            for file in files:
                fPath = os.path.join(root, file)
                if not IsCHeaderFile(fPath):
                    continue
                try:
                    f = open(fPath, 'r')
                    lines = f.readlines()
                    f.close()
                except IOError:
                    self.Log('Fail to open file %s\n' % fPath)
                    continue
                for line in lines:
                    if line.find(name) != -1 and \
                       line.find('extern') != -1:
                        return fPath.replace('\\', '/')
        return None

    def GetPackageModuleList(self, pObj):
        """
        Get all module's INF path under package's root path
        @param     pObj  package object
        @return    arrary of INF full path
        """
        mArray = []
        packPath = pObj.GetFileObj().GetPackageRootPath()
        if not os.path.exists:
            return None
        for root, dirs, files in os.walk(packPath):
            for dir in dirs:
                if dir.lower() in _ignore_dir:
                    dirs.remove(dir)
            for file in files:
                if CheckPathPostfix(file, 'inf'):
                    fPath = os.path.join(root, file).replace('\\', '/')
                    mArray.append(fPath)
        return mArray

    def GenerateModulePages(self, pObj, configFile):
        """
        Generate sub pages for package's module which is under the package
        root directory.

        @param  pObj            package object
        @param  configFilf      doxygen config file object
        """
        infList = self.GetPackageModuleList(pObj)
        rootPages = []
        libObjs = []
        modObjs = []
        for infpath in infList:
            infObj = inf.INFFile(infpath)
            #infObj = INFFileObject.INFFile (pObj.GetWorkspacePath(),
            #                                inf)
            if not infObj:
                self.Log('Fail create INF object for %s' % inf)
                continue
            if not infObj.Parse():
                self.Log('Fail to load INF file %s' % inf)
                continue
            if infObj.GetProduceLibraryClass() is not None:
                libObjs.append(infObj)
            else:
                modObjs.append(infObj)

        if len(libObjs) != 0:
            libRootPage = doxygen.Page('Libraries', 'lib_root_page')
            rootPages.append(libRootPage)
            for libInf in libObjs:
                libRootPage.AddPage(self.GenerateModulePage(pObj, libInf, configFile, True))

        if len(modObjs) != 0:
            modRootPage = doxygen.Page('Modules', 'module_root_page')
            rootPages.append(modRootPage)
            for modInf in modObjs:
                modRootPage.AddPage(self.GenerateModulePage(pObj, modInf, configFile, False))

        return rootPages

    def GenerateModulePage(self, pObj, infObj, configFile, isLib):
        """
        Generate page for a module/library.
        @param infObj     INF file object for module/library
        @param configFile doxygen config file object
        @param isLib      Whether this module is library

        @param module doxygen page object
        """
        workspace = pObj.GetWorkspace()
        refDecObjs = []
        for obj in  infObj.GetSectionObjectsByName('packages'):
            decObj = dec.DECFile(os.path.join(workspace, obj.GetPath()))
            if not decObj:
                ErrorMsg ('Fail to create pacakge object for %s' % obj.GetPackageName())
                continue
            if not decObj.Parse():
                ErrorMsg ('Fail to load package object for %s' % obj.GetPackageName())
                continue
            refDecObjs.append(decObj)

        modPage = doxygen.Page('%s' % infObj.GetBaseName(),
                               'module_%s' % infObj.GetBaseName())
        modPage.AddDescription(infObj.GetFileHeader())

        basicInfSection = doxygen.Section('BasicModuleInformation', 'Basic Module Information')
        desc = "<TABLE>"
        for obj in infObj.GetSectionObjectsByName('defines'):
            key = obj.GetKey()
            value = obj.GetValue()
            if key not in _inf_key_description_mapping_table.keys(): continue
            if key == 'LIBRARY_CLASS' and value.find('|') != -1:
                clsname, types = value.split('|')
                desc += '<TR>'
                desc += '<TD><B>%s</B></TD>' % _inf_key_description_mapping_table[key]
                desc += '<TD>%s</TD>' % clsname
                desc += '</TR>'

                desc += '<TR>'
                desc += '<TD><B>Supported Module Types</B></TD>'
                desc += '<TD>%s</TD>' % types
                desc += '</TR>'
            else:
                desc += '<TR>'
                desc += '<TD><B>%s</B></TD>' % _inf_key_description_mapping_table[key]
                if key == 'EFI_SPECIFICATION_VERSION' and value == '0x00020000':
                    value = '2.0'
                desc += '<TD>%s</TD>' % value
                desc += '</TR>'
        desc += '</TABLE>'
        basicInfSection.AddDescription(desc)
        modPage.AddSection(basicInfSection)

        # Add protocol section
        data  = []
        for obj in infObj.GetSectionObjectsByName('pcd', self._arch):
            data.append(obj.GetPcdName().strip())
        if len(data) != 0:
            s = doxygen.Section('Pcds', 'Pcds')
            desc = "<TABLE>"
            desc += '<TR><TD><B>PCD Name</B></TD><TD><B>TokenSpace</B></TD><TD><B>Package</B></TD></TR>'
            for item in data:
                desc += '<TR>'
                desc += '<TD>%s</TD>' % item.split('.')[1]
                desc += '<TD>%s</TD>' % item.split('.')[0]
                pkgbasename = self.SearchPcdPackage(item, workspace, refDecObjs)
                desc += '<TD>%s</TD>' % pkgbasename
                desc += '</TR>'
            desc += "</TABLE>"
            s.AddDescription(desc)
            modPage.AddSection(s)

        # Add protocol section
        #sects = infObj.GetSectionByString('protocol')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('protocol', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Protocols', 'Protocols')
            desc = "<TABLE>"
            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
            for item in data:
                desc += '<TR>'
                desc += '<TD>%s</TD>' % item
                pkgbasename = self.SearchProtocolPackage(item, workspace, refDecObjs)
                desc += '<TD>%s</TD>' % pkgbasename
                desc += '</TR>'
            desc += "</TABLE>"
            s.AddDescription(desc)
            modPage.AddSection(s)

        # Add ppi section
        #sects = infObj.GetSectionByString('ppi')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('ppi', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Ppis', 'Ppis')
            desc = "<TABLE>"
            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
            for item in data:
                desc += '<TR>'
                desc += '<TD>%s</TD>' % item
                pkgbasename = self.SearchPpiPackage(item, workspace, refDecObjs)
                desc += '<TD>%s</TD>' % pkgbasename
                desc += '</TR>'
            desc += "</TABLE>"
            s.AddDescription(desc)
            modPage.AddSection(s)

        # Add guid section
        #sects = infObj.GetSectionByString('guid')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('guid', self._arch):
            data.append(obj.GetName().strip())
        if len(data) != 0:
            s = doxygen.Section('Guids', 'Guids')
            desc = "<TABLE>"
            desc += '<TR><TD><B>Name</B></TD><TD><B>Package</B></TD></TR>'
            for item in data:
                desc += '<TR>'
                desc += '<TD>%s</TD>' % item
                pkgbasename = self.SearchGuidPackage(item, workspace, refDecObjs)
                desc += '<TD>%s</TD>' % pkgbasename
                desc += '</TR>'
            desc += "</TABLE>"
            s.AddDescription(desc)
            modPage.AddSection(s)

        section = doxygen.Section('LibraryClasses', 'Library Classes')
        desc = "<TABLE>"
        desc += '<TR><TD><B>Name</B></TD><TD><B>Type</B></TD><TD><B>Package</B></TD><TD><B>Header File</B></TD></TR>'
        if isLib:
            desc += '<TR>'
            desc += '<TD>%s</TD>' % infObj.GetProduceLibraryClass()
            desc += '<TD>Produce</TD>'
            try:
                pkgname, hPath = self.SearchLibraryClassHeaderFile(infObj.GetProduceLibraryClass(),
                                                              workspace,
                                                              refDecObjs)
            except:
                self.Log ('fail to get package header file for lib class %s' % infObj.GetProduceLibraryClass())
                pkgname = 'NULL'
                hPath   = 'NULL'
            desc += '<TD>%s</TD>' % pkgname
            if hPath != "NULL":
                desc += '<TD>\link %s \endlink</TD>' % hPath
            else:
                desc += '<TD>%s</TD>' % hPath
            desc += '</TR>'
        for lcObj in infObj.GetSectionObjectsByName('libraryclasses', self._arch):
            desc += '<TR>'
            desc += '<TD>%s</TD>' % lcObj.GetClass()
            retarr = self.SearchLibraryClassHeaderFile(lcObj.GetClass(),
                                                       workspace,
                                                       refDecObjs)
            if retarr is not None:
                pkgname, hPath = retarr
            else:
                self.Log('Fail find the library class %s definition from module %s dependent package!' % (lcObj.GetClass(), infObj.GetFilename()), 'error')
                pkgname = 'NULL'
                hPath   = 'NULL'
            desc += '<TD>Consume</TD>'
            desc += '<TD>%s</TD>' % pkgname
            desc += '<TD>\link %s \endlink</TD>' % hPath
            desc += '</TR>'
        desc += "</TABLE>"
        section.AddDescription(desc)
        modPage.AddSection(section)

        section = doxygen.Section('SourceFiles', 'Source Files')
        section.AddDescription('<ul>\n')
        for obj in infObj.GetSourceObjects(self._arch, self._tooltag):
            sPath = infObj.GetModuleRootPath()
            sPath = os.path.join(sPath, obj.GetSourcePath()).replace('\\', '/').strip()
            if sPath.lower().endswith('.uni') or sPath.lower().endswith('.s') or sPath.lower().endswith('.asm') or sPath.lower().endswith('.nasm'):
                newPath = self.TranslateUniFile(sPath)
                configFile.AddFile(newPath)
                newPath = newPath[len(pObj.GetWorkspace()) + 1:]
                section.AddDescription('<li> \link %s \endlink </li>' %  newPath)
            else:
                self.ProcessSourceFileForInclude(sPath, pObj, configFile, infObj)
                sPath = sPath[len(pObj.GetWorkspace()) + 1:]
                section.AddDescription('<li>\link %s \endlink </li>' % sPath)
        section.AddDescription('</ul>\n')
        modPage.AddSection(section)

        #sects = infObj.GetSectionByString('depex')
        data  = []
        #for sect in sects:
        for obj in infObj.GetSectionObjectsByName('depex'):
            data.append(str(obj))
        if len(data) != 0:
            s = doxygen.Section('DependentSection', 'Module Dependencies')
            s.AddDescription('<br>'.join(data))
            modPage.AddSection(s)

        return modPage

    def TranslateUniFile(self, path):
        newpath = path + '.dox'
        #import core.textfile as textfile
        #file = textfile.TextFile(path)

        try:
            file = open(path, 'r')
        except (IOError, OSError) as msg:
            return None

        t = file.read()
        file.close()

        output = '/** @file \n'
        #output = '<html><body>'
        arr = t.split('\r\n')
        for line in arr:
            if line.find('@file') != -1:
                continue
            if line.find('*/') != -1:
                continue
            line = line.strip()
            if line.strip().startswith('/'):
                arr = line.split(' ')
                if len(arr) > 1:
                    line = ' '.join(arr[1:])
                else:
                    continue
            output += '%s<br>\n' % line
        output += '**/'

        if os.path.exists(newpath):
            os.remove(newpath)

        file = open(newpath, "w")
        file.write(output)
        file.close()
        return newpath

    def SearchPcdPackage(self, pcdname, workspace, decObjs):
        for decObj in  decObjs:
            for pcd in decObj.GetSectionObjectsByName('pcd'):
                if pcdname == pcd.GetPcdName():
                    return decObj.GetBaseName()
        return None

    def SearchProtocolPackage(self, protname, workspace, decObjs):
        for decObj in  decObjs:
            for proto in decObj.GetSectionObjectsByName('protocol'):
                if protname == proto.GetName():
                    return decObj.GetBaseName()
        return None

    def SearchPpiPackage(self, ppiname, workspace, decObjs):
        for decObj in  decObjs:
            for ppi in decObj.GetSectionObjectsByName('ppi'):
                if ppiname == ppi.GetName():
                    return decObj.GetBaseName()
        return None

    def SearchGuidPackage(self, guidname, workspace, decObjs):
        for decObj in  decObjs:
            for guid in decObj.GetSectionObjectsByName('guid'):
                if guidname == guid.GetName():
                    return decObj.GetBaseName()
        return None

    def SearchLibraryClassHeaderFile(self, className, workspace, decObjs):
        for decObj in  decObjs:
            for cls in decObj.GetSectionObjectsByName('libraryclasses'):
                if cls.GetClassName().strip() == className:
                    path = cls.GetHeaderFile().strip()
                    path = os.path.join(decObj.GetPackageRootPath(), path)
                    path = path[len(workspace) + 1:]
                    return decObj.GetBaseName(), path.replace('\\', '/')

        return None

    def _ConvertPathToDoxygen(self, path, pObj):
        pRootPath = pObj.GetWorkspace()
        path = path[len(pRootPath) + 1:]
        return path.replace('\\', '/')

def IsCHeaderFile(path):
    return CheckPathPostfix(path, 'h')

def CheckPathPostfix(path, str):
    index = path.rfind('.')
    if index == -1:
        return False
    if path[index + 1:].lower() == str.lower():
        return True
    return False
