## @file
# This file is used to create report for Eot tool
#
# Copyright (c) 2008 - 2018, 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
#
from __future__ import absolute_import
import Common.LongFilePathOs as os
from . import EotGlobalData
from Common.LongFilePathSupport import OpenLongFilePath as open

## Report() class
#
#  This class defined Report
#
#  @param object: Inherited from object class
#
class Report(object):
    ## The constructor
    #
    #  @param  self: The object pointer
    #  @param  ReportName: name of the report
    #  @param  FvObj: FV object after parsing FV images
    #
    def __init__(self, ReportName = 'Report.html', FvObj = None, DispatchName=None):
        self.ReportName = ReportName
        self.Op = open(ReportName, 'w+')
        self.DispatchList = None
        if DispatchName:
            self.DispatchList = open(DispatchName, 'w+')
        self.FvObj = FvObj
        self.FfsIndex = 0
        self.PpiIndex = 0
        self.ProtocolIndex = 0
        if EotGlobalData.gMACRO['EFI_SOURCE'] == '':
            EotGlobalData.gMACRO['EFI_SOURCE'] = EotGlobalData.gMACRO['EDK_SOURCE']

    ## WriteLn() method
    #
    #  Write a line in the report
    #
    #  @param  self: The object pointer
    #  @param Line:  The lint to be written into
    #
    def WriteLn(self, Line):
        self.Op.write('%s\n' % Line)

    ## GenerateReport() method
    #
    #  A caller to generate report
    #
    #  @param  self: The object pointer
    #
    def GenerateReport(self):
        self.GenerateHeader()
        self.GenerateFv()
        self.GenerateTail()
        self.Op.close()
        self.GenerateUnDispatchedList()

    ## GenerateUnDispatchedList() method
    #
    #  Create a list for not dispatched items
    #
    #  @param  self: The object pointer
    #
    def GenerateUnDispatchedList(self):
        FvObj = self.FvObj
        EotGlobalData.gOP_UN_DISPATCHED.write('%s\n' % FvObj.Name)
        for Item in FvObj.UnDispatchedFfsDict.keys():
            EotGlobalData.gOP_UN_DISPATCHED.write('%s\n' % FvObj.UnDispatchedFfsDict[Item])

    ## GenerateFv() method
    #
    #  Generate FV information
    #
    #  @param  self: The object pointer
    #
    def GenerateFv(self):
        FvObj = self.FvObj
        Content = """  <tr>
    <td width="20%%"><strong>Name</strong></td>
    <td width="60%%"><strong>Guid</strong></td>
    <td width="20%%"><strong>Size</strong></td>
  </tr>"""
        self.WriteLn(Content)

        for Info in FvObj.BasicInfo:
            FvName = Info[0]
            FvGuid = Info[1]
            FvSize = Info[2]

            Content = """  <tr>
    <td>%s</td>
    <td>%s</td>
    <td>%s</td>
  </tr>"""  % (FvName, FvGuid, FvSize)
            self.WriteLn(Content)

        Content = """    <td colspan="3"><table width="100%%"  border="1">
      <tr>"""
        self.WriteLn(Content)

        EotGlobalData.gOP_DISPATCH_ORDER.write('Dispatched:\n')
        for FfsId in FvObj.OrderedFfsDict.keys():
            self.GenerateFfs(FvObj.OrderedFfsDict[FfsId])
        Content = """     </table></td>
  </tr>"""
        self.WriteLn(Content)

        # For UnDispatched
        Content = """    <td colspan="3"><table width="100%%"  border="1">
      <tr>
        <tr><strong>UnDispatched</strong></tr>"""
        self.WriteLn(Content)

        EotGlobalData.gOP_DISPATCH_ORDER.write('\nUnDispatched:\n')
        for FfsId in FvObj.UnDispatchedFfsDict.keys():
            self.GenerateFfs(FvObj.UnDispatchedFfsDict[FfsId])
        Content = """     </table></td>
  </tr>"""
        self.WriteLn(Content)

    ## GenerateDepex() method
    #
    #  Generate Depex information
    #
    #  @param  self: The object pointer
    #  @param DepexString: A DEPEX string needed to be parsed
    #
    def GenerateDepex(self, DepexString):
        NonGuidList = ['AND', 'OR', 'NOT', 'BEFORE', 'AFTER', 'TRUE', 'FALSE']
        ItemList = DepexString.split(' ')
        DepexString = ''
        for Item in ItemList:
            if Item not in NonGuidList:
                SqlCommand = """select DISTINCT GuidName from Report where GuidValue like '%s' and ItemMode = 'Produced' group by GuidName""" % (Item)
                RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
                if RecordSet != []:
                    Item = RecordSet[0][0]
            DepexString = DepexString + Item + ' '
        Content = """                <tr>
                  <td width="5%%"></td>
                  <td width="95%%">%s</td>
                </tr>""" % (DepexString)
        self.WriteLn(Content)

    ## GeneratePpi() method
    #
    #  Generate PPI information
    #
    #  @param self: The object pointer
    #  @param Name: CName of a GUID
    #  @param Guid: Value of a GUID
    #  @param Type: Type of a GUID
    #
    def GeneratePpi(self, Name, Guid, Type):
        self.GeneratePpiProtocol('Ppi', Name, Guid, Type, self.PpiIndex)

    ## GenerateProtocol() method
    #
    #  Generate PROTOCOL information
    #
    #  @param self: The object pointer
    #  @param Name: CName of a GUID
    #  @param Guid: Value of a GUID
    #  @param Type: Type of a GUID
    #
    def GenerateProtocol(self, Name, Guid, Type):
        self.GeneratePpiProtocol('Protocol', Name, Guid, Type, self.ProtocolIndex)

    ## GeneratePpiProtocol() method
    #
    #  Generate PPI/PROTOCOL information
    #
    #  @param self: The object pointer
    #  @param Model: Model of a GUID, PPI or PROTOCOL
    #  @param Name: Name of a GUID
    #  @param Guid: Value of a GUID
    #  @param Type: Type of a GUID
    #  @param CName: CName(Index) of a GUID
    #
    def GeneratePpiProtocol(self, Model, Name, Guid, Type, CName):
        Content = """                <tr>
                  <td width="5%%"></td>
                  <td width="10%%">%s</td>
                  <td width="85%%" colspan="3">%s</td>
                  <!-- %s -->
                </tr>""" % (Model, Name, Guid)
        self.WriteLn(Content)
        if Type == 'Produced':
            SqlCommand = """select DISTINCT SourceFileFullPath, BelongsToFunction from Report where GuidName like '%s' and ItemMode = 'Callback'""" % Name
            RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
            for Record in RecordSet:
                SqlCommand = """select FullPath from File
                                where ID = (
                                select DISTINCT BelongsToFile from Inf
                                where Value1 like '%s')""" % Record[0]
                ModuleSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
                Inf = ModuleSet[0][0].replace(EotGlobalData.gMACRO['WORKSPACE'], '.')
                Function = Record[1]
                Address = ''
                for Item in EotGlobalData.gMap:
                    if Function in EotGlobalData.gMap[Item]:
                        Address = EotGlobalData.gMap[Item][Function]
                        break
                    if '_' + Function in EotGlobalData.gMap[Item]:
                        Address = EotGlobalData.gMap[Item]['_' + Function]
                        break
                Content = """                <tr>
                      <td width="5%%"></td>
                      <td width="10%%">%s</td>
                      <td width="40%%">%s</td>
                      <td width="35%%">%s</td>
                      <td width="10%%">%s</td>
                    </tr>""" % ('Callback', Inf, Function, Address)
                self.WriteLn(Content)

    ## GenerateFfs() method
    #
    #  Generate FFS information
    #
    #  @param self: The object pointer
    #  @param FfsObj: FFS object after FV image is parsed
    #
    def GenerateFfs(self, FfsObj):
        self.FfsIndex = self.FfsIndex + 1
        if FfsObj is not None and FfsObj.Type in [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xA]:
            FfsGuid = FfsObj.Guid
            FfsOffset = FfsObj._OFF_
            FfsName = 'Unknown-Module'
            FfsPath = FfsGuid
            FfsType = FfsObj._TypeName[FfsObj.Type]

            # Hard code for Binary INF
            if FfsGuid.upper() == '7BB28B99-61BB-11D5-9A5D-0090273FC14D':
                FfsName = 'Logo'

            if FfsGuid.upper() == '7E374E25-8E01-4FEE-87F2-390C23C606CD':
                FfsName = 'AcpiTables'

            if FfsGuid.upper() == '961578FE-B6B7-44C3-AF35-6BC705CD2B1F':
                FfsName = 'Fat'

            # Find FFS Path and Name
            SqlCommand = """select Value2 from Inf
                            where BelongsToFile = (select BelongsToFile from Inf where Value1 = 'FILE_GUID' and lower(Value2) = lower('%s') and Model = %s)
                            and Model = %s and Value1='BASE_NAME'""" % (FfsGuid, 5001, 5001)
            RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
            if RecordSet != []:
                FfsName = RecordSet[0][0]

            SqlCommand = """select FullPath from File
                            where ID = (select BelongsToFile from Inf where Value1 = 'FILE_GUID' and lower(Value2) = lower('%s') and Model = %s)
                            and Model = %s""" % (FfsGuid, 5001, 1011)
            RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
            if RecordSet != []:
                FfsPath = RecordSet[0][0]

            Content = """  <tr>
      <tr class='styleFfs' id='FfsHeader%s'>
        <td width="55%%"><span onclick="Display('FfsHeader%s', 'Ffs%s')" onMouseOver="funOnMouseOver()" onMouseOut="funOnMouseOut()">%s</span></td>
        <td width="15%%">%s</td>
        <!--<td width="20%%">%s</td>-->
        <!--<td width="20%%">%s</td>-->
        <td width="10%%">%s</td>
      </tr>
      <tr id='Ffs%s' style='display:none;'>
        <td colspan="4"><table width="100%%"  border="1">""" % (self.FfsIndex, self.FfsIndex, self.FfsIndex, FfsPath, FfsName, FfsGuid, FfsOffset, FfsType, self.FfsIndex)

            if self.DispatchList:
                if FfsObj.Type in [0x04, 0x06]:
                    self.DispatchList.write("%s %s %s %s\n" % (FfsGuid, "P", FfsName, FfsPath))
                if FfsObj.Type in [0x05, 0x07, 0x08, 0x0A]:
                    self.DispatchList.write("%s %s %s %s\n" % (FfsGuid, "D", FfsName, FfsPath))

            self.WriteLn(Content)

            EotGlobalData.gOP_DISPATCH_ORDER.write('%s\n' %FfsName)

            if FfsObj.Depex != '':
                Content = """          <tr>
            <td><span id='DepexHeader%s' class="styleDepex" onclick="Display('DepexHeader%s', 'Depex%s')" onMouseOver="funOnMouseOver()" onMouseOut="funOnMouseOut()">&nbsp&nbspDEPEX expression</span></td>
          </tr>
          <tr id='Depex%s' style='display:none;'>
            <td><table width="100%%"  border="1">""" % (self.FfsIndex, self.FfsIndex, self.FfsIndex, self.FfsIndex)
                self.WriteLn(Content)
                self.GenerateDepex(FfsObj.Depex)
                Content = """            </table></td>
          </tr>"""
                self.WriteLn(Content)
            # End of DEPEX

            # Find Consumed Ppi/Protocol
            SqlCommand = """select ModuleName, ItemType, GuidName, GuidValue, GuidMacro from Report
                            where SourceFileFullPath in
                            (select Value1 from Inf where BelongsToFile =
                            (select BelongsToFile from Inf
                            where Value1 = 'FILE_GUID' and Value2 like '%s' and Model = %s)
                            and Model = %s)
                            and ItemMode = 'Consumed' group by GuidName order by ItemType""" \
                            % (FfsGuid, 5001, 3007)

            RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
            if RecordSet != []:
                Count = len(RecordSet)
                Content = """          <tr>
            <td><span id='ConsumedHeader%s' class="styleConsumed" onclick="Display('ConsumedHeader%s', 'Consumed%s')" onMouseOver="funOnMouseOver()" onMouseOut="funOnMouseOut()">&nbsp&nbspConsumed Ppis/Protocols List (%s)</span></td>
          </tr>
          <tr id='Consumed%s' style='display:none;'>
            <td><table width="100%%"  border="1">""" % (self.FfsIndex, self.FfsIndex, self.FfsIndex, Count, self.FfsIndex)
                self.WriteLn(Content)
                self.ProtocolIndex = 0
                for Record in RecordSet:
                    self.ProtocolIndex = self.ProtocolIndex + 1
                    Name = Record[2]
                    CName = Record[4]
                    Guid = Record[3]
                    Type = Record[1]
                    self.GeneratePpiProtocol(Type, Name, Guid, 'Consumed', CName)

                Content = """            </table></td>
          </tr>"""
                self.WriteLn(Content)
            #End of Consumed Ppi/Portocol

            # Find Produced Ppi/Protocol
            SqlCommand = """select ModuleName, ItemType, GuidName, GuidValue, GuidMacro from Report
                            where SourceFileFullPath in
                            (select Value1 from Inf where BelongsToFile =
                            (select BelongsToFile from Inf
                            where Value1 = 'FILE_GUID' and Value2 like '%s' and Model = %s)
                            and Model = %s)
                            and ItemMode = 'Produced' group by GuidName order by ItemType""" \
                            % (FfsGuid, 5001, 3007)

            RecordSet = EotGlobalData.gDb.TblReport.Exec(SqlCommand)
            if RecordSet != []:
                Count = len(RecordSet)
                Content = """          <tr>
            <td><span id='ProducedHeader%s' class="styleProduced" onclick="Display('ProducedHeader%s', 'Produced%s')" onMouseOver="funOnMouseOver()" onMouseOut="funOnMouseOut()">&nbsp&nbspProduced Ppis/Protocols List (%s)</span></td>
          </tr>
          <tr id='Produced%s' style='display:none;'>
            <td><table width="100%%"  border="1">""" % (self.FfsIndex, self.FfsIndex, self.FfsIndex, Count, self.FfsIndex)
                self.WriteLn(Content)
                self.PpiIndex = 0
                for Record in RecordSet:
                    self.PpiIndex = self.PpiIndex + 1
                    Name = Record[2]
                    CName = Record[4]
                    Guid = Record[3]
                    Type = Record[1]
                    self.GeneratePpiProtocol(Type, Name, Guid, 'Produced', CName)

                Content = """            </table></td>
          </tr>"""
                self.WriteLn(Content)
            RecordSet = None
            # End of Produced Ppi/Protocol

            Content = """        </table></td>
        </tr>"""
            self.WriteLn(Content)

    ## GenerateTail() method
    #
    #  Generate end tags of HTML report
    #
    #  @param self: The object pointer
    #
    def GenerateTail(self):
        Tail = """</table>
</body>
</html>"""
        self.WriteLn(Tail)

    ## GenerateHeader() method
    #
    #  Generate start tags of HTML report
    #
    #  @param self: The object pointer
    #
    def GenerateHeader(self):
        Header = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Execution Order Tool Report</title>
<meta http-equiv="Content-Type" content="text/html">
<style type="text/css">
<!--
.styleFfs {
    color: #006600;
    font-weight: bold;
}
.styleDepex {
    color: #FF0066;
    font-weight: bold;
}
.styleProduced {
    color: #0000FF;
    font-weight: bold;
}
.styleConsumed {
    color: #FF00FF;
    font-weight: bold;
}
-->
</style>
<Script type="text/javascript">
function Display(ParentID, SubID)
{
    SubItem = document.getElementById(SubID);
    ParentItem = document.getElementById(ParentID);
    if (SubItem.style.display == 'none')
    {
        SubItem.style.display = ''
        ParentItem.style.fontWeight = 'normal'
    }
    else
    {
        SubItem.style.display = 'none'
        ParentItem.style.fontWeight = 'bold'
    }

}

function funOnMouseOver()
{
    document.body.style.cursor = "hand";
}

function funOnMouseOut()
{
    document.body.style.cursor = "";
}

</Script>
</head>

<body>
<table width="100%%"  border="1">"""
        self.WriteLn(Header)

##
#
# This acts like the main() function for the script, unless it is 'import'ed into another
# script.
#
if __name__ == '__main__':
    # Initialize log system
    FilePath = 'FVRECOVERYFLOPPY.fv'
    if FilePath.lower().endswith(".fv"):
        fd = open(FilePath, 'rb')
        buf = array('B')
        try:
            buf.fromfile(fd, os.path.getsize(FilePath))
        except EOFError:
            pass

        fv = FirmwareVolume("FVRECOVERY", buf, 0)

    report = Report('Report.html', fv)
    report.GenerateReport()
