import os, inspect, errno
import tempfile
import re
import subprocess
import sys

#windows platform specfic include
if re.match('^(linux|sunos)', sys.platform):
    windows = False
else:
    windows = True
    import cygpathHandler

def processCommand(comCommand):
        pCommand= comCommand
        tmpdir = tempfile.gettempdir()
        tmpText = os.path.join (tmpdir, "QAtempCommFile.txt")
        try:
                f = open(tmpText, 'w')
                cmdProcess = subprocess.Popen(pCommand, stdin = subprocess.PIPE, stdout = f, stderr = subprocess.STDOUT)
                cmdProcess.communicate()
                f.close()
                if cmdProcess.returncode != 0:
                    raise Exception("Command failed")
        except:
                assert False, "Failed to execute command '%s'" % pCommand                

# Function to validate id compiler command is valid
def comandValidate(compilercmd):
        args = '-v'
        str = ' '
        seq = (compilercmd,args)
        cmd = str.join(seq)
        process = subprocess.Popen(cmd, shell=True, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
        process.communicate()
        exit_code = process.wait()
        return exit_code

#Function to get which binary
def whichComp(comExe):
        if comandValidate(comExe) == 0:
                return comExe

def orderForceIncludes(forceIncludeList):
    """
        There are dependencies between force included headers. We need to include prlgcc.h or prlg++.h first,
        then headers containing built-in function and then the rest.
    """
    forceIncludeList.sort() # alphabetical order is the ground
    
    def indexEndsWith(lst, pattern):
        for i, v in enumerate(lst):
            if v.strip().strip("\"'").endswith(pattern):
                return i
        return -1

    def moveToTop(lst, pattern):
        i = indexEndsWith(lst, pattern)
        if i == -1:
            return
        item = lst.pop(i)
        lst.insert(0, item)
    
    moveToTop(forceIncludeList, "builtins_all.h")
    moveToTop(forceIncludeList, "qacpp_builtin.h")
    moveToTop(forceIncludeList, "prlg++.h")
    moveToTop(forceIncludeList, "prlgcc.h") # this ends up at the top
    

def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:  # Python >2.5
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

def scanIncludePathForVersion(incPath):
    glibcxx_re = r'.*\\([0-9])\.([0-9])\.([0-9])\\include\\c\+\+' if windows else r'.*/include/c\+\+/([0-9])\.([0-9])\.([0-9])'
    prl_glibcxx = None
    match = re.match (glibcxx_re, incPath)
    if match:
        prl_glibcxx = { '__PRL_GLIBCXX__': match.group(1),
                        '__PRL_GLIBCXX_MINOR__': match.group(2),
                        '__PRL_GLIBCXX_PATCHLEVEL__': match.group(3) }
    return prl_glibcxx

            
# Search for System include and add them to cip file
def outCommand(absScriptName,
               systemIncludeOptionName,
               systemDefineOptionName,
               executableName,
               alternativeExecutableName,
               additionalArgs):
        currentDir = os.path.dirname(absScriptName)
        fileName = os.path.basename(absScriptName);

        tmpDir = tempfile.gettempdir()
        tmpText = os.path.join (tmpDir, "QAtempCommFile.txt")
        tmpSrc = os.path.join (tmpDir, "QAtempTest.c")
        
        cipFilename = os.path.splitext(fileName.strip())[0] + '.cip'
        normalParent = os.path.dirname(currentDir)
        dataDir = os.path.dirname(normalParent)
        cctDir = os.path.dirname(dataDir)
        configDir = os.path.dirname(cctDir)
        cipDir = os.path.join(configDir, "cip")
        mkdir_p (cipDir);
        cipFilepath = os.path.join(cipDir, cipFilename)
        stubDir = os.path.join(normalParent, "Stub")
        cipFile = open(cipFilepath, 'w')
        if not os.path.exists(tmpSrc):
                tfile = open(tmpSrc, 'w')
                tfile.close()

        # Check if Compiler Executable is available
        comExist = True
        compiler = whichComp(executableName)
        if compiler is None:
                comExist = False
        if not comExist:
                compiler = whichComp(alternativeExecutableName)
                comExist = True
        #if compiler executable not found then create cip file with information message
        if compiler is None:
                cipFile.write("* Error: Compiler Not Found \n* please contact support@programmingresearch.com")
                return compiler
            
        #if compiler executable found then continue
        commandLine = [ compiler, '-Wp,-v,-dM', '-E', tmpSrc ];
        if additionalArgs:
            commandLine.extend (additionalArgs)

        processCommand( commandLine )

        try:
                fopen = open(tmpText)
                #
                # Hard coded Handling for
                #    o -forceinclude/-q forceinclude path
                #    o -i/-q stub_header_path
                #
                # QAF-1_0_5: To match prlgcc behavior, stub
                # directories are added based on the following
                # algorithm:
                # 
                #   o A non empty top level Stub directory is added
                #     (eg. gcc)
                #
                #   o A nested directory that itself has children
                #     directories is added.
                #
                #   o Children of added parents won't themselves be
                #     added.
                #     
                forceIncludeList = []
                addedDirectories = []
                for dirName,dirnames,filenames in os.walk(stubDir, topdown=True):
                        if stubDir != dirName:
                                if dirName.endswith("prlforceinclude"):
                                    cipFile.write('-q "%s"\n' % dirName)
                                    for headerFile in filenames: # add all header files
                                        forceIncludeList.append('-fi "%s"\n' % os.path.join(dirName, headerFile))
                                else:
                                        shortName = dirName[len(stubDir):]
                                        sepCount = shortName.count(os.sep)
                                        addDirectory = False
                                        if sepCount == 1 and len(filenames) != 0:
                                            addDirectory = True
                                        elif sepCount > 1 and len(dirnames) != 0:
                                            addDirectory = True
                                            for addedDirectory in addedDirectories:
                                                if shortName.startswith (addedDirectory):
                                                    addDirectory = False

                                        if addDirectory:
                                                cipFile.write('-i "%s"\n' % dirName)
                                                cipFile.write('-q "%s"\n' % dirName)
                                                addedDirectories.append (shortName)

                orderForceIncludes(forceIncludeList)
                for i in forceIncludeList:
                        cipFile.write(i)


                prl_glibcxx = None
                includes = False
                for line in fopen.readlines():
                        line = line.strip()
                        if re.match(r'.*search starts here.$', line, re.IGNORECASE|re.U):
                                includes = True
                        elif re.match(r'^End of search list.$', line, re.IGNORECASE|re.U):
                                includes = False 
                        elif includes:
                                incPath = os.path.normcase(line)
                                if windows:
                                    if re.match(r'.*\\usr\\lib.*', incPath, re.IGNORECASE | re.U) or \
                                       re.match(r'.*\\usr\\include.*', incPath, re.IGNORECASE | re.U):
                                        incPath = cygpathHandler.ch_path(incPath)
                                cipFile.write('-%s "%s"\n' % (systemIncludeOptionName, incPath))
                                cipFile.write('-q  "%s"\n' % incPath)

                                if prl_glibcxx == None:
                                    prl_glibcxx = scanIncludePathForVersion(incPath)
                                        

                        # Search for defines that help select path through headers
                        special_macros = [ '__GNUC_PATCHLEVEL__', '__GNUC_RH_RELEASE__' ]
                        for macro in special_macros:
                            m = re.match('#define\s*(%s)\s*([0-9]*)' % macro, line)
                            if m:
                                cipFile.write('\n* The detected %s.\n' % macro)
                                cipFile.write('-%s "%s=%s"\n' % (systemDefineOptionName, m.group(1), m.group(2)))

                        # Search for the __VERSION__ as part of creating the CIP
                        m = re.match(r'#define\s*(__VERSION__)\s+"(.*)"[^"]*$', line)
                        if m:
                            cipFile.write('\n* The detected __VERSION__ string.\n')
                            cipFile.write('-%s "%s=_PRL_STRINGIFY(%s)"\n' % (systemDefineOptionName, m.group(1), m.group(2)))

                if prl_glibcxx != None:
                    cipFile.write('\n* The detected version of the GLIBCXX headers.\n');
                    for key in prl_glibcxx:
                        cipFile.write('-%s %s=%s\n' % ( systemDefineOptionName, key, prl_glibcxx[key]))
                        
                                
                fopen.close()
                cipFile.close()
        except IOError: pass 

def removeTemp():
        tmpDir = tempfile.gettempdir()
        tmpText = os.path.join (tmpDir, "QAtempCommFile.txt")
        tmpSrc= os.path.join (tmpDir, "QAtempTest.c")
        if os.path.exists(tmpText):
                os.remove(tmpText)
        if os.path.exists(tmpSrc):
                os.remove(tmpSrc)

def main(absScriptName,
         systemIncludeOptionName,
         systemDefineOptionName,
         executableName,
         alternativeExecutableName,
         additionalArgs):
        """
            systemIncludeOptionName - "i" for qac, "si" for qacpp
            systemDefineOptionName - "d" for qac, "sd" for qacpp
            executableName - e.g. "gcc"
            alternativeExecutableName - e.g. "gcc47"
            additionalArgs - e.g. "['-Wall']"
        """
        if not isinstance(additionalArgs, list):
            # Legacy scripts may still pass arguments as strings
            additionalArgs = additionalArgs.split()
            
        outCommand (absScriptName,
                    systemIncludeOptionName,
                    systemDefineOptionName,
                    executableName,
                    alternativeExecutableName,
                    additionalArgs)
        removeTemp()
