'''
Created on 26.01.2016

@author: fabian
'''
import logging
from lxml import etree
from patches.mavenfilepatches import MavenFilePatch
import multiprocessing
from parser.baseparser import BaseParser
import os
import evotest.util
import subprocess
from evotest.error import FileError
import sys

class MavenParser(BaseParser):
    '''
    classdocs
    '''


    def __init__(self):
        '''
        Constructor
        '''
        self.logger = logging.getLogger("parser")
        self.root = None

    @property
    def name(self):
        return "mavenParser"

    def getBuildFile(self, processingCommit):
        """
        Try to find the correct pom in the project.

        If there are more valid poms, that have a surefire-plugin in it, abort.

        :param processingCommit: current commit that is processed
        :return: path to the buildfile
        """

        standardMavenBuildFile = processingCommit.workingDirectory+"/pom.xml"
        if(os.path.exists(standardMavenBuildFile) and
           self.checkIfXMLIsMavenBuildFile(standardMavenBuildFile)):
            self.logger.debug("Found the following build files for mavenParser: "+standardMavenBuildFile)
            return [standardMavenBuildFile]


        potentialBuildFiles = []
        for root, subFolders, files in os.walk(processingCommit.workingDirectory):

            for file in files:
                if file.endswith(".xml"):
                    if(self.checkIfXMLIsMavenBuildFile(os.path.join(root, file))):
                        potentialBuildFiles.append(os.path.join(root, file))

        if potentialBuildFiles:
            self.logger.debug("Found the following build files for mavenParser: "+','.join(potentialBuildFiles))
        return potentialBuildFiles

    def checkIfXMLIsMavenBuildFile(self, path):
        try:
            tree = etree.parse(path)
            root = tree.getroot()
            projectNode = root.xpath('/x:project', namespaces={'x': "http://maven.apache.org/POM/4.0.0"})
            buildNode = root.xpath('/x:project/x:build',
                               namespaces={'x': "http://maven.apache.org/POM/4.0.0"})
            if(projectNode and buildNode):
                return True
            else:
                return False
        except Exception as e:
            return False


    def getNewBuildFile(self, processingCommit):
        return self.buildFileRootPath+"/newBuild.xml"

    def parseErrors(self, processingCommit):
        errors = set([])
        try:
            with open(processingCommit.outputDirectory+"/build_errors.txt", "r") as buildErrorF:
                data = buildErrorF.readlines()
                
            for i in range(0,len(data)):
                line = data[i]
                if(line.startswith("[ERROR] Exit code: 1 - ")):
                    line = line[23:]

                if(line.startswith("[ERROR] ")):
                    line = line[8:]

                parts = line.split(":")
                parts = [part.strip() for part in parts]
                if("error" in line):
                    # Check if exact place is a new error or not
                    exactPlace = data[i+1]
                    checkExactPlace = exactPlace.split(":")
                    if(len(checkExactPlace) == 1):
                        exactPlace = exactPlace[9:].lstrip().rstrip('\r\n')
                    else:
                        exactPlace = ""

                    if(len(parts) == 3):
                        errorMessage = [part.rstrip('\r\n') for part in parts[2:]]
                    else:
                        errorMessage = [part.rstrip('\r\n') for part in parts[3:]]

                    sourceFileLineNumber = parts[1].replace("error", "")
                    sourceFileLineNumber = parts[1].replace("[", "")
                    sourceFileLineNumber = parts[1].replace("]", "")


                    errorMessage = ':'.join(errorMessage)
                    fileError = FileError(sourceFile=parts[0], sourceFileLineNumber=parts[1], errorMessage=errorMessage, exactPlace = exactPlace)
                    errors.add(fileError)   

        except Exception:
            pass

        return errors
                    
    def sanitizeParsedOutput(self, output):
        output = output.replace("[javac]", "")
        output = output.strip()

        return output
 
    def executeClean(self, processingCommit):
        try:
            self.logger.info("Calling mvn clean task")
            output = subprocess.check_output(["mvn", "-f", self.getNewBuildFile(processingCommit), "clean"], stderr=subprocess.STDOUT)
        except Exception as e:
            logging.error("Error calling clean: "+str(e))

    def executeCoverageAndMutationTask(self, processingCommit):
        try:  
            self.logger.info("Calling mvn prepare-package task, output is written to: "+processingCommit.outputDirectory+'/build_out.txt')
            output = subprocess.check_output(["mvn", "-f", self.getNewBuildFile(processingCommit), "prepare-package"], stderr=subprocess.STDOUT)
            evotest.util.writeCommandOutputToFile(processingCommit.outputDirectory+'/build_out.txt', output)
            return True

        except subprocess.CalledProcessError as e:
            self.logger.error("Error for calling prepare-package! Output is written to: "+processingCommit.outputDirectory+'/build_errors.txt')
            evotest.util.writeCommandOutputToFile(processingCommit.outputDirectory+'/build_errors.txt', e.output)
            return False

    def isApplicable(self, processingCommit):
        buildFiles = self.getBuildFile(processingCommit)
        if(len(buildFiles) != 1):
            self.logger.debug("Number of potential build files for mavenParser is not equal to 1!")
            return False


        # Parse XML
        try:
            self.tree = etree.parse(buildFiles[0])
        except etree.XMLSyntaxError as e:
            self.logger.error("Could not parse xml file!")
            return False

        self.buildFileRootPath = os.path.dirname(os.path.abspath(buildFiles[0]))
        
        # Get root node
        self.root = self.tree.getroot()

        # Generate new build file
        self.generateOutputFile(processingCommit)

        return True
    
    def createNewBuildFile(self, processingCommit):
        self.addJacocoPlugin()
        self.addPitPlugin(processingCommit)
        self.generateOutputFile(processingCommit)

        self.executeMavenPatches(processingCommit)
        

    def executeMavenPatches(self, processingCommit):
        for sc in MavenFilePatch.__subclasses__():
            patch = sc(processingCommit)
            if(patch.isApplicable()):
                self.logger.info("Apply maven patch: "+patch.name)
                patch.execute()
                processingCommit.addBuildFilePatch(patch.name)

    def addJacocoPlugin(self):
        # If there is already an jacocoplugin configured, remove it
        jacocoPlugin = self.root.xpath('/x:project/x:build/x:plugins/x:plugin[x:artifactId[text()="jacoco-maven-plugin"]]', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})
        if(jacocoPlugin):
            jacocoPlugin[0].getparent().remove(jacocoPlugin[0])
        
        
        pluginsNode = self.root.xpath('/x:project/x:build/x:plugins', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})
        pluginNode = etree.SubElement(pluginsNode[0], "plugin")
        groupId = etree.SubElement(pluginNode, "groupId")
        groupId.text = "org.jacoco"
        artifactId = etree.SubElement(pluginNode, "artifactId")
        artifactId.text= "jacoco-maven-plugin"
        version = etree.SubElement(pluginNode, "version")
        version.text="0.7.5.201505241946"
        executions = etree.SubElement(pluginNode, "executions")
        execution1 = etree.SubElement(executions, "execution")
        execution1_id = etree.SubElement(execution1, "id")
        execution1_id.text = "default-prepare-agent"
        execution1_phase = etree.SubElement(execution1, "phase")
        execution1_phase.text = "initialize"
        execution1_goals = etree.SubElement(execution1, "goals")
        execution1_goal = etree.SubElement(execution1_goals, "goal")
        execution1_goal.text = "prepare-agent"


        execution2 = etree.SubElement(executions, "execution")
        execution2_id = etree.SubElement(execution2, "id")
        execution2_id.text = "default-report"
        execution2_phase = etree.SubElement(execution2, "phase")
        execution2_phase.text = "prepare-package"
        execution2_goals = etree.SubElement(execution2, "goals")
        execution2_goal = etree.SubElement(execution2_goals, "goal")
        execution2_goal.text = "report"
        execution2_configuration = etree.SubElement(execution2, "configuration")
        execution2_outputDirectory = etree.SubElement(execution2_configuration, "outputDirectory")
        execution2_outputDirectory.text = "report"
        
        surefirePlugin = self.root.xpath('/x:project/x:build/x:plugins/x:plugin[x:artifactId[text()="maven-surefire-plugin"]]', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})

        if(surefirePlugin):
            surefirePlugin_jacoco = surefirePlugin[0].xpath('x:configuration/x:systemPropertyVariables/x:jacoco-agent.destfile',  namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})

            if(surefirePlugin_jacoco):
                surefirePlugin_jacoco[0].text = "${project.build.directory}/jacoco.exec"
            else:
                # add elements if they are not existent (e.g. configuration, systempropertyvariables and so on)
                surefirePlugin_configuration = surefirePlugin[0].xpath('x:configuration', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"} )
                if(not surefirePlugin_configuration):
                    surefirePlugin_configuration = [etree.SubElement(surefirePlugin, "configuration")]

                surefirePlugin_systemPropertyVariables = surefirePlugin_configuration[0].xpath('x:systemPropertyVariables', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"} )
                if(not surefirePlugin_systemPropertyVariables):
                    surefirePlugin_systemPropertyVariables = [etree.SubElement(surefirePlugin_configuration[0], "systemPropertyVariables")]

                surefirePlugin_jacoco = etree.SubElement(surefirePlugin_systemPropertyVariables[0], "jacoco-agent.destfile")
                surefirePlugin_jacoco.text = "${project.build.directory}/jacoco.exec"

                
        

    
    def addPitPlugin(self, processingCommit):
        # If there is already an pitplugin configured, remove it
        pitPlugin = self.root.xpath('/x:project/x:build/x:plugins/x:plugin[x:artifactId[text()="pitest-maven"]]', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})
        if(pitPlugin):
            pitPlugin[0].getparent().remove(pitPlugin[0])
            
        pluginsNode = self.root.xpath('/x:project/x:build/x:plugins', namespaces={'x' : "http://maven.apache.org/POM/4.0.0"})
        pluginNode = etree.SubElement(pluginsNode[0], "plugin")
        groupId = etree.SubElement(pluginNode, "groupId")
        groupId.text = "org.pitest"
        artifactId = etree.SubElement(pluginNode, "artifactId")
        artifactId.text= "pitest-maven"
        version = etree.SubElement(pluginNode, "version")
        version.text="1.1.9"
        executions = etree.SubElement(pluginNode, "executions")
        execution1 = etree.SubElement(executions, "execution")
        execution1_id = etree.SubElement(execution1, "id")
        execution1_id.text = "mutationCoverage goal"
        execution1_phase = etree.SubElement(execution1, "phase")
        execution1_phase.text = "prepare-package"
        execution1_goals = etree.SubElement(execution1, "goals")
        execution1_goal = etree.SubElement(execution1_goals, "goal")
        execution1_goal.text = "mutationCoverage"
        
        
        execution1_configuration = etree.SubElement(execution1, "configuration")
        execution1_targetClasses = etree.SubElement(execution1_configuration, "targetClasses")
        execution1_targetTests = etree.SubElement(execution1_configuration, "targetTests")
        
        targetTests, targetClasses = self.determineTargetTestsAndClasses(processingCommit.workingDirectory)
        
        for targetClass in targetClasses:
            param = etree.SubElement(execution1_targetClasses, "param")
            param.text=targetClass
        
        for targetTest in targetTests:
            param = etree.SubElement(execution1_targetTests, "param")
            param.text=targetTest

        execution1_outputFormats = etree.SubElement(execution1_configuration, "outputFormats")
        execution1_outputFormatsParam = etree.SubElement(execution1_outputFormats, "param")
        execution1_outputFormatsParam.text = "html"
        execution1_outputFormatsParam2 = etree.SubElement(execution1_outputFormats, "param")
        execution1_outputFormatsParam2.text = "csv"
        execution1_outputFormatsParam3 = etree.SubElement(execution1_outputFormats, "param")
        execution1_outputFormatsParam3.text = "xml"
        
        
        execution1_maxMutationsPerClass = etree.SubElement(execution1_configuration, "maxMutationsPerClass")
        execution1_maxMutationsPerClass.text = "30"
        
        execution1_threads = etree.SubElement(execution1_configuration, "threads")