#!/usr/bin/python3

#Python3 compilation library
#Aaron M. Schinder
#29 Dec 2020
#
#Cleanup and refactor from 2017 python2 version compilation libraries

import os,sys,math,subprocess

#####################
#Directory Functions#
#####################

##flist - list all files in a given directory pth
##optional arguments:
#   recurse - (T/F): Whether to recursively search for files in directory tree
#   exts - (list): A list of file extensions to filter on
#   normpath (T/F): whether to normalize path variables after
#filelist = flist(pth,**kwargs): 
def flist(pth,**kwargs): 
    flst = []
    if(not('recurse' in kwargs)):
        recurse_ = False
    else:
        recurse_ = kwargs['recurse']
    if(not('exts' in kwargs)):
        filterexts_ = False
    else:
        filterexts_ = True
        exts = kwargs['exts']
    if(not('normpath' in kwargs)):
        normpath_ = True
    else:
        normpath_ = kwargs['normpath']
    if(not('linuxpath' in kwargs)):
        linuxpath_ = False
    else:
        linuxpath_ = kwargs['linuxpath']
    if(not('followlinks' in kwargs)):
        followlinks_ = False
    else:
        followlinks_ = kwargs['followlinks']

    dirlist = []
    rawlist = os.listdir(pth)

    for F in rawlist:
        F2 = os.path.join(pth,F)
        if(os.path.isdir(F2)):
            b = (followlinks_) or ((not followlinks_) and not(os.path.islink(F2)))
            if(b):
                if((F2!=".")&(F2!="..")):
                    dirlist.append(F2)
        elif(os.path.isfile(F2)):
            flst.append(F2)

    #Recurse through directories
    if(recurse_):
        for D in dirlist:
            lst = flist(D,**kwargs)
            for L in lst:
                flst.append(L)

    #Postprocess:
    #Filter out all extensions except the selected ext list
    if(filterexts_):
        flst = filterexts(flst,exts)

    #Normalize filename path according to os
    if(normpath_):
        flst2 = list(flst)
        for I in range(0,len(flst2)):
            flst[I] = os.path.normpath(flst2[I])

    #If linuxpath, convert all \\ to /
    #if(linuxpath_):
    #    flst2 = list(flst)
    #    for I in range(0,len(flst2)):
    #        flst[I] = linuxpath(flst2[I])
    
    return flst

#Filters by extensions in a list of files
#flst = def filterexts(flst,exts):
def filterexts(flst,exts):
    flst2 = []
    if(isinstance(exts,str)):
        exts = list([exts])
    for F in flst:
        b = False
        for ext in exts:
            if(ext[0]!='.'):
                ext = '.'+ext
            F2 = os.path.splitext(F)
            if(len(F2)>=2):
                ex = F2[1]
                if(len(ex)>0):            
                    if(ex[0]!='.'):
                        ex = '.'+ex
                    if(ex==ext):
                        b = True
        if(b):
            flst2.append(F)
              
    return flst2

#Find a file fname, starting in pth and recursing
#Used for finding library files to link
def findfile(fname,pth,**kwargs):
    fullfname = ""
    flst = flist(pth,recurse=True)
    for F in flst:
        F2 = os.path.split(F)[1]
        if(F2 == fname):
            fullfname = F
    
    return fullfname

#List to space-seperated-string
def list_to_sss(lst):
    lout = ""
    for I in range(0,len(lst)-1):
        lout = lout + lst[I] + " "
    if(len(lst)>0):
        lout = lout + lst[len(lst)-1]
    return lout

def strip_whitespace(strin):
    strout = ""
    I1 = -1
    I2 = -1
    for I in range(0,len(strin)):
        if(strin[I]!=' ' and strin[I]!='\t' and strin[I]!='\r'and strin[I]!='\n'):
            I1 = I
            break
    q = list(range(0,len(strin)))
    q.reverse()
    for I in q:
        if(strin[I]!=' ' and strin[I]!='\t' and strin[I]!='\r'and strin[I]!='\n'):
            I2 = I+1
            break
    if(I1>=0 and I2>=0):
        strout = strin[I1:I2]
    return strout

def sss_to_list(sss):
    lout = []
    l1 = sss.split(' ')
    for l in l1:
        l2 = strip_whitespace(l)
        lout.append(l2)
    return lout


def replaceext(fname,ext):
    fname2 = ""
    if(len(ext)>0):
        if(ext[0]!='.'):
            ext = '.'+ext
        fname2 = os.path.splitext(fname)[0]+ext
    else:
        fname2 = os.path.splitext(fname)[0]
    return fname2

def replaceexts(fnamelist,ext):
    fname2list = []
    for F in fnamelist:
        F2 = replaceext(F,ext)
        fname2list.append(F2)
    return fname2list

# def except_contains_oldv(lst1,exc):
#     lst2 = []
#     for item in lst1:
#         b = 1
#         for item2 in exc:
#             if(item.find(item2)>=0):
#                 b = 0
#                 break
#         if(b==1):
#             lst2.append(item)
#     return lst2

#filenames must match
def except_contains(lst1,exc):
    lst2 = []
    for item in lst1:
        b = 1
        for item2 in exc:
            fsplit = os.path.split(item)
            fn = fsplit[len(fsplit)-1]
            if(fn==item2):
                b = 0
                break
        if(b==1):
            lst2.append(item)
    return lst2

##########################
##System Call Procedures##
##########################

def callproc(cmd, **kwargs):
    if(not('logfile' in kwargs)):
        use_lf = False
    else:
        logfile = kwargs['logfile']
        if(logfile!=""):
            fp = open(kwargs['logfile'],'a+')
            use_lf = True
        else:
            use_lf = False
        
    if(not('echo' in kwargs)):
        echo = True
    else:
        echo = kwargs['echo']

    if(echo):
        print(cmd)

    #encoding/deconding to/from bytes is necessary to use the subprocess command
    #in python3.7
    #However, only do this in linux
    if(sys.platform!='win32'):
        cmd2 = cmd.encode(encoding='utf-8')
    else:
        cmd2 = cmd
    proc = subprocess.Popen(cmd2,stderr = subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
    (out, err) = proc.communicate()

    out = out.decode(encoding='utf-8')
    
    if(echo):
        print(out)
        #print(err);
    if(use_lf):
        fp.writelines(cmd+'\n')
        fp.writelines(out+'\n')

    if(use_lf):
        fp.close()

#######################################
##Compiler, Archive, and Linker Calls##
#######################################

def smartcompile(srcfile,objext='.o'):
    mtsrc = os.path.getmtime(srcfile)
    objfile = replaceext(srcfile,objext)
    objexists = os.path.exists(objfile)
    ret = True
    if(objexists):
        mtobj = os.path.getmtime(objfile)
        if(mtobj>=mtsrc):
            ret = False
    
    return ret

#MSVC compiler wrapper
def msvc_compile(compilername, srcfile, **kwargs):

    if(not('include' in kwargs)):
        include = ''
    else:
        include = kwargs['include']
        if(isinstance(include,list)):
            include  = list_to_sss(include)
    
    if(not('flags' in kwargs)):
        flags = ''
    else:
        flags = kwargs['flags']
        if(isinstance(flags,list)):
            flags = list_to_sss(flags)

    if(not('objext' in kwargs)):
        objext = '.obj'
    else:
        objext = kwargs['objext']
    
    if(not('srcfileflag' in kwargs)):
        srcfileflag = '/c'
    else:
        srcfileflag = kwargs['srcfileflag']

    if(not('outfileflag' in kwargs)):
        outfileflag = '/Fo:'
    else:
        outfileflag = kwargs['outfileflag']
    if(not('logfile' in kwargs)):
        logfile = ""
    else:
        logfile = kwargs['logfile']

    outfile = replaceext(srcfile,objext)
    ln = compilername+" "+flags+" "+" "+srcfileflag+" "+srcfile+" "+outfileflag+outfile
    ln = ln + " " + include

    callproc(ln,echo=True,logfile=logfile)

    return

#MSVC compiler wrapper
def msvc_compile_list(compiler,srclist,**kwargs):
    for S in srclist:
        msvc_compile(compiler,S,**kwargs)
    return

#gnu-style compiler compile: Should work with gcc, g++, gfortran
def gs_compile(compiler,srcfile,**kwargs):
    if(not('include' in kwargs)):
        include = ''
    else:
        include = kwargs['include']
        if(isinstance(include,list)):
            include  = list_to_sss(include)
    
    if(not('flags' in kwargs)):
        flags = ''
    else:
        flags = kwargs['flags']
        if(isinstance(flags,list)):
            flags = list_to_sss(flags)

    if(not('objext' in kwargs)):
        objext = '.o'
    else:
        objext = kwargs['objext']

    if(not('srcfileflag' in kwargs)):
        srcfileflag = '-c'
    else:
        srcfileflag = kwargs['srcfileflag']

    if(not('outfileflag' in kwargs)):
        outfileflag = '-o'
    else:
        outfileflag = kwargs['outfileflag']

    if(not('logfile' in kwargs)):
        logfile = ""
    else:
        logfile = kwargs['logfile']

    if(not('smartcompile' in kwargs)):
        _smartcompile = True
    else:
        _smartcompile = kwargs['smartcompile']
    
    #Do I want to make this thing this general?

    if(not(_smartcompile) or smartcompile(srcfile,objext)):
        outfile = replaceext(srcfile,objext)
        ln = compiler+" "+flags+" " + outfileflag+" "+outfile+" "+srcfileflag+" "+srcfile
        ln = ln + " " + include

        callproc(ln,echo=True,logfile=logfile)
            
    return

def gs_compile_list(compiler,srclist,**kwargs):
    for S in srclist:
        gs_compile(compiler,S,**kwargs)
    return

def gs_compile_all(compiler,srcdir,srcexts,**kwargs):
    if(not('recurse' in kwargs)):
        recurse = True
    else:
        recurse = kwargs['recurse']

    srcfils = flist(srcdir,exts=srcexts,recurse=recurse)
    
    for S in srcfils:
        gs_compile(compiler,S,**kwargs)

    return

def gs_link_all(linker,srcpath,target,**kwargs):

    if(not('objext' in kwargs)):
        objext = '.o'
    else:
        objext = kwargs['objext']

    if(not('recurse' in kwargs)):
        recurse = True
    else:
        recurse = kwargs['recurse']


    objfils = flist(srcpath,exts=objext,recurse=recurse)
    oflst = list_to_sss(objfils)
    
    gs_link_list(linker,oflst,target,**kwargs)
    
    return

def gs_link_list(linker,objlist,target,**kwargs):

    if(not('objext' in kwargs)):
        objext = '.o'
    else:
        objext = kwargs['objext']

    if(not('libdir' in kwargs)):
        libdir = ''
    else:
        libdir = kwargs['libdir']

    if(not('staticlibs' in kwargs)):
        staticlibs = ''
    else:
        staticlibs = kwargs['staticlibs']

    if(not('libflags' in kwargs)):
        libflags = ''
    else:
        libflags = kwargs['libflags']

    if(not('linkerflags' in kwargs)):
        linkerflags = ''
    else:
        linkerflags = kwargs['linkerflags']

    if(not('recurse' in kwargs)):
        recurse = True
    else:
        recurse = kwargs['recurse']

    if(not('logfile' in kwargs)):
        logfile = ''
    else:
        logfile = kwargs['logfile']

    ln = linker+" -o "+target+" "+libdir
    ln = ln+" "+objlist+" "+staticlibs+" "+libflags+" "+linkerflags
    
    callproc(ln,logfile=logfile)
    return

def msvc_link_list(objlist,target,**kwargs):

    linker = 'link'

    if(not('objext' in kwargs)):
        objext = '.obj'
    else:
        objext = kwargs['objext']

    if(not('libdir' in kwargs)):
        libdir = ''
    else:
        libdir = kwargs['libdir']

    if(not('staticlibs' in kwargs)):
        staticlibs = ''
    else:
        staticlibs = kwargs['staticlibs']

    if(not('libflags' in kwargs)):
        libflags = ''
    else:
        libflags = kwargs['libflags']

    if(not('linkerflags' in kwargs)):
        linkerflags = ''
    else:
        linkerflags = kwargs['linkerflags']

    if(not('recurse' in kwargs)):
        recurse = True
    else:
        recurse = kwargs['recurse']

    if(not('logfile' in kwargs)):
        logfile = ''
    else:
        logfile = kwargs['logfile']

    ln = linker+" "+libdir
    ln = ln+" "+objlist+" "+staticlibs+" "+linkerflags
    ln = ln+" /out:"+target+" "+libflags
    
    callproc(ln,logfile=logfile)

    return

def ar_all(srcpath,arname,**kwargs):
    if(not('recurse' in kwargs)):
        recurse = True
    else:
        recurse = kwargs['recurse']
    if(not('objext' in kwargs)):
        objext = '.o'
    else:
        objext = kwargs['objext']
    
    objlist = flist(srcpath,exts=objext,recurse=recurse)
    ar_list(objlist,arname,**kwargs)
    
    return

def msvc_lib_list(objlist,arname,**kwargs):
    objlist2 = list_to_sss(objlist)
    
    ln = "lib "+objlist2+" /out:"+arname
    callproc(ln)

    return

def ar_list(objlist,arname,**kwargs):
    objlist2 = list_to_sss(objlist)

    ln = "ar cr "+ arname+" "+objlist2
    callproc(ln)
    
    return

def ar_add_list(objlist,arname,**kwargs):
    objlist2 = list_to_sss(objlist)

    ln = "ar t "+arname+" "+objlist2
    callproc(ln)
    return

##############################
##Derived Compiler Functions##
##############################

def gcc_compile(srcfile,**kwargs):
    compiler = 'gcc'
    kwargs['objext'] = '.o'
    #srcexts = ['.c']
    
    gs_compile(compiler,srcfile,**kwargs)
    
    return

def gcc_compile_all(srcdir,**kwargs):
    compiler = 'gcc'
    kwargs['objext'] = '.o'
    srcexts = ['.c']

    gs_compile_all(compiler,srcdir,srcexts,**kwargs)
    
    return

def gcc_compile_list(srclist,**kwargs):
    compiler = 'gcc'
    kwargs['objext'] = '.o'
    #srcexts = ['.c']

    gs_compile_list(compiler,srclist,**kwargs)

    return

def gpp_compile(srcfile,**kwargs):
    compiler = 'g++'
    kwargs['objext'] = '.o'
    #srcexts = ['.c','.cpp']
    
    gs_compile(compiler,srcfile,**kwargs)
    
    return

def gpp_compile_all(srcdir,**kwargs):
    compiler = 'g++'
    kwargs['objext'] = '.o'
    srcexts = ['.c','.cpp']

    gs_compile_all(compiler,srcdir,srcexts,**kwargs)

    return

def gpp_compile_list(srclist,**kwargs):
    compiler = 'g++'
    kwargs['objext'] = '.o'
    #srcexts = ['.c','.cpp']

    gs_compile_list(compiler,srclist,**kwargs)

    return

def gfortran_compile(srcfile,**kwargs):
    compiler = 'gfortran'
    kwargs['objext'] = '.o'
    #srcexts = ['.f','.f90','.f77']

    gs_compile(compiler,srcfile,**kwargs)
    
    return

def gfortran_compile_all(srcdir,**kwargs):
    compiler = 'gfortran'
    kwargs['objext'] = '.o'
    srcexts = ['.f','.f90','.f77']

    gs_compile_all(compiler,srcdir,srcexts,**kwargs)

    return

def gfortran_compile_list(srclist,**kwargs):
    compiler = 'gfortran'
    kwargs['objext'] = '.o'
    #srcexts = ['.f','.f90','.f77']

    gs_compile_list(compiler,srclist,**kwargs)

    return

def clang_compile(srcfile,**kwargs):
    compiler = 'clang++'
    kwargs['objext'] = '.o'
    #srcexts = ['.c','.cpp']
    
    gs_compile(compiler,srcfile,**kwargs)
    
    return

def clang_compile_all(srcdir,**kwargs):
    compiler = 'clang++'
    kwargs['objext'] = '.o'
    srcexts = ['.c','.cpp']

    gs_compile_all(compiler,srcdir,srcexts,**kwargs)
    
    return

def clang_compile_list(srclist,**kwargs):
    compiler = 'clang++'
    kwargs['objext'] = '.o'
    #srcexts = ['.c','.cpp']

    gs_compile_list(compiler,srclist,**kwargs)

    return