import re
import platform
from StringUtils import StringUtils
from SpecStructures import *
from constants import constants

class SpecParser(object):

    def __init__(self):
        self.cleanMacro=rpmMacro().setName("clean")
        self.prepMacro=rpmMacro().setName("prep")
        self.buildMacro=rpmMacro().setName("build")
        self.installMacro=rpmMacro().setName("install")
        self.changelogMacro=rpmMacro().setName("changelog")
        self.checkMacro=rpmMacro().setName("check")
        self.packages={}
        self.specAdditionalContent=""
        self.globalSecurityHardening=""
        self.defs={}
        self.conditionalCheckMacroEnabled = False
        self.macro_pattern = re.compile(r'%{(\S+?)\}')


    def readPkgNameFromPackageMacro(self,data,basePkgName=None):
        data=" ".join(data.split())
        pkgHeaderName=data.split(" ")
        lenpkgHeaderName = len(pkgHeaderName)
        i=1;
        pkgName = None
        while i<lenpkgHeaderName:
            if pkgHeaderName[i] == "-n" and i+1 < lenpkgHeaderName:
                pkgName = pkgHeaderName[i+1]
                break
            if pkgHeaderName[i].startswith('-'):
                i = i + 2
            else:
                pkgName = basePkgName+"-"+pkgHeaderName[i]
                break
        if pkgName is None:
            return True, basePkgName
        return True, pkgName

    def replaceMacros(self, string):
        """Replace all macros in given string with corresponding values.

        For example: a string '%{name}-%{version}.tar.gz' will be transformed to 'foo-2.0.tar.gz'.

        :return A string where all macros in given input are substituted as good as possible.

        """
        def _is_conditional(macro):
            return macro.startswith("?") or macro.startswith("!")

        def _test_conditional(macro):
            if macro[0] == "?":
                return True
            if macro[0] == "!":
                return False
            raise Exception("Given string is not a conditional macro")

        def _is_macro_defined(macro):
            return (macro in self.defs.keys()) or (macro in constants.userDefinedMacros.keys())

        def _get_macro(macro):
            if macro in self.defs.keys():
                return self.defs[macro]
            elif macro in constants.userDefinedMacros.keys():
                return constants.userDefinedMacros[macro]
            raise Exception("Unknown macro: " + macro)

        def _macro_repl(match):
            macro_name = match.group(1)
            if _is_conditional(macro_name):
                parts = macro_name[1:].split(":")
                assert len(parts) > 0
                if _test_conditional(macro_name):  # ?
                    if _is_macro_defined(parts[0]):
                        if len(parts) == 2:
                            return parts[1]
                        else:
                            return _get_macro(parts[0])
                    else:
                        return ""
                else:  # !
                    if not _is_macro_defined(parts[0]):
                        if len(parts) == 2:
                            return parts[1]
                    return ""

            if _is_macro_defined(macro_name):
                return _get_macro(macro_name)
            return match.string[match.start():match.end()]

        #User macros
        for macroName in constants.userDefinedMacros.keys():
            value = constants.userDefinedMacros[macroName]
            macro="%"+macroName
            if string.find(macro) != -1:
                string = string.replace(macro,value)
        #Spec definitions
        for macroName in self.defs.keys():
            value = self.defs[macroName]
            macro="%"+macroName
            if string.find(macro) != -1:
                string = string.replace(macro,value)
        return re.sub(self.macro_pattern, _macro_repl, string)

    def parseSpecFile(self,specfile):
        self.createDefaultPackage()
        currentPkg="default"
        specFile = open(specfile)
        lines = specFile.readlines()
        totalLines=len(lines)
        i=0
        while i < totalLines:
            line = lines[i].strip()
            if self.isConditionalArch(line):
                if (platform.machine() != self.readConditionalArch(line)):
                    # skip conditional body
                    deep = 1
                    while (i < totalLines and deep != 0):
                        i=i+1
                        line = lines[i].strip()
                        if self.isConditionalMacroStart(line):
                            deep = deep + 1
                        elif self.isConditionalMacroEnd(line):
                            deep = deep - 1
            elif self.isIfCondition(line):
                if (not self.isConditionTrue(line)):
                    # skip conditional body
                    deep = 1
                    while (i < totalLines and deep != 0):
                        i=i+1
                        line = lines[i].strip()
                        if self.isConditionalMacroStart(line):
                            deep = deep + 1
                        elif self.isConditionalMacroEnd(line):
                            deep = deep - 1
            elif self.isSpecMacro(line):
                macro,i=self.readMacroFromFile(i, lines)
                self.updateMacro(macro)
            elif self.isPackageMacro(line):
                defaultpkg = self.packages.get('default')
                returnVal,packageName=self.readPkgNameFromPackageMacro(line, defaultpkg.name)
                packageName=self.replaceMacros(packageName)
                if not returnVal:
                    return False
                if re.search('^'+'%package',line) :
                    pkg = Package(defaultpkg)
                    pkg.name=packageName
                    currentPkg=packageName
                    self.packages[pkg.name]=pkg
                else:
                    if defaultpkg.name == packageName :
                        packageName = 'default'
                    macro,i=self.readMacroFromFile(i, lines)
                    if not self.packages.has_key(packageName):
                        i=i+1
                        continue
                    self.packages[packageName].updatePackageMacro(macro)
            elif self.isPackageHeaders(line):
                self.readPackageHeaders(line, self.packages[currentPkg])
            elif self.isGlobalSecurityHardening(line):
                self.readSecurityHardening(line)
            elif self.isChecksum(line):
                self.readChecksum(line, self.packages[currentPkg])
            elif self.isDefinition(line):
                self.readDefinition(line)
            elif self.isConditionalCheckMacro(line):
                self.conditionalCheckMacroEnabled = True
            elif self.conditionalCheckMacroEnabled and self.isConditionalMacroEnd(line):
                self.conditionalCheckMacroEnabled = False
            else:
                self.specAdditionalContent+=line+"\n"
            i=i+1
        specFile.close()

    def createDefaultPackage(self):
        pkg = Package()
        self.packages["default"]=pkg

    def readMacroFromFile(self,currentPos,lines):
        macro = rpmMacro()
        line = lines[currentPos]
        macro.position = currentPos
        macro.endposition=currentPos
        endPos=len(lines)
        line = " ".join(line.split())
        flagindex = line.find(" ")
        if flagindex != -1:
            macro.macroFlag=line[flagindex+1:]
            macro.macroName=line[:flagindex]
        else:
            macro.macroName=line

        if currentPos+1 < len(lines) and self.isMacro(lines[currentPos+1]):
            return macro,currentPos

        for j in range(currentPos+1,endPos):
            content = lines[j]
            if j+1 < endPos and self.isMacro(lines[j+1]):
                return macro,j
            macro.content += content +'\n'
            macro.endposition=j
        return macro,endPos


    def updateMacro(self,macro):
        if macro.macroName == "%clean":
            self.cleanMacro=macro
            return True
        if macro.macroName == "%prep":
            self.prepMacro=macro
            return True
        if macro.macroName == "%build":
            self.buildMacro=macro
            return True
        if macro.macroName == "%install":
            self.installMacro=macro
            return True
        if macro.macroName == "%changelog":
            self.changelogMacro=macro
            return True
        if macro.macroName == "%check":
            self.checkMacro=macro
            return True
        return False

    def isMacro(self,line):
        return self.isPackageMacro(line) or self.isSpecMacro(line) or self.isConditionalMacroStart(line) or self.isConditionalMacroEnd(line)

    def isConditionalArch(self,line):
        if re.search('^'+'%ifarch',line) :
            return True
        return False

    def isSpecMacro(self,line):
        if re.search('^'+'%clean',line) :
            return True
        elif re.search('^'+'%prep',line) :
            return True
        elif re.search('^'+'%build',line) :
            return True
        elif re.search('^'+'%install',line) :
            return True
        elif re.search('^'+'%changelog',line) :
            return True
        elif re.search('^'+'%check',line) :
            return True
        return False

    def isPackageMacro(self,line):
        line=line.strip()

        if re.search('^'+'%post',line) :
            return True
        elif re.search('^'+'%postun',line) :
            return True
        elif re.search('^'+'%files',line) :
            return True
        elif re.search('^'+'%description',line) :
            return True
        elif re.search('^'+'%package',line) :
            return True
        return False

    def isPackageHeaders(self,line):
        if re.search('^'+'summary:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'name:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'group:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'license:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'version:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'release:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'distribution:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'requires:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'requires\((pre|post|preun|postun)\):',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'provides:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'obsoletes:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'conflicts:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'url:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'source[0-9]*:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'patch[0-9]*:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'buildrequires:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'buildprovides:',line,flags=re.IGNORECASE) :
            return True
        elif re.search('^'+'buildarch:',line,flags=re.IGNORECASE) :
            return True
        return False

    def isGlobalSecurityHardening(self,line):
        if re.search('^%global *security_hardening',line,flags=re.IGNORECASE) :
            return True
        return False

    def isChecksum(self,line):
        if re.search('^%define *sha1',line,flags=re.IGNORECASE) :
            return True
        return False

    def isDefinition(self,line):
        if re.search('^'+'%define',line) :
            return True
        if re.search('^'+'%global',line) :
            return True
        return False

    def readConditionalArch(self,line):
        w=line.split()
        if len(w) == 2:
           return w[1]
        return None

    def readDefinition(self,line):
        listDefines=line.split()
        if len(listDefines) == 3:
           self.defs[listDefines[1]] = self.replaceMacros(listDefines[2])
           return True
        return False

    def readHeader(self,line):
        headerSplitIndex=line.find(":")
        if(headerSplitIndex+1 == len(line) ):
            print line
            print "Error:Invalid header"
            return False, None,None
        headerName=line[0:headerSplitIndex].lower()
        headerContent=line[headerSplitIndex+1:].strip()
        return True,headerName,headerContent


    def readDependentPackageData(self,line):
        strUtils = StringUtils()
        listPackages=line.split(",")
        listdependentpkgs=[]
        for line in listPackages:
            line=strUtils.getStringInConditionalBrackets(line)
            listContents=line.split()
            totalContents = len(listContents)
            i=0
            while i < totalContents:
                dpkg = dependentPackageData()
                compare=None
                packageName=listContents[i]
                if listContents[i].startswith("/"):
                    provider=constants.providedBy.get(listContents[i], None)
                    i=i+1
                    if provider is not None:
                        packageName=provider
                    else:
                        continue
                if i+2 < len(listContents):
                    if listContents[i+1] == ">=":
                        compare="gte"
                    elif listContents[i+1] == "<=":
                        compare="lte"
                    elif listContents[i+1] == "==":
                        compare="eq"
                    elif listContents[i+1] == "<":
                        compare="lt"
                    elif listContents[i+1] == ">":
                        compare="gt"
                    elif listContents[i+1] == "=":
                        compare="eq"

                if compare is not None:
                    dpkg.package=packageName
                    dpkg.compare=compare
                    dpkg.version=listContents[i+2]
                    i=i+3
                else:
                    dpkg.package=packageName
                    i=i+1
                listdependentpkgs.append(dpkg)
        return listdependentpkgs

    def readPackageHeaders(self,line,pkg):
        returnVal,headerName,headerContent=self.readHeader(line)
        if not returnVal:
            return False

        headerContent=self.replaceMacros(headerContent)
        if headerName == 'summary':
            pkg.summary=headerContent
            return True
        if headerName == 'name':
            pkg.name=headerContent
            if (pkg == self.packages["default"]):
                self.defs["name"] = pkg.name
            return True
        if headerName == 'group':
            pkg.group=headerContent
            return True
        if headerName == 'license':
            pkg.license=headerContent
            return True
        if headerName == 'version':
            pkg.version=headerContent
            if (pkg == self.packages["default"]):
                self.defs["version"] = pkg.version
            return True
        if headerName == 'buildarch':
            pkg.buildarch=headerContent
            return True
        if headerName == 'release':
            pkg.release=headerContent
            if (pkg == self.packages["default"]):
                self.defs["release"] = pkg.release
            return True
        if headerName == 'distribution':
            pkg.distribution=headerContent
            return True
        if headerName == 'url':
            pkg.URL=headerContent
            return True
        if headerName.find('source') != -1:
            pkg.sources.append(headerContent)
            return True
        if headerName.find('patch') != -1:
            pkg.patches.append(headerContent)
            return True
        if headerName.startswith('requires') or headerName == 'provides' or headerName == 'obsoletes' or headerName == 'conflicts' or headerName == 'buildrequires' or headerName == 'buildprovides':
            dpkg=self.readDependentPackageData(headerContent)
            if dpkg is None:
                return False
            if headerName.startswith('requires'):
                pkg.requires.extend(dpkg)
            if headerName == 'provides':
                pkg.provides.extend(dpkg)
            if headerName == 'obsoletes':
                pkg.obsoletes.extend(dpkg)
            if headerName == 'conflicts':
                pkg.conflicts.extend(dpkg)
            if headerName == 'buildrequires':
                if self.conditionalCheckMacroEnabled:
                    pkg.checkbuildrequires.extend(dpkg)
                else:
                    pkg.buildrequires.extend(dpkg)
            if headerName == 'buildprovides':
                pkg.buildprovides.extend(dpkg)

            return True
        return False

    def readSecurityHardening(self,line):
        data = line.lower().strip();
        words=data.split(" ")
        nrWords = len(words)
        if (nrWords != 3):
            print "Error: Unable to parse line: "+line
            return False
        if (words[2] != "none" and words[2] != "nonow" and words[2] != "nopie") :
            print "Error: Invalid security_hardening value: " + words[2]
            return False
        self.globalSecurityHardening = words[2]
        return True;

    def readChecksum(self,line,pkg):
        strUtils = StringUtils()
        line=self.replaceMacros(line)
        data = line.strip();
        words=data.split()
        nrWords = len(words)
        if (nrWords != 3):
            print "Error: Unable to parse line: "+line
            return False
        value=words[2].split("=")
        if (len(value) != 2):
            print "Error: Unable to parse line: "+line
            return False
        matchedSources=[]
        for source in pkg.sources:
            sourceName=strUtils.getFileNameFromURL(source)
            if (sourceName.startswith(value[0])):
                matchedSources.append(sourceName)
        if (len(matchedSources) == 0):
            print "Error: Can not find match for sha1 "+value[0]
            return False
        if (len(matchedSources) > 1):
            print "Error: Too many matched Sources:" + ' '.join(matchedSources) + " for sha1 "+value[0]
            return False
        pkg.checksums[sourceName] = value[1]
        return True;

    def isConditionalCheckMacro(self,line):
        data = line.strip()
        words = data.split()
        nrWords = len(words)
        if(nrWords != 2):
            return False
        if(words[0] != "%if" or words[1] != "%{with_check}"):
            return False
        return True

    def isIfCondition(self,line):
        return line.startswith("%if ")

    # Supports only %if %{}
    def isConditionTrue(self,line):
        data = line.strip()
        words = data.split()
        nrWords = len(words)
        # condition like %if a > b is not supported
        if(nrWords != 2):
            return True
        if (self.replaceMacros(words[1]) == "0"):
            return False
        return True

    def isConditionalMacroStart(self,line):
        return line.startswith("%if")

    def isConditionalMacroEnd(self,line):
        return (line.strip() == "%endif")