#! /usr/bin/python

import argparse
import os 
import shutil
import re

def parse_proto():
	line = proto_file.readline()
	flag_proto_valid = False;
	flag_package_detected = False;
	flag_message_detected = False;
	flag_type_name_detected = False;
	flag_type_name_saved = False;
	flag_enum_detected = False;
	flag_type_detected = False;
	flag_message_saved = True;

	known_types = ("int32", "string")	# TODO: Um alle google protobuf types erweitern, die unterstuetzt werden sollen

	message_depth = 0;

	c_type = "";		# int32 | string ...
	message_type = ""; 	# optional | required | repeated
	type_name = "";
	
	package_name = "";			
	
	message_name = [];
	global_message_name = [];
	class_name = "";
	class_code = [];
	embind_code = "namespace emscripten {\n\nEMSCRIPTEN_BINDINGS(";

	while line:
		line = line.strip()					# delete leading spaces/tabulators
		line = line.replace('\t', ' ')		# replace tabs by spaces
		while '  ' in line:
			line = line.replace('  ', ' ')	# replace multiple spaces
		line = line.replace(' ', ',')		# replace spaces by commata
#		if args.verbose:
#			print line
		mylist = line.strip().split(',')
#		print mylist
		for s in mylist:
			print s
			if flag_package_detected == True:
				#print s
				package_name = s.rpartition(';')[0]
				#package_name = s
				embind_code = embind_code + package_name + ") {\n"
				embind_code = embind_code + "\tclass_<::google::protobuf::MessageLite>(\"MessageLite\")\n"
				embind_code = embind_code + "//\t\t.function(\"SerializeToString\", &::google::protobuf::MessageLite::SerializeToString, allow_raw_pointer<arg<0>>())\n"
				embind_code = embind_code + "\t\t.function(\"SerializeAsString\", &::google::protobuf::MessageLite::SerializeAsString)\n"
				embind_code = embind_code + "\t\t.function(\"ParseFromString\", &::google::protobuf::MessageLite::ParseFromString)\n"
				embind_code = embind_code + "//\t\t.function(\"SerializeToArray\", &::google::protobuf::MessageLite::SerializeToArray, allow_raw_pointers())\n"
				embind_code = embind_code + "//\t\t.function(\"ParseFromArray\", &::google::protobuf::MessageLite::ParseFromArray, allow_raw_pointers())\n"
				embind_code = embind_code + "\t\t;\n\n"
				embind_code = embind_code + "\tclass_<::google::protobuf::Message, base<::google::protobuf::MessageLite>>(\"Message\")\n"
				embind_code = embind_code + "//\t\t.function(\"SerializeToFileDescriptor\", &::google::protobuf::Message::SerializeToFileDescriptor)\n"
				embind_code = embind_code + "//\t\t.function(\"SerializeToOstream\", &::google::protobuf::Message::SerializeToOstream, allow_raw_pointer<arg<0>>())\n"
				embind_code = embind_code + "\t\t;\n"
	
				flag_proto_valid = True;
				flag_package_detected = False;

			if (flag_message_detected and flag_proto_valid) == True:
				message_depth += 1
				print "------------------Level: " + str(message_depth)
				message_name.append(s)
				global_message_name.append(s)
				# Save name of embind class (it depends on
				# the message it is found in (and the message
				# it is found in)
				if message_depth > 1:
					class_name = class_name + "_" + message_name[message_depth-1]
				else:
					class_name = message_name[message_depth-1]

				class_code.append("\tclass_<" + package_name + "::" + class_name + ", base<::google::protobuf::Message>>(\"" + message_name[message_depth-1] + "\")\n\t\t.constructor<>()\n");
				print "Message detected: " + message_name[message_depth-1]
				flag_message_detected = False;
				# Ich brauche ein array fuer die message_names und 
				# die Info in welcher message ich grade bin 
				# print "-Jetzt nach depth erhoehen\n -Array class_code erweitern und schonmal class Zeugs schreiben\n -suckzessive die members finden. " 
				
#				if ( (s=="}") and not flag_enum_detected) == True:
#					flag_message_detected = False;	
#					message_depth -= 1
#					print "Level: " + str(message_depth)
		
			# detect keywords and set 
			# flags	
			if s == "package":
				flag_package_detected = True;
			if s == "message":
				flag_message_detected = True;
			if s == "enum":
				flag_enum_detected = True;
	
			# detect and save message member	
			if flag_type_name_detected :
				type_name = s
				flag_type_name_detected = False # will be reset later
				flag_type_name_saved = True 
			if flag_type_detected:
				c_type = s 	
				# check wether type is known 
				if c_type in known_types: #TODO: um alle Typen erweitern, die google protobuf kennt
					print "Type is known"
				elif c_type in message_name:
					print "Type is custom"
				else:
					print "warning: Type is not known, yet"	
				flag_type_detected = False
				flag_type_name_detected = True
			if s in ("required", "repeated", "optional"):
				message_type = s
				flag_type_detected = True;
				print "Type detected: " + str(message_type)

			# Diese Zeilen ueberdenken. Wenn
			# mehrere geschweifte Klammern 
			# vorhanden sind kann es zu Pro-
			# blemen fuehren.
			if s == "}": 
				if flag_enum_detected:	
					flag_enum_detected = False; # enum is closed
					print "do not reduce message level"
				else:
					message_depth -= 1
					# TODO: class_name bei letztem _ abschneiden
					print "---------------Level: " + str(message_depth)	
					print "enum_flag: " + str(flag_enum_detected)

			# save message if message_depth
			# is zero
			if message_depth != 0:
				print "Members der Message ermitteln"
				if flag_type_name_saved:
					flag_type_name_saved = False;
					# if known type .property ...
					# else .function ..
					if c_type in known_types:	
						# message member is 
						# an atomar type
						class_code[message_depth-1] += "\t\t.property(\"" + type_name + ", &" + package_name + "::" + class_name + "::" + type_name + ")\n"
					elif c_type in global_message_name:
						# message is a custom type
						# defined by a message
						class_code[message_depth-1] += "\t\t.function(\"" + type_name + "\" select_overload<const ::" + package_name + "::" + message_name[message_depth-1] + "::" + c_type + "& (int)const>(&" + package_name + "::" + message_name[message_depth-1] + "::" + type_name + "))\n"

					# functions for the 
					# current message member
					if c_type == "string":
						class_code[message_depth-1] += "\t\t.function(\"set_" + type_name + ", select_overload<void(const ::std::" + c_type + "&)>(&" + package_name + "::" + class_name + "::set_" + type_name + "))\n\n"
					elif c_type == "int32":
						class_code[message_depth-1] += "\t\t.function(\"set_" + type_name + ", &" + package_name + "::" + class_name + "::set_" + type_name + ")\n\n"
					
					if message_type == "repeated":
						class_code[message_depth-1] += "\t\t.function(\"add_" + type_name + "\", &" + package_name + "::" + message_name[message_depth-1] + "::add_" + type_name + ", allow_raw_pointers())\n"
						class_code[message_depth-1] += "\t\t.function(\"clear_" + type_name + "\", &" + package_name + "::" + message_name[message_depth-1] + "::clear_" + type_name + ")\n"
#------------------------------------------------------------------------------------
# optional and required are 
# not necessary to implement yet
#					elif message_type == "optional":
#						print "---------------------------------------------required"
#					elif message_type == "required":
#						print "---------------------------------------------repeated"
#------------------------------------------------------------------------------------

					flag_type_detected = False;
				flag_message_saved = False;
			else: # message_depth is zero
				if flag_message_saved == False:
					print "Jetzt das array abspeichern"
					for code_line in class_code:
						embind_code = embind_code + "\n\n" + code_line + "\t\t;"
					print class_code
					class_code = []
					class_name = ""
					message_name = []
					flag_message_saved = True;
				else: # flag_message_saved is False
					print "Message saved already."

		#function parse_proto(subclass?)
		# 1. search package (once at the beginning)
		# 2. search message/enum -> search open brace -> search optional/required/repeated|message/enum
		#     if message -> parse_proto(subclass?)
		# 3. if close_brace restart at 1.
		# Rekursion waere interessant
		line = proto_file.readline()
		# Idee: parse_proto(Tiefe)
		# bei } Tiefe --
		# bei message Tiefe ++
		# code in der_code[Tiefe] abspeichern
		# in_message=in_message+"_subclass"
		# -> sobald die Tiefe wieder Null ist 
		# embind_code = embind_code + den ganzen der_code[Tiefe] Zeugs 
	
	embind_code = embind_code + "\n\n\tfunction(\"ShutdownProtobufLibrary\", &google::protobuf::ShutdownProtobufLibrary, allow_raw_pointers());\n}"
	print embind_code

def detect_messages():
	line = proto_file.readline()

	while line:
		line = line.strip()                 # delete leading spaces/tabulators
		line = line.replace('\t', ' ')      # replace tabs by spaces
		while '  ' in line:
			line = line.replace('  ', ' ')  # replace multiple spaces
		line = line.replace(' ', ',')       # replace spaces by commata
		if args.verbose:
			print line
		mylist = line.strip().split(',')
		print mylist
		for s in mylist:
			print s
		line = proto_file.readline()

parser = argparse.ArgumentParser() 
#parser.add_argument('file', type=argparse.FileType('r'),
#                    help="input file (.proto)")
parser.add_argument('file', type=str,
                     help="input file (.proto)")
parser.add_argument("-v", "--verbose", 
                    help="increase output verbosity", action="store_true")
args = parser.parse_args()

# get file name/path and file extension
proto_path, proto_extension = os.path.splitext(args.file)
src_extension = ".pb.cc"
dst_extension = ".falsch.embind.cc"


if proto_extension==".proto":
	print "Open " + args.file
	proto_file = open(proto_path + ".proto")

	print "Parse " + args.file
	#TODO: hier File parsen und Zeugs rausziehen
	parse_proto();
#	detect_messages();
	#line = proto_file.readline()
	#while line:
	#	if args.verbose:
	#		print line
		#function parse_proto(subclass?)
		# 1. search package (once at the beginning)
		# 2. search message/enum -> search open brace -> search optional/required/repeated|message/enum
		#     if message -> parse_proto(subclass?)
		# 3. if close_brace restart at 1.
		# Rekursion waere interessant
	#	line = proto_file.readline()

	embind_code = "\n\nnamespace custom_pb {\n//embind supports raw pointers to classes and smart pointers to classes. \n//embind does not (and for which I have no plan to add) support for pointers \n//to primitives like float* and int*\n\n// Anstatt Klasse waere vlt. Member von AddressBook und\n// direkter Aufruf von dessen Memberfunktionen besser\n\n// Ueber Vector Typ als buffer nachdenken\nclass pbCustomizer {\n\tpublic: \n\t\tpbCustomizer(tutorial::AddressBook &address_book)\n\t\t\t:_address_book(&address_book)\n\t\t{\n\t\t\t//_address_book = address_book;\n\t\t\tstd::cout << \"pbCustomizer Constructor ...\" << std::endl;\n\t\t}\n\t\t~pbCustomizer() {\n\t\t\tstd::cout << \"pbCustomizer Destructor ...\" << std::endl;\n\t\t}\n\t\t//tutorial::AddressBook _address_book;\n\t\ttutorial::AddressBook* _address_book;\n\n\t\t// difference between c_str() and data() -> c_string is NULL terminated\n\n\t\t// Hier wird oft kopiert, nicht gerade speicherschonend,\n\t\t// TODO: bessere Loesung finden!\n\t\tstd::string SerializeToArray(int size) {\n\t\t\tchar* c = new char[size];\n\t\t\tstd::string buffer;\n\n\t\t\t_address_book->SerializeToArray((char*) c, size);\n\t\t\tbuffer = c;\n\n\t\t\treturn buffer;\n\t\t};\n\t\t\n\t\t// Const Uebergabe von Reference funktioniert\n\t\tbool ParseFromArray(const std::string &buffer, int size) {\t\n\t\t\treturn _address_book->ParseFromArray((char*) buffer.data(), size);\n\t\t};\n\t\t\n};\n\n} // namespace custom_pb\n\nnamespace emscripten {\n\nEMSCRIPTEN_BINDINGS(my_simple_class) {\n\tclass_<::google::protobuf::MessageLite>(\"MessageLite\")\n//\t\t.function(\"SerializeToString\", &::google::protobuf::MessageLite::SerializeToString, allow_raw_pointer<arg<0>>())\n\t\t.function(\"SerializeAsString\", &::google::protobuf::MessageLite::SerializeAsString)\n\t\t.function(\"ParseFromString\", &::google::protobuf::MessageLite::ParseFromString)\n//\t\t.function(\"SerializeToArray\", &::google::protobuf::MessageLite::SerializeToArray, allow_raw_pointers())\n//\t\t.function(\"ParseFromArray\", &::google::protobuf::MessageLite::ParseFromArray, allow_raw_pointers())\n\t\t;\n//\t\t.function(\"MessageLite::SerializeToString\", &::google::protobuf::MessageLite::SerializeToString)\n//\t\t;\n\n\tclass_<::google::protobuf::Message, base<::google::protobuf::MessageLite>>(\"Message\")\n\t\t.function(\"SerializeToFileDescriptor\", &::google::protobuf::Message::SerializeToFileDescriptor)\t\n\t\t.function(\"SerializeToOstream\", &::google::protobuf::Message::SerializeToOstream, allow_raw_pointer<arg<0>>())\n\t\t;\n\n    class_<tutorial::AddressBook, base<::google::protobuf::Message>>(\"AddressBook\")\n        .constructor<>()\n        .function(\"set_some_val\", &tutorial::AddressBook::set_some_val)\n\t\t.function(\"ByteSize\", &tutorial::AddressBook::ByteSize)\n        .property(\"some_val\", &tutorial::AddressBook::some_val)\n//\t\t.property(\"PhoneNumber\", &tutorial::AddressBook::AddressBook_PhoneNumber)\n        ;\n//\t\t.function(\"incrementX\", &MyClass::incrementX)\n//\t\t.property(\"x\", &MyClass::getX, &MyClass::setX)\n//\t\t\t.class_function(\"getStringFromInstance\", &MyClass::getStringFromInstance)\n//\t\t\t;\n\n/*\n\tclass_<custom_pb::pbArrayEncode>(\"pbArrayEncode\")\n\t\t.constructor<>()\n\t\t//.function<>(\"my_test\", &custom_pb::pbArray::my_test)\n\t\t;\n*/\n\n\tclass_<custom_pb::pbCustomizer>(\"pbCustomizer\")\n\t\t.constructor<tutorial::AddressBook&>()\n\t\t.function(\"SerializeToArray\", &custom_pb::pbCustomizer::SerializeToArray)//, allow_raw_pointer<arg<0>>())\n\t\t.function(\"ParseFromArray\", &custom_pb::pbCustomizer::ParseFromArray)\n\t\t//.property(\"_address_book\", &custom_pb::pbCustomizer::_address_book)\n\t\t;\n\n\t//emscripten::function(\"MySerializeToBytes\", &MySerializeToBytes);\n//\temscripten::class_<tutorial::AddressBook_PhoneNumber>(\"PhoneNumber\")\n//\t\t;\n}\n\n} // namespace emscripten\n\n"
	
	#embind_code = "EMSCRIPTEN_BINDINGS(my_simple_class) {\n\temscripten::class_<tutorial::AddressBook>(\"AddressBook\")\n\t\t.constructor<>()\n\t\t.function(\"set_some_val\", &tutorial::AddressBook::set_some_val)\n\t\t.property(\"some_val\", &tutorial::AddressBook::some_val)\n\t\t;\n\t\t//  .function(\"incrementX\", &MyClass::incrementX)\n\t\t//  .property(\"x\", &MyClass::getX, &MyClass::setX)\n\t\t//  .class_function(\"getStringFromInstance\", &MyClass::getStringFromInstance)\n\t\t//  ;\n}"

#	if args.verbose:
#		print proto_file.readlines()	
#		print proto_path
#		print proto_extension
#	else:
#		print "Ausgabe limitiert"

	print "Close " + args.file
	proto_file.close()

	print "Create file " + proto_path + dst_extension 
	dst_file = open(proto_path + dst_extension, 'w')

	print "Open " + proto_path + dst_extension  + " for reading"
	pb_header_file = open(proto_path + src_extension,'r')	
	line = pb_header_file.readline()	

	print "Write Content to " + proto_path + dst_extension 
	# write info
	dst_file.write("// " + proto_path + dst_extension + " edit by parse_proto.py script.  DO NOT EDIT!\n\n")

	# write include
	dst_file.write("\n//begin lines added by parse_proto.py\n#include <emscripten/bind.h>\n#include <iostream>\n//end lines added by parse_proto.py\n\n")

	# write rest of the header file
	while line: # hopefully the last #endif line will not change in google protobuf versions
		#print line
		dst_file.write(line)
		line = pb_header_file.readline()

	# write embind stuff
	dst_file.write("\n// begin lines added by parse_proto.py\n")
	dst_file.write(embind_code)
	dst_file.write("\n// end lines added by parse_proto.py \n")
	
	print "Close files " + proto_path + dst_extension + " and " + proto_path + src_extension 
	dst_file.close()
	pb_header_file.close()

else: # no valid file extension
	print "use a .proto file"

# close file
#args.file.close()
