#!/usr/bin/env python2.4

#
# CTL IDL compiler, based on idlParse.py (Copyright 2003, Paul McGuire)
#
# Copyright 2005, 2006 Boris Buegling <boris@icculus.org>
#

import os, re, sys
from pyparsing import alphas, alphanums, CharsNotIn, cStyleComment, Forward, \
	Literal, nums, OneOrMore, Optional, ParseException, restOfLine, Suppress, \
	Word, ZeroOrMore

# Comment which is put into all stub classes
__stubComment = """/** This stub code was autogenerated by the CTL IDL compiler.
Written by Boris Buegling, licensed under the GNU General Public license. 
Thanks to Paul McGuire for writing pyparsing. */"""

# Keywords
define	= Literal('#') + Literal('define')
include	= Literal('#') + Literal('include')

# CI grammar
c_ident	= Word(alphas + '_', alphanums + '_')
ident	= Forward()
ident	<< Optional(c_ident + '::') + c_ident + Optional('<' + ident + '>')

numargs = Suppress(Word(nums))
id		= Suppress(Word(nums))

oper = Literal('operator') + (Literal('()') | Literal('+=') | Literal('-=') | \
	Literal('*=') | Literal('/=') | Literal('%=') | Literal('^=') | \
	Literal('&=') | Literal('|=') | Literal('<<') | Literal('>>') | \
	Literal('<<=') | Literal('>>=') | Literal('==') | Literal('!=') | \
	Literal('<=') | Literal('>=') | Literal('&&') | Literal('||') | \
	Literal('++') | Literal('--') | Literal('->*') | Literal('[]') | \
	Literal('new') | Literal('new[]') | Literal('delete') | \
	Literal('delete[]')) # :: . .* ?: sizeof typeid
funcname = oper | c_ident

type	= Forward()
type1	= Suppress(Optional('const')) + ident + Suppress(Optional(Literal('*') | Literal('&')))
type2	= Suppress(Literal('(')) + Suppress(Optional('const')) + ident + \
	Suppress(Literal(',')) + \
	Literal('(') +  type + ZeroOrMore(Literal(',') + type) + Literal(')') + \
	Suppress(Literal(',') + numargs + Literal(')'))
type	<< (type1 | type2)

typelist1	= Suppress('(') + Suppress(')') + Suppress(Optional('const')) + \
	Suppress(',') + Suppress('0')
typelist2	= Suppress('(') + type + ZeroOrMore(Suppress(',') + type) + Suppress(')') + \
	Suppress(Optional('const')) + Suppress(',') + numargs
typelist	= typelist1 | typelist2

funcsign	= type + Suppress(',') + funcname + Suppress(',') + typelist
exception	= id + 'Throws' + typelist

constructor = Suppress(define + 'CTL_Constructor') + id + typelist + \
	Optional(Suppress(define + 'CTL_Constructor') + exception)
method		= Suppress(define + 'CTL_Method') + id + funcsign + \
	Optional(Suppress(define + 'CTL_Method') + exception)
s_method	= Suppress(define + 'CTL_StaticMethod') + id + funcsign + \
	Optional(Suppress(define + 'CTL_StaticMethod') + exception)

function		= Suppress(define + 'CTL_Function') + id + funcsign + \
	Optional(define + 'CTL_Function' + exception)
functiontmpl	= define + 'CTL_FunctionTmpl' + id + funcsign + ',' + \
	typelist + Optional(define + 'CTL_FunctionTmpl' + exception)

classentry 	= method | s_method | constructor
classbody	= Suppress(include + 'CTL_ClassBegin') + OneOrMore(classentry) + \
	Suppress(include + 'CTL_ClassEnd')
classname	= Suppress(define + Literal('CTL_Class')) + c_ident
classnameT	= Suppress(define + Literal('CTL_ClassTmpl')) + c_ident
classdecl	= classname + classbody
classtmpl	= classnameT + ',' + typelist + classbody

libentry	= classdecl | classtmpl | function | functiontmpl
libbody		= Suppress(include + 'CTL_LibBegin') + OneOrMore(libentry) + \
	Suppress(include + 'CTL_LibEnd')
libname		= Suppress(define + Literal('CTL_Library')) + c_ident
library		= libname + libbody

interface	= Suppress(Optional('#include <ctl.h>')) + (library | classdecl | classtmpl)

# Ignore inline C code
inlcode		= Forward()
inlcode		<< '{' + ZeroOrMore(inlcode | CharsNotIn('{}')) + '}' + Optional(';')

classdecl2	= 'class' + restOfLine
classdef	= 'class' + Optional(c_ident) + inlcode
comment		= ('//' + restOfLine) | cStyleComment
defn		= ((Literal('#ifndef') | Literal('#ifdef')) + restOfLine + \
	'#define' + restOfLine) | '#endif' + restOfLine
enumdef		= 'enum' + inlcode
namespace	= 'using namespace' + restOfLine
struct		= 'struct' + Optional(c_ident) + Optional(':' + c_ident + c_ident) + inlcode
typedef 	= 'typedef' + restOfLine
modifier	= (Literal('public') | Literal('private')) + restOfLine

interface.ignore(classdef)
interface.ignore(classdecl2)
interface.ignore(comment)
interface.ignore(defn)
interface.ignore(enumdef)
interface.ignore(namespace)
interface.ignore(struct)
interface.ignore(typedef)
interface.ignore(modifier)

def parseType (inp, loc, tokens):
	assert len(tokens) == 1
	if tokens[0] == 'int4':
		return ('jint', 'int', 'int', '%s', '%s')
	if tokens[0] == 'string':
		# TODO: Missing free() function
		return ('jstring', 'std::string', 'String', 'env->GetStringUTFChars(%s, 0)',
			'env->NewStringUTF(%s.c_str())')
	if tokens[0] == 'void':
		return ('void', 'void', 'void', '%s', '%s')
	raise TypeError('No conversion for %s found.' % tokens[0])

def parseMethod (inp, loc, tokens):
	pkgname = curPkgName
	if pkgname == '_default':
		pkgname = ''
	arglist, decode_args = [], ''
	for i in range(len(tokens)-2):
		jniType, cppType, javaType, convert, reconvert = tokens[i+2]
		arglist.append('%s jarg%i' % (jniType, i))
		conv = convert % ('jarg'+str(i))
		decode_args += '\t\t%s arg%i = %s;\n' % (cppType, i, conv)
	if len(tokens)-2 > 0:
		arglist = ', '+', '.join(arglist)
	conv =  tokens[0][4] % 'res'
	# Support for void methods...
	call_me, ret_me = '', ''
	if tokens[0][1] != 'void':
	    call_me = '%s res = ' % tokens[0][1]
	    ret_me = '\n\t\treturn %s;' % conv
	
	wrapper_code = """
	try
	{
%s
		%s::%s tmp;
		%stmp.%s(%s);%s
	}
	catch (JNIException &e)
	{
		std::cerr << e.getMessage() << "\\n";
	}
	catch (jthrowable &e)
	{
		std::cerr << "Java runtime exception: " << e << "\\n";
	}
	catch (ctl::exception &e)
	{
		std::cerr << "CTL exception: " << e << "\\n";
	}
	catch (...)
	{
		std::cerr << "Unknown exception.\\n";
	}
""" % (decode_args, pkgname, curClassName, call_me, tokens[1],
		','.join(['arg%i' % i for i in range(len(tokens)-2)]), ret_me)
	allMethods.append("\tpublic native %s %s (%s);" % (tokens[0][2], tokens[1],
		', '.join(['%s arg%i' % (tokens[i+2][2], i) for i in range(len(tokens)-2)])))
	return """JNIEXPORT %s JNICALL Java_%s_%s_%s (JNIEnv *env, jobject o%s)
{ STDJNI(env) {%s
} }""" % (tokens[0][0], curPkgName, curClassName, tokens[1], arglist, wrapper_code)

def parseClass (inp, loc, tokens):
	global curClassName
	curClassName = tokens[0]
	return tokens[1:]

def parseLibrary (inp, loc, tokens):
	global curPkgName
	curPkgName = tokens[0]
	return tokens[1:]

def ri2jni (fname):
	type.setParseAction(parseType)
	classname.setParseAction(parseClass)
	libname.setParseAction(parseLibrary)
	method.setParseAction(parseMethod)
	res = interface.parseFile(fname)

	global curPkgName
	if repPkgName:
		curPkgName = repPkgName

	basename = fname.split('.')[0].split('/')[-1]
	dirname = '/'.join(fname.split('.')[0].replace('cpp/', '').split('/')[:-1])
	dir = ''
	lib = os.getenv('PWD')
	if os.path.exists('build.xml'):
		dir = 'src/'
		lib += '/cpp/libs/lib%s.so' % basename
	else:
		lib += '/lib%s.so' % basename
		if not os.path.exists(curPkgName):
			os.mkdir(curPkgName)

	out = file('%s%s/%s.java' % (dir, curPkgName, curClassName), 'w')
	out.write("""package %s;

public class %s
{
%s
	public native void intuse (String str);

	static
	{
		System.load("%s");
	}
}
""" % (curPkgName, curClassName, '\n'.join(allMethods), lib))
	out.close()

	dir = ''
	if os.path.exists('build.xml'):
		dir = 'cpp/libs/'
	out = file('%s%s.cpp' % (dir, basename), 'w')
	out.write("""#include <%s>
#include <StdJNI.h>

// Blame Mailman and wtf are you reading this anyways?
using namespace de::tubs::wire::libstdjni;

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT void JNICALL Java_%s_%s_intuse (JNIEnv *env, jobject o, jstring str)
{ STDJNI(env) {
	const char *str2 = env->GetStringUTFChars(str, 0);
	std::string type("%s::%s");
	ctl::link lnk(str2);

	if(!lnk)
	{
		std::cerr << "Could not create link -> " << "\\"" << str2 << "\\"\\n";
		return;
	}

	ctl::setLink(type, lnk);
	env->ReleaseStringUTFChars(str, str2);
	return;
	std::cout << "Never reached!\\n"; // Never reached
} }

%s

#ifdef __cplusplus
}
#endif
""" % (fname, curPkgName, curClassName, curPkgName, curClassName, ''.join(res)))
	out.close()

	if os.path.exists('build.xml'):
		out = file('cpp/libs/makefile.%s' % basename, 'w')
		out.write("""CI=__MOO__

include inc.mak

CXXFLAGS+=-I$(PWD)/../..
LDLIBS+=$(PWD)/../__DIR__/__MOO__.so

.PHONY: all

all: lib__MOO__.so
""".replace('__MOO__', basename).replace('__DIR__', dirname))
		out.close()

		wrapname = '%sCI' % re.sub('RI', '', curClassName)
		out = file('src/%s/%s.java' % (curPkgName, wrapname), 'w')
		out.write("""package %s;

public class %s extends %s
{
	private static CTL.Process proc = null;

	public static void use (CTL.Process proc2)
	{
		proc = proc2;
	}

	public %s ()
	{
		super();
		if (proc.loc().linkage() != CTL.Types.Location.CLIB)
		{
			System.out.println("Error: you are trying to use a native wrapper with non-native location '"+proc.loc()+"'.");
			// TODO: Is this OK?
			System.exit(1);
		}
		intuse(proc.loc().path()+" -l %s");
	}
}
""" % (curPkgName, wrapname, curClassName, wrapname, 'lib'))
		out.close()

if __name__ == '__main__':
	if len(sys.argv) == 1:
		print "Usage: %s [CI]" % sys.argv[0]
		sys.exit(1)

	allMethods = []
	curPkgName 	= '_default'
	curClassName = None
	repPkgName 	= None
	replace		= {}
	for arg in sys.argv[1:-1]:
		if arg.startswith('-P'):
			repPkgName = arg[2:]
		elif arg.startswith('-D'):
			tmp = arg[2:].split('=')
			replace[tmp[0]] = tmp[1]

	try:
		ri2jni(sys.argv[-1])
	except ParseException, err:
		print >>sys.stderr, err.line
		print >>sys.stderr, " "*(err.column-1) + "^"
		print >>sys.stderr, err
		sys.exit(1)
