#! /usr/bin/env python
###############################################################################
#                                                                             #
#   Copyright 2005 University of Cambridge Computer Laboratory.               #
#                                                                             #
#   This file is part of Nprobe.                                              #
#                                                                             #
#   Nprobe is free software; you can redistribute it and/or modify            #
#   it under the terms of the GNU General Public License as published by      #
#   the Free Software Foundation; either version 2 of the License, or         #
#   (at your option) any later version.                                       #
#                                                                             #
#   Nprobe is distributed in the hope that it will be useful,                 #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of            #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             #
#   GNU General Public License for more details.                              #
#                                                                             #
#   You should have received a copy of the GNU General Public License         #
#   along with Nprobe; if not, write to the Free Software                     #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
#                                                                             #
###############################################################################


from __future__ import generators
import os, sys, re, shutil
from sys import argv
from getopt import getopt
from string import replace
from time import asctime
import array
from math import pow


##############################################################################

# Handy global
scriptname = None

def opf(fnm, mode):
    global scriptname
    #print 'opf', fnm, mode
    try:
        return open(fnm, mode)
    except IOError, s:
        print '%s ERROR: %s' % (scriptname, s)
        sys.exit(1)

##############################################################################
##############################################################################



def get_types(typef, savecodes):
 
    lines = typef.readlines()
    lno = -1
    lined = {}
    typed = {}
    coded = {}
    tlist1 = []
    tlist2 = []

    maxcode = pow(2, 16)
    codemask = [0]*int(maxcode)

    def gencode():

        highcode = 0
        i = highcode
        while 1:
            while codemask[i]:
                i += 1
            highcode = i
            codemask[i] = 1
            
            yield i
    
    te = re.compile('(?P<type>\S+)(\s+(?P<code>\d+))?(\s+(?P<noparse>\*))?.*')

    if savecodes:
        tmpfnm = typef.name + '.tmp'
        tmpf = opf(tmpfnm, 'w')

    for line in lines:

        lno += 1
            
        l = line.strip()
        if (not l) or (l[0] == '#'):
            if savecodes:
                tmpf.write(line)
            continue

        if lined.has_key(l):
            print 'Error duplicate lines %d and %d \'%s\'' % (lined[l], lno, l)
            sys.exit(1)
        else:
            lined[l] = lno

        m = te.match(l)
        if m:
            type = m.group('type')
            if typed.has_key(type):
                print 'Error duplicate types lines %d and %d \'%s\'' % (typed[type], lno, type)
                sys.exit(1)
            else:
                typed[type] = lno
            code = m.group('code')
            noparse = m.group('noparse')
            if code != None:
                code = int(code)
                #print lno, type, code
                if coded.has_key(code):
                    print 'Error duplicate values lines %d and %d \'%d\'' % (coded[code], lno, code)
                    sys.exit(1)
                else:
                    coded[code] = lno

                tlist1.append((type, code, noparse))
                codemask[code] = 1
                if savecodes:
                    tmpf.write(line)
            else:
                tlist2.append((type, noparse))
        else:
            print 'don\'t understand input line %d \'%s\'' % (lno, line.replace('\n', ''))
            sys.exit(1)

    codes = gencode()
    getcode = gencode().next
    for type, noparse in tlist2:
        code = getcode()
        tlist1.append((type, code, noparse))

        if savecodes:
            tmpf.write('%s %d\n' % (type.ljust(40), code))

    if savecodes:
        tmpf.close()
        os.rename(tmpfnm, typef.name)
    
    return tlist1
	    
##############################################################################

def pt(node):

    #print node

    d = node[0]
    c = node[1]

    keys = d.keys()
    for k in keys:
        #print k, d[k]
        n = d[k]
        if not n:
            return
        pt(n)
        if len(n[0]) == 1 and not n[1]:
            newkey, newnode = n[0].items()[0]
            newkey = k + newkey
            #print newnode
            d[newkey] = newnode
            del d[k]
	    
##############################################################################


def print_node(n, s, indent):
    print '%s%s: [{' % ('  '*indent, s),
    for k in n[0].keys():
        print k + ', ',
    print '}, %s]' % (n[1])

def print_ttree(t, s, indent):
    print_node(t, s, indent)
    for k, n in t[0].items():
        print_ttree(n, k, indent+1)
        
    


sw_ref = 0
sw_stack = []

case_stack = []

max_indent = 100
indc = '  '
ind = 0
inds = []

def gen_sw(ttree, swf):

    def write(s):
        #print s,
        swf.write(s)

    def make_indents():
        for i in range(max_indent):
            inds.append(indc*i)
        

    def case(c):
        global ind
        write('%scase \'%s\':\n' % (inds[ind], c))
        ind += 1
        case_stack.append(c)

    def endcase():
        global ind
        write('%sbreak; // end case %s\n' % (inds[ind], case_stack.pop()))
        ind -= 1

    def cmp(pref):
        global ind
        if len(pref) > 1:
            write('%sif(ci_seqstrcmp(buf, \"%s\"))\n%s{\n' % (inds[ind], pref, inds[ind+1]))
        else:
            write('%sif(*buf != \'%s\')\n%s{\n' % (inds[ind], pref, inds[ind+1]))
        write('%sgoto unrecognised;\n%s}\n' % (inds[ind+2], inds[ind+1]))
        write('%selse\n%s{\n' % (inds[ind], inds[ind+1]))
        write('%sbuf += %d;\n' % (inds[ind+2], len(pref)))
        ind += 1

    def close_cmp(pref):

        global ind
        write('%s} // end cmp %s\n' % (inds[ind], pref))
        ind -= 1

    def gotit(const):

        write('%s*field = %s;\n%sgoto done;\n' % (inds[ind], const, inds[ind]))


    def by_keylen(a, b):
        return len(b[0]) - len(a[0])

    def open_sw():
        global sw_ref, ind
        sw_stack.append(sw_ref)
        write('%sswitch (*buf++) //%d\n%s{\n' % (inds[ind], sw_ref, inds[ind+1]))
        sw_ref += 1
        ind += 2

    def close_sw(code):
        global ind
        if code:
            write('%sdefault:\n%scode = %s;\n%sgoto check_end;\n%sbreak;\n' % (inds[ind], inds[ind+1], code, inds[ind+1], inds[ind+1]))
        else:
            write('%sdefault:\n%sgoto unrecognised;\n%sbreak;\n' % (inds[ind], inds[ind+1], inds[ind+1]))
        write('%s}//end switch %d\n' % (inds[ind-1], sw_stack.pop()))
        ind -= 2

    def check_end(const):
        global ind
        write('%scode = %s;\n' % (inds[ind], const))
        write('%sgoto check_end;\n' % (inds[ind]))
        
    def wt(node, s):
        global ind
        ind += 1
        d = node[0]
        const = node[1]
            
        in_cmp = 0
        in_sw = 0
        ents =  d.items()
        ents.sort(by_keylen)
        if len(ents) > 1 or (const and ents):
            open_sw()
            in_sw = 1
        for k, n in ents:
            if in_sw:
                case(k[0])

                if len(k) > 1:
                    cmp(k[1:])
                    in_cmp = 1
            else:
                cmp(k)
                in_cmp = 1

            wt(n, k)

            if in_cmp:
                close_cmp(k[1:])
                in_cmp = 0
            if in_sw:
                endcase()

        if const and (not ents):
            check_end(const)
        if in_sw:
            close_sw(const)
            in_sw = 0
        ind -= 1    

    make_indents()
    wt(ttree, '')
            


	    
##############################################################################

def gen_types(typef, printf, hf, swf, savecodes):


    types = get_types(typef, savecodes)
    ttree = [{}, None]

    for type, code, noparse in types:
        const = 'CT_' + type.upper().replace('/', '_').replace('-', '_').replace('.', '_')
        #write_const(type, code)
        hf.write('#define %s %d\n' % (const, code))
        printf.write('      case %s: return \"%s\"; break;\n' % (const, type))
        if noparse:
            continue
        d = ttree[0]
        for c in type:
            t = d.setdefault(c, [{}, None])
            d = t[0]
        t[1] = const

     
    pt(ttree)

    gen_sw(ttree,  swf)

	    
##############################################################################

    

def copy_template(tmplf, typef, printf, hf, swf, savecodes):

    c_h_delim = re.compile('\s*/\*+\s*DO NOT DELETE THIS LINE - IT SEPARATES THE SRC AND HEADER FILE PARTS OF THE TEMPLATE\s*\*+/\s*')

    printf_swf_head_delim = re.compile('\s*/\*+\s*DO NOT DELETE THIS LINE - IT SEPARATES THE PRINTFILE AND SWITCHFILE HEAD PARTS OF THE TEMPLATE\s*\*+/\s*')

    printf_swf_tail_delim = re.compile('\s*/\*+\s*DO NOT DELETE THIS LINE - IT SEPARATES THE PRINTFILE AND SWITCHFILE TAIL PARTS OF THE TEMPLATE\s*\*+/\s*')

    c_insert_delim = re.compile('\s*/\*+\s*DO NOT DELETE THIS LINE - IT MARKS THE INSERTION POINT FOR AUTOMATICALLY GENERATED CODE\s*\*+/\s*')

    lines = tmplf.readlines()
    outf = hf

    for l in lines:
        if c_h_delim.match(l):
            outf = printf
            continue
        elif printf_swf_head_delim.match(l):
            outf = swf
            continue
        elif printf_swf_tail_delim.match(l):
            outf = printf
            continue
        elif c_insert_delim.match(l):
            gen_types(typef, printf, hf, swf, savecodes)
            continue
        else:
            outf.write(l)

    
	    
##############################################################################

def gen_outf(args, savecodes):

    typef = opf(args[0], 'r')
    tmplf = opf(args[1], 'r')

    outfiles = []
    for nm in args[2:5]:
        outfiles.append((opf(nm, 'w'), nm))

    for f, nm in outfiles:
        f.write('\n\n/*\n * %s\n * Atomatically generated from %s and %s \n * by %s@%s 0n %s\n */\n\n' % (nm, args[1], args[0], os.environ['LOGNAME'], os.environ['HOSTNAME'], asctime()))

    copy_template(tmplf, typef, outfiles[0][0], outfiles[1][0], outfiles[2][0], savecodes)

    outfiles[1][0].write('#endif /* ifndef __CONTENT_T_H__ */')

    for f, nm in outfiles:
        f.write('\n\n/*\n * End %s\n */\n\n' % (nm))
	    
##############################################################################

def backup(files):

    def copy(s, d):
        try:
            shutil.copy2(s, d)
        except IOError, e:
            print 'ERROR backing up %s: %s' % (s, e)
            sys.exit(1)

    for f in files:
        if os.path.isfile(f):
            print 'Backing up %s' % (f)
            copy(f, f + '.bak')
	    
##############################################################################
	
def main():

    global scriptname
    
    scriptname = os.path.basename(argv[0])
    savecodes = 0

    try:
        optlist, args = getopt(argv[1:], 's')

    except getopt.error, s:
        print '%s: Unrecognised option' % (scriptname)
        sys.exit(1)

    for opt in optlist:
        if opt[0] == '-s':
            savecodes = 1

    if len(args) != 5:
        print '%s ERROR: Takes five arguments - %d given' % (scriptname, len(args))
        sys.exit(1)

    backups = args[2:4]
    if savecodes:
        backups.append(args[0])
    backup(backups)
    gen_outf(args, savecodes)
        
	    
##############################################################################


# Call main when run as script
if __name__ == '__main__':
        main()
