###################################################################
#
# Copyright (c) 2014 Wi-Fi Alliance
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
# USE OR PERFORMANCE OF THIS SOFTWARE.
#
###################################################################

#!/usr/bin/evn python
from socket import *
from select import select
import os, sys
import logging
import logging.handlers
import time
import xml.dom.minidom
import inspect
import re
import subprocess
import thread, Queue
import ctypes
import pprint
import HTML

from xml.dom.minidom import Document
from xml.dom.minidom import getDOMImplementation
from xml.dom.minidom import parse
from xml.dom.minidom import Node

# Other libs needed for binary packaging
try:
    from myutils import scanner
    from myutils import process_ipadd
    from myutils import process_cmdfile
    from myutils import firstword
    from myutils import init_logging
    from myutils import printStreamResults
    from myutils import close_conn
    from myutils import wfa_sys_exit
    from myutils import setUCCPath
    from myutils import reset
    from InitTestEnv import InitTestEnv
    from time import gmtime, strftime
    from XMLLogger import XMLLogger
except ImportError:
    print ""

# Override createElement:
class XMLDoc(Document):
    def createElement(self, tag, textNode=None):
        el = Document.createElement(self, "%s" % tag)
        el.doc = self
        if textNode:
            el.appendChild(self.createTextNode(textNode))
        return el

class TBDevice:
    def __init__(self, vendor, model, driver, os, WTSControlAgent):
        self.vendor = vendor
        self.model = model
        self.driver = driver
        self.os = os
        self.WTSControlAgent = WTSControlAgent
        self.verified = 0
        self.msg = ""

    def AddXMLNode(self, doc, parent, ver=0):
        device = doc.createElement("Device")
        parent.appendChild(device)

        device.appendChild(doc.createElement("Vendor", self.vendor))
        device.appendChild(doc.createElement("Model", self.model))
        device.appendChild(doc.createElement("Driver", self.driver))
        device.appendChild(doc.createElement("OS", self.os))
        device.appendChild(doc.createElement("WTSControlAgent", self.WTSControlAgent))

        #Check if verification node to be added
        if ver:
            logging.debug("TBDevice::AddXMLNode : ver")
            verification = doc.createElement("Verification")
            if self.verified:
                status = "OK"
            else:
                status = "Mismatch"

            verification.appendChild(doc.createElement("Status", status))
            verification.appendChild(doc.createElement("Message", self.msg))

            device.appendChild(verification)

    def compare(self, node):
        rc = 0
        if self.vendor.lower() != node.vendor.lower():
            rc = 0
        else:
            if self.model.lower() != node.model.lower():
                rc = ("%s\n Model - Expected:%s \n-OR-" % (self.msg, node.model))
            if self.driver.lower() != node.driver.lower():
                rc = ("%s\n Driver - Expected:%s \n-OR-" % (self.msg, node.driver))
            if self.WTSControlAgent.lower() != node.WTSControlAgent.lower():
                rc = ("%s\n WTS Control Agent Expected:%s \n-OR-" % (self.msg, node.WTSControlAgent))

            if rc:
                self.msg = rc
            else:
                self.verified = 1

        return rc
    def __str__(self):
        pass

class WTSComponent:
    def __init_(self, name, version="", others=""):
        self.name = name
        self.version = version
        self.others = others

    def AddXMLNode(self, doc, parent):
        WTS = doc.createElement("WTSComponent")
        parent.appendChild(WTS)

        WTS.appendChild(doc.createElement("Name", self.name))
        WTS.appendChild(doc.createElement("Version", self.version))
        WTS.appendChild(doc.createElement("Others", self.others))

    def __str__(self):
        pass

class LogFile:
    def __init__(self, name, signature):
        self.name = name
        self.signature = signature

    def __str__(self):
        pass

class ACCClient:
    def __init__(self, ipaddr, ipport):
        self.ipaddr = ipaddr
        self.ipport = ipport
        self.socket = 0
        self.info = ""

    def connect(self):
        addr = (self.ipaddr, int(self.ipport))
        self.socket = socket(AF_INET, SOCK_STREAM)
        logging.info("Opening connection with ACC module at %s %s" % addr)

        try:
            self.socket.connect(addr)
            time.sleep(2)
        except:
            exc_info = sys.exc_info()
            logging.error('Connection Error, IP = %s PORT = %s REASON = %s', self.ipaddr, self.ipport, exc_info[1])
            raise StandardError("")

    def verifySign(self, filename):
        logging.info("ACC Client: Verifying Signature for [%s]" % filename)
        verResult = 0

        if not self.socket:
            self.connect()

        cmd = "verify_signature,FileName,%s" % filename

        logging.info(">> ACC:%s" % cmd)

        status = self.socket.send(cmd)
        logging.info("SENT: %s Bytes" % status)
        status = self.socket.recv(1024)

        logging.info("<< ACC:%s" % status)

        if status:
            status = status.lower()
            ss = status.split(",")
            logging.debug(ss)
            if len(ss) < 3:
                logging.error("<< Invalid ACC response: %s" % status)

            logging.info("Sign result [%s] Sign file [%s]" % (ss[1], ss[3]))
            verResult = ss[1]

            if len(ss) > 4:
                self.info = "%s" % ss[5]

        else:
            logging.error("<< Invalid ACC response: %s" % status)

        return verResult

    def __str__(self):
        return "%s:%s [%s]" % (self.ipaddr, self.ipport, self.socket)

class TestCase:
    def __init__(self, testID, result, r1="", r2="", logfiles=[]):
        self.testID = testID
        self.result = result
        self.r1 = r1
        self.r2 = r2
        self.logfiles = []
        self.totalRun = 0
        self.nPass = 0
        self.nFail = 0
        self.nErr = 0
        self.type = ""
        self.optionalFeature = ""
        self.passLogFile = ""
        self.SignVerification = 0
        self.SignVerificationResult = 0

    def AddXMLNode(self, doc, parent):
        TestCase = doc.createElement("TestCase")
        parent.appendChild(TestCase)

        TestCase.appendChild(doc.createElement("TestID", self.testID))
        TestCase.appendChild(doc.createElement("Result", self.result))
        TestCase.appendChild(doc.createElement("R1", self.r1))
        TestCase.appendChild(doc.createElement("R2", self.r2))
        TestCase.appendChild(doc.createElement("LatestPassLogFile", self.passLogFile))

        #Signature verification
        logging.debug("Signature verification required ? [%s]" % self.SignVerification)

        if self.passLogFile:
            if self.passLogFile.startswith("./"):
                fullPathFile = ("%s\\%s" % (os.getcwd(), self.passLogFile.lstrip("./").replace("/", "\\")))
            else:
                fullPathFile = self.passLogFile

        # Verify signature if Sign Flag is ON and .asc signature file exists
        if self.passLogFile and int(self.SignVerification) and os.path.exists("%s.asc" % fullPathFile):
            logging.info("Verifying Signature for [%s]" % fullPathFile)
            self.SignVerificationResult = ACC.verifySign(fullPathFile)
            TestCase.appendChild(doc.createElement("SignVerificationInfo", "%s" % (ACC.info)))
        else:
            TestCase.appendChild(doc.createElement("SignVerificationInfoInfo", "NO sign verification"))

        TestCase.appendChild(doc.createElement("SignVerification", "%s" % (self.SignVerificationResult)))
        LogFiles = doc.createElement("LogFiles")

        for l in self.logfiles:
            LogFile = doc.createElement("LogFile")
            LogFile.appendChild(doc.createElement("Name", l.name))
            LogFile.appendChild(doc.createElement("Signature", l.signature))
            LogFiles.appendChild(LogFile)

        Stats = doc.createElement("Stats")
        TestCase.appendChild(Stats)
        Stats.appendChild(doc.createElement("TotalRun", "%s" % self.totalRun))
        Stats.appendChild(doc.createElement("TotalPASS", "%s" % self.nPass))
        Stats.appendChild(doc.createElement("TotalFAIL", "%s" % self.nFail))
        Stats.appendChild(doc.createElement("TotalERROR", "%s" % self.nErr))
        TestCase.appendChild(LogFiles)

        TestCase.appendChild(doc.createElement("Type", self.type))

        if self.type == "Optional":
            TestCase.appendChild(doc.createElement("OptionalFeature", self.optionalFeature))

    def __str__(self):
        pass

class ResultSummary:
    def __init__(self, fileName, TestCriteriaFile, Prog, uid="", logpath="", mChk=1, stylesheet="Results-Format.xsl"):
        self.fileName = fileName
        self.TestResults = ""
        self.doc = XMLDoc()
        self.uid = uid
        self.logpath = logpath
        self.versionInfoFlag = 0
        self.testplanVersion = ""
        self.testplanID = ""
        self.sigKeyID = ""
        self.TestCases = []
        self.ProgramName = Prog
        self.TestCriteriaFile = xml.dom.minidom.parse(TestCriteriaFile)
        self.SignVerification = 0

        # Stylesheet
        self.doc.appendChild(self.doc.createProcessingInstruction("xml-stylesheet",
                             "type=\"text/xsl\" href=\"%s\"" % stylesheet))

        self.Results = self.doc.createElement("Results")
        self.Result = self.doc.createElement("Result")
        self.ResultInfo = self.doc.createElement("Info")
        self.TestResults = self.doc.createElement("TestResults")

        self.doc.appendChild(self.Results)
        self.Results.appendChild(self.Result)
        self.Result.appendChild(self.ResultInfo)
        self.Result.appendChild(self.TestResults)

        self.ResultInfo.appendChild(self.doc.createElement("ProgramName", self.ProgramName))
        self.ResultInfo.appendChild(self.doc.createElement("time", "%s" % time.strftime("%Y-%m-%dT%H:%M:%SZ", time.localtime())))
        self.ResultInfo.appendChild(self.doc.createElement("UID", self.uid))
        self.ResultInfo.appendChild(self.doc.createElement("TestplanVersion", self.testplanVersion))
        self.ResultInfo.appendChild(self.doc.createElement("TestCriteriaFileVersion", self.getAttrValue(self.TestCriteriaFile, "ConfigSets", "Version")))
        self.ResultInfo.appendChild(self.doc.createElement("TestplanID", self.testplanID))
        self.ResultInfo.appendChild(self.doc.createElement("SignatureKeyID", self.sigKeyID))

        self.mChk = int(mChk)

    def addVersionInfo(self, dlog):
        Testbed = getNodeHandle(dlog, ["Log", "Info", "Testbed"])

        # Add verified Testbed information in results summary
        vTestbed = self.doc.createElement("Testbed")

        if Testbed:
            # List of valid config sets from TestCriteriaFile
            tRight = getNodeHandle(self.TestCriteriaFile, ["ConfigSets", self.ProgramName, "ConfigSet"], "", 1)
            t = getNodeHandle(dlog, ["Log", "Info", "Testbed", "Device"], "", 1)

            if t:
                for tLeft in t:
                    l = self.compareTwoTBNodes(tLeft, tRight)
                    l.AddXMLNode(self.doc, vTestbed, 1)

                self.Result.appendChild(vTestbed)

        DUT = getNodeHandle(dlog, ["Log", "Info", "DUT"])
        if DUT:
            self.Result.appendChild(DUT)

        WTS = getNodeHandle(dlog, ["Log", "Info", "WTS"])
        if WTS:
            self.Result.appendChild(WTS)

        self.versionInfoFlag = 1

    def generateSummary(self):
        for f1 in os.listdir(self.logpath):
            logging.debug("Folder name [%s]" % f1)
            if f1.startswith(self.ProgramName):
                for f in os.listdir(self.logpath + "\\" + f1):
                    logging.debug("--File name [%s]" % f)
                    if f.endswith(".xml"):
                        lf = "%s%s/%s" % (self.logpath, f1, f)
                        logging.info("Processing log file - %s \n", lf)
                        try:
                            dlogFile = xml.dom.minidom.parse(lf)
                        except:
                            logging.debug("------------------------ Malformed file %s--" % f)
                            continue

                        if not self.versionInfoFlag:
                            self.addVersionInfo(dlogFile)
                        tID = self.getAttrValue(dlogFile, "Log", "id")
                        tID = tID.strip()
                        tResult = self.getNodeValue(dlogFile, "Log", "TestCaseResult")
                        sig = "1"

                        # Add result nodes to TestCase
                        if tID and tResult:
                            tResult = tResult.strip()
                            tList = ""
                            # check if test already present

                            for tList in self.TestCases:
                                if tList.testID == tID:
                                    break
                                logging.debug("-----------------[%s][%s]" % (tList.testID, tID))

                            # Always pick the latest PASS results
                            if tList == "" or tList.testID != tID:
                                t1 = TestCase(tID, tResult)
                                t1.passLogFile = lf
                                t1.SignVerification = self.SignVerification
                                self.TestCases.append(t1)
                                logging.debug("-----------------Appending[%s]" % (tID))
                            else:
                                t1 = tList

                            if tResult == "PASS" or tResult == "PASSED" or tResult == "COMPLETED":
                                t1.nPass = int(t1.nPass) + 1
                                t1.result = tResult
                                t1.passLogFile = lf

                            elif tResult == "FAILED" or tResult == "FAIL":
                                t1.nFail = int(t1.nFail) + 1
                            else:
                                #Check if manual check should be done
                                mchkInfo1 = self.getNodeValueloop(dlogFile, "Log", "ManualCheckInfo")
                                mchkInfo1 = mchkInfo1.split(",")
                                var = ""
                                for mchkInfo in mchkInfo1:
                                    if self.mChk and mchkInfo:
                                        os.startfile(self.logpath + "\\" + f1)
                                        var = raw_input("-Manual Analysis Needed- Test ID -[%s]\n %s \n\n-Enter the result [P or F]: " % (tID, mchkInfo))
                                        t1.result = var
                                        if var != "P":
                                            t1.nFail = int(t1.nFail) + 1
                                            t1.result = "FAILED (%s)" % mchkInfo
                                            break

                                    else:
                                        t1.nErr = int(t1.nErr) + 1
                                        break

                                if var == "P":
                                    t1.nPass = int(t1.nPass) + 1
                                    t1.result = "PASS"

                            t1.totalRun = t1.totalRun + 1

                            t1.logfiles.append(LogFile(lf, sig))

        testList = getNodeHandle(self.TestCriteriaFile, ["ConfigSets", self.ProgramName, "TestCases", "TestCase"], "", 1)
        for t in testList:
            listEmpty = 1
            for tList in self.TestCases:
                iTestFound = 0
                listEmpty = 0
                tID = self.getNodeValue(t, "TestCase", "TestID")
                tID = tID.strip()

                if tID == tList.testID:
                    logging.debug("----------------- MATCH[%s][%s]" % (tList.testID, tID))
                    printNode(t)
                    iTestFound = 1
                    tList.type = self.getNodeValue(t, "TestCase", "Type")
                    if tList.type == "Optional":
                        tList.optionalFeature = self.getNodeValue(t, "TestCase", "OptionalFeature")

                    tList.AddXMLNode(self.doc, self.TestResults)
                    break

            # If results not found for the test, mark it missing
            if not listEmpty and not iTestFound:
                tMissing = TestCase(tID, "NO Results Found")
                tMissing.type = self.getNodeValue(t, "TestCase", "Type")
                if tMissing.type == "Optional":
                    tMissing.optionalFeature = self.getNodeValue(t, "TestCase", "OptionalFeature")
                tMissing.AddXMLNode(self.doc, self.TestResults)

    def getAttrValue(self, node, tag, attr):
        """Return the first attribute value match"""
        for n in node.getElementsByTagName(tag):
            return n.attributes[attr].value

    def getNodeValue(self, node, tag, attr):
        """Return the first node value match"""
        for l in node.getElementsByTagName(attr):
            logging.debug(l)
            return l.firstChild.data

    def getNodeValueloop(self, node, tag, attr):
        """Return the first node value match"""
        val = ""
        for l in node.getElementsByTagName(attr):
            logging.debug(l)
            if val:
                val = "%s,%s" % (val, l.firstChild.data)
            else:
                val = "%s" % (l.firstChild.data)

        return val

    def getTBDeviceObject(self, node):
        """Convert all string to lower case for comparison"""
        lList = {}

        for n in node.getElementsByTagName("*"):
            if n.tagName:
                if hasattr(n.firstChild, 'data'):
                    lList.setdefault(n.tagName.strip(), n.firstChild.data.strip())
                else:
                    lList.setdefault(n.tagName.strip(), "")

        return TBDevice(lList["Vendor"], lList["Model"], lList["Driver"], lList["OS"], lList["WTSControlAgent"])

    def compareTwoTBNodes(self, left, right):
        logging.debug("TLeft = %s TRight = %s" % (left, right))

        lTBDevice = self.getTBDeviceObject(left)
        for r in right:
            rightList = getNodeHandle(r, ["ConfigSet", "Testbed", "Device"], r, 1)
            for d in rightList:
                rTBDevice = self.getTBDeviceObject(d)
                lTBDevice.compare(rTBDevice)
                if lTBDevice.verified: break
            if lTBDevice.verified: break

        if lTBDevice.verified == 0:
            logging.debug(lTBDevice.msg)

        return lTBDevice

    def writeXML(self):
        fXMLFile = open(self.fileName, "w")
        fXMLFile.write(self.doc.toprettyxml(indent=" "))
        fXMLFile.close()
        return

class ResultValidation:
    def __init__(self, SummaryFile, TestCriteriaFile, ProgramName):
        self.SummaryFile = xml.dom.minidom.parse(SummaryFile)
        self.TestCriteriaFile = xml.dom.minidom.parse(TestCriteriaFile)
        self.ProgramName = ProgramName
        self.result = "PASS"
        self.msgs = ["msg1", "msg2"]

        logging.debug("Summary File = %s TestCriteriaFile = %s" %(SummaryFile, TestCriteriaFile))

def printNode(node):
    for n in node.getElementsByTagName("*"):
        logging.debug("[%s]=[%s] " % (n.tagName.strip(), n.firstChild.data.strip()))

def _get_elements_by_tagName_helper(parent, name, rc):
    for node in parent.childNodes:
        if node.nodeType == Node.ELEMENT_NODE and (name == "*" or node.tagName == name):
            rc.append(node)

    return rc


def getNodeHandle(node, tagList, string="", lst=0):
    """Get the node handle for given depth of node name in tagList"""
    TagCounter = 0
    nHandle = []
    if string:
        node = xml.dom.minidom.parseString(string.toprettyxml(indent=" "))

    while TagCounter < len(tagList):
        nHandle = []
        _get_elements_by_tagName_helper(node, tagList[TagCounter], nHandle)

        logging.debug("Level = %s Element = %s Search %s"  % (TagCounter, nHandle, tagList[TagCounter]))
        if nHandle == []:
            break
        else:
            node = nHandle[0]

        TagCounter = TagCounter + 1

    #return the first match
    if nHandle:
        if lst:
            return nHandle
        else:
            return nHandle[0]
    return nHandle

def init_logging(_filename):
    logging.basicConfig(level=logging.DEBUG,
                        format='%(message)s',
                        datefmt='%a, %d %b %Y %H:%M:%S',
                        filename=_filename,
                        filemode='w')
    console = logging.StreamHandler()

    console.setLevel(logging.INFO)

    formatter = logging.Formatter('%(message)s')
    console.setFormatter(formatter)
    # add the handler to the root logger
    logging.getLogger('').addHandler(console)

    logging.info("###########################################################\n")
    logging.info('Debug log in file - %s' % (_filename))

def ReadMapFile(filename, index, delim, n=1):
    iCount = 1
    returnString = -1
    if os.path.exists(filename) == 0:
        print "File not found -%s-" % filename
        return -1
    fileP = open(filename, 'r')
    for l in fileP.readlines():
        if not l: break
        line = l.split('#')
        command = line[0].split(delim)
        if index in command:
            returnString = command[command.index(index)+n].strip()
            break

    fileP.close()
    return returnString

ACC = ""
def main():
    try:
        global ACC

        if nargs < 2:
            print "\n\rUSAGE : ResultSummary <Result Summary Config File> \n\r Result Summary Config File : See Sample Config File - WTS-ResultSummary.conf"
            return

        f = ReadMapFile(sys.argv[1], "OUTPUT_FILE", "=")
        if not f.endswith(".xml"):
            f = "%s.xml" % f

        sm = ReadMapFile(sys.argv[1], "M_CHECK", '=')
        log_path = ReadMapFile(sys.argv[1], "LOG_PATH", '=')

        if not log_path.endswith("/"):
            log_path = "%s/" % log_path

        prog_name = ReadMapFile(sys.argv[1], "PROG_NAME", '=')
        UID = ReadMapFile(sys.argv[1], "UID", '=')
        test_criteria = ReadMapFile(sys.argv[1], "TEST_CRITERIA_FILE", '=')
        format_style = ReadMapFile(sys.argv[1], "FORMAT_STYLE", '=')

        init_logging("debug-log-result-summary.txt")
        logging.info("LOG_PATH [%s] PROG_NAME [%s] UID [%s] OUTPUT_FILE [%s] TEST_CRITERIA_FILE [%s] FORMAT_STYLE [%s]"  % (log_path, prog_name, UID, f, test_criteria, format_style))

        if prog_name == '60G':
            prog_name = "_" + prog_name
        rSummary = ResultSummary(f, test_criteria, prog_name, UID, log_path, sm, format_style)

        signVerification = ReadMapFile(sys.argv[1], "SIGN_VERIFICATION", '=')

        # Enable Signature verification - by default it is disabled
        if signVerification == "1":
            setattr(rSummary, "SignVerification", 1)
            acc_ip = ReadMapFile(sys.argv[1], "ACC_IP_ADDR", '=')
            acc_port = ReadMapFile(sys.argv[1], "ACC_IP_PORT", '=')
            ACC = ACCClient(acc_ip, acc_port)
            #unit test with hard coded file name

        rSummary.generateSummary()
        rSummary.writeXML()

    except StandardError:
        err = sys.exc_info()
        logging.error("End %s" % err[1])

## Main
nargs = len(sys.argv)
if __name__ == "__main__":
    main()