#!/usr/bin/python

import sqlite3,os,re,sys

def allfilefrom(path,ext):
    for filename in sorted( (pth+'/'+name).replace('//','/')
             for pth,_,names in os.walk(path)
             for name in names
             if name.endswith(ext)
             if not name.startswith('.') ):
        yield filename,str(open(filename,'r').read())

def macroapply(src,macro):
    """

    
    >>> case = '''
    ... an atom return itself
    ... when a function is curryed, it is decomposed into MONAD
    ... like a dyad x y => atom
    ... is curryed as monad y => monad x => atom
    ... and a triad a b c => atom
    ... is curryed as monad c => monad b => monad a => atom
    ... '''
    >>> macro = {
    ...   'atom' : 'ATOM',
    ...   'monad ([a-z]+)' : 'MONAD({1})',
    ...   'dyad ([a-z]+) ([a-z]+)' : 'DYAD({1}, {2})',
    ...   'triad ([a-z]+) ([a-z]+) ([a-z]+)' : 'TRIAD({1}, {2}, {3})'
    ... }
    >>> macroapply(case,macro).strip()
    'an ATOM return itself\\nwhen a function is curryed, it is decomposed into MONAD\\nlike a DYAD(x, y) => ATOM\\nis curryed as MONAD(y) => MONAD(x) => ATOM\\nand a TRIAD(a, b, c) => ATOM\\nis curryed as MONAD(c) => MONAD(b) => MONAD(a) => ATOM'
    
    >>> case = '''
    ...
    ... '''
    >>> macro = {
    ... 
    ... }
    >>> macroapply(case,macro).strip()
    ''

    """
    for reg, rep in macro.items():
        p = re.compile(reg)
        numParams = p.groups+1
        m = p.search(src)
        while m:
            b, e = m.span()
            params = [ m.group(i) 
                       for i in range(numParams) ]
            src = ''.join([ src[:b], 
                            rep.format(*params),
                            src[e:] ])
            m = p.search(src)
    return src

def dbexec(conn,script):
    cur = conn.cursor()
    #statementLst = re.compile("/\*[^\*]+\*/").split(script)
    statementLst = [ '/*'+stm for stm in script.split('/*') ]
    with conn:     
        for separation,statement in enumerate(statementLst):
            try:
                cur.executescript(statement)
                conn.commit()
            except Exception as e:
                print("after " + str(separation) + "th /* commentary */ ")
                print("[...] "+statement+" [...]")
                raise
            print '.',

re_topologicalsort = re.compile('--DEPENDS ON ([^:]+):--')

def topologicalSort(allScript):
    sortedScriptName = []
    def scriptToSort():
        return [ (scriptName,scriptTxt)
                 for scriptName,scriptTxt in allScript 
                 if scriptName not in sortedScriptName ]
    def scriptSortable():
        return [ scriptName
                 for scriptName,scriptTxt in scriptToSort()
                 if not [ 'unresolved dependency' 
                          for m in re_topologicalsort.finditer(scriptTxt)
                          if not filenameMatcher(m.group(1),sortedScriptName) ]
                ]
    def filenameMatcher(filename,filepathlst):
        for filepath in filepathlst:
            if filepath.endswith(filename):
                return True
        return False
    while scriptToSort():
        dependenciesResolved = scriptSortable()
        if not dependenciesResolved:
            problem = " ".join([name for name,_ in scriptToSort()])
            print "these file have unresolvable dependencies :" + problem
            sys.exit()
        else:
            sortedScriptName += dependenciesResolved
    return [ (scriptName, dict(allScript)[scriptName])
             for scriptName in sortedScriptName ]

re_ismacro = re.compile('--MACRO:([^:]+):--')

def findFirstMacroFromScript(script):
    m = re_ismacro.search(script)
    regex, macro = 'a^',''
    if m:
        regex = m.group(1)
        macro = script[m.end():script.find('--END:--',m.end())]
    return regex,macro

def removeFirstMacroFromScript(script):
    """

    >>> removeFirstMacroFromScript('''
    ...   nothing to remove
    ... ''').strip()
    'nothing to remove'

    >>> removeFirstMacroFromScript('''
    ... --MACRO:TEST:--
    ... remove all because not end clause
    ... ''').strip()
    ''

    >>> removeFirstMacroFromScript('''
    ... --MACRO:TEST:--
    ... remove this
    ... --END:--
    ... keep this
    ... ''').strip()
    'keep this'

    >>> removeFirstMacroFromScript('''
    ... keep this
    ... --MACRO:TEST2:--
    ... not that
    ... ''').strip()
    'keep this'

    >>> removeFirstMacroFromScript('''
    ... ''').strip()
    ''

    """
    m = re_ismacro.search(script)
    if m:
        n = script.find('--END:--',m.end())
        if n != -1:
            n += 8
        script = script[:m.start()]+script[n:]
    return script

def buildMacroTable(srcfile='src/sql/'):
    allscript = [ loaded for loaded in allfilefrom(srcfile,'.sql')]
    macrolst = []
    for filename,script in allscript:
        while re_ismacro.search(script):
            macrolst.append(findFirstMacroFromScript(script))
            script = removeFirstMacroFromScript(script)
    macro = dict(macrolst)
    return macro

def dbcompile(dbfile,srcfile='src/sql/',macro={}):
    allscript = [ loaded for loaded in allfilefrom(srcfile,'.sql') ]
    allscript = topologicalSort(allscript)
    conn =  sqlite3.connect(dbfile,check_same_thread=False)
    for filename, script in allscript:
        while re_ismacro.search(script):
            script = removeFirstMacroFromScript(script)
        if script.split():
            print 'loading '+filename,
            script = macroapply(script,macro)
            dbexec(conn,script)
            print
    return conn

def build(dbname,srcfilelst):
    libdir = os.path.dirname(os.path.realpath(__file__))+'/lib/'
    macro = buildMacroTable(libdir)
    for srcdir in sys.argv[2:]:
        macro.update(buildMacroTable(srcdir))
    print "create"
    dbcompile(dbname,libdir,macro)
    for srcdir in srcfilelst:
        print "process ",srcdir
        dbcompile(dbname,srcdir,macro)
    print "done"


if __name__ == '__main__':
    import sys,os
    if len(sys.argv) > 2:
        dbname = sys.argv[1]
        build(dbname,sys.argv[2:])
    elif len(sys.argv) == 2 and sys.argv[1] == 'TEST':
        import doctest
        doctest.testmod()
    else:
        print("usage : "+sys.argv[0]+" destinationfile.hss ./src /absdir/src ...")

