# encoding=utf-8
#################################
# Author: ideawu
# Link: http://www.ideawu.net/
#################################

import sys, os, shutil, datetime
import antlr3
import antlr3.tree
from ExprLexer import ExprLexer
from ExprParser import ExprParser

class CpyEngine:
	found_files = set()

	def find_imports(self, srcfile, base_dir):
		#print '  file', srcfile
		srcfile = os.path.realpath(srcfile)
		if srcfile in self.found_files:
			return set()
		self.found_files.add(srcfile)
		
		fp = open(srcfile, 'rt')
		lines = fp.readlines()
		fp.close()

		imports = []
		for line in lines:
			if line.find('import') == -1:
				continue
			line = line.strip().strip(';');
			ps = line.split();
			if ps[0] != 'import':
				continue
			for p in ps[ 1 :]:
				p = p.strip(',')
				imports.append(p);

		for p in imports:
			#print 'import ' + p
			self.find_files(p, base_dir);
		return self.found_files

	def find_files(self, member, base_dir):
		ps = member.split('.')
		last = ps.pop(-1)
		path = base_dir + '/' + '/'.join(ps)
		if last == '*':
			if os.path.isdir(path):
				fs = os.listdir(path)
				for f in fs:
					if f.endswith('.cpy'):
						file = os.path.realpath(path + '/' + f)
						self.find_imports(file, path)
		else:
			file = path + '/' + last + '.cpy'
			if os.path.isfile(file):
				self.find_imports(file, path)
		
	def compile(self, srcfile, base_dir, output_dir):
		srcfile = os.path.realpath(srcfile)
		base_dir = os.path.realpath(base_dir)
		output_dir = os.path.realpath(output_dir)

		# files = self.find_imports(srcfile, base_dir)
		# files.remove(srcfile)
		# if len(files) > 0:
		# 	files = list(files)
		# 	files.sort()
		# 	#print '  ' + '\n  '.join(files)
		#
		# shead, stail = os.path.split(srcfile)
		# slen = len(shead)
		# for f in files:
		# 	head, tail = os.path.split(f)
		# 	rel_dir = head[slen :]
		# 	self._compile(f, base_dir, output_dir + rel_dir)

		dstfile = self._compile(srcfile, base_dir, output_dir)
		return dstfile
				
	def _compile(self, srcfile, base_dir, output_dir):
		head, tail = os.path.split(srcfile)

		dstfile = os.path.normpath(output_dir + '/' + tail.split('.')[0] + '.py')
		if os.path.exists(dstfile):
			src_mtime = os.path.getmtime(srcfile)
			dst_mtime = os.path.getmtime(dstfile)
			#print src_mtime, dst_mtime
			if src_mtime < dst_mtime:
				return dstfile
		#print 'compile: %-30s=> %s' % (srcfile, dstfile)
		#print 'compile: %-30s=> %s' % (srcfile[len(base_dir)+1:], dstfile[len(base_dir)+1:])

		if not os.path.exists(output_dir):
			os.makedirs(output_dir)
		if not os.path.exists(output_dir + '/__init__.py'):
			fp = open(output_dir + '/__init__.py', 'w')
			fp.close()

		#fp = codecs.open(sys.argv[1], 'r', 'utf-8')
		fp = open(srcfile, 'r')
		char_stream = antlr3.ANTLRInputStream(fp)
		lexer = ExprLexer(char_stream)
		tokens = antlr3.CommonTokenStream(lexer)

		parser = ExprParser(tokens)
		r = parser.prog()

		# this is the root of the AST
		root = r.tree
		#print (root.toStringTree())
		#print '-------'

		nodes = antlr3.tree.CommonTreeNodeStream(root)
		nodes.setTokenStream(tokens)
		from Eval import Eval
		eval = Eval(nodes)

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

		cpy = CpyBuilder(dstfile, base_dir, output_dir)
		eval.prog(cpy)
		return dstfile


class CpyBuilder:
	compiled_files = set()

	def __init__(self, dstfile, base_dir, output_dir):
		self.vars = -1
		self.if_depth = 0
		self.block_depth = 0
		self.switch_expr_stack = []
		self.switch_continue_stack = []
		self.class_stack = []
		self.class_names = [];
		self.constructed = False;

		self.base_dir = base_dir
		self.output_dir = output_dir

		self.fp = open(dstfile, 'w')

		self.write('# encoding=utf-8\n')
		self.write('# Generated by cpy\n');
		self.write('# ' + datetime.datetime.now().isoformat(' ') + '\n');
		self.write('import os, sys\n')
		self.write('from sys import stdin, stdout\n\n')

	def tmp_var(self, name = ''):
		self.vars += 1
		return '_cpy_%s_%d' %(name, self.vars)

	def close(self):
		self.fp.close()

	def write(self, text):
		text = text.encode('utf-8')
		self.fp.write(text)
		# debug
		#sys.stdout.write(text)

	def indent(self):
		return '\t' * self.block_depth

	def _compile_dir(self, rel_path):
		mods = []
		files = os.listdir(self.base_dir + '/' + rel_path)
		for f in files:
			if f.endswith('.cpy'):
				mods.append(f[0: -4])
			if f.endswith('.py'):
				mods.append(f[0: -3])
			self._compile(rel_path, f)
		return mods

	def _compile(self, rel_path, f):
		base_dir = os.path.normpath(self.base_dir + '/' + rel_path)
		srcfile = os.path.normpath(base_dir + '/' + f)
		output_dir = os.path.normpath(self.output_dir + '/' + rel_path)
		#print base_dir, output_dir, f, rel_path;

		if f.endswith('.py'):
			head, tail = os.path.split(f)
			#print 'copy:    %-30s=> %s' % (srcfile, output_dir + '/' + tail)
			shutil.copy(srcfile, output_dir + '/' + tail)
		elif f.endswith('.cpy'):
			self.write('#### start cpy import ###\n');
			self.write(self.indent())
			self.write('from engine import CpyEngine\n')
			self.write(self.indent())
			self.write('cpy = CpyEngine()\n')
			self.write(self.indent())
			self.write('dstfile = cpy.compile(\'' + srcfile + '\', \'' + rel_path + '\', \'' + output_dir + '\')\n')
			self.write('#### end cpy import ###\n');

			if srcfile in self.compiled_files:
				return
			self.compiled_files.add(srcfile)
			#e = CpyEngine()
			#d = e.compile(srcfile, base_dir, output_dir)

	def op_import(self, member, all):
		ps = member.split('.')
		package = []
		while True:
			if len(ps) == 0:
				break
			p = ps.pop(0)
			package.append(p)
			rel_path = '/'.join(package);
			path = self.base_dir + '/' + rel_path
			if os.path.isdir(path):
				if len(ps) == 0:
					mods = self._compile_dir(rel_path)
					if all == '*':
						for m in mods:
							self.write(self.indent())
							self.write('from %s import %s\n' %(member, m))
					else:
						self.write(self.indent())
						self.write('import %s\n' % member)
					break
			elif os.path.isfile(path + '.cpy') or os.path.isfile(path + '.py'):
				filename = os.path.basename(path)
				rel_path = '/'.join(package[ : -1]);
				if os.path.isfile(path + '.cpy'):
					self._compile(rel_path, filename + '.cpy')
				else:
					self._compile(rel_path, filename + '.py')
				if len(ps) == 0:
					if all == '*':
						self.write(self.indent())
						self.write('from %s import *\n' % member)
					else:
						self.write(self.indent())
						self.write('import %s\n' % member)
					break
				elif len(ps) == 1:
					mod = '.'.join(package)
					cls = ps[-1]
					self.write(self.indent())
					self.write('from %s import %s\n' %(mod, cls))
				else:
					# error
					print ("Cpy error: invalid module '%s'" % member)
					sys.exit(0)
				break
			else:
				self.write(self.indent())
				if all == '*':
					self.write('from %s import *\n' % member)
				else:
					ps = member.split('.')
					if len(ps) == 1:
						self.write('import %s\n' % member)
					else:
						self.write('from %s import %s\n' %('.'.join(ps[0 : -1]), ps[-1]))
				break

	def block_enter(self):
		self.block_depth += 1
		self.write(self.indent() + 'pass\n')

	def block_leave(self):
		self.block_depth -= 1

	def if_enter(self):
		self.write('\n')
		self.write(self.indent())
		self.if_depth += 1

	def if_leave(self):
		self.if_depth -= 1

	def op_if(self, expr):
		self.write('if %s:\n' % expr)

	def op_else(self):
		self.write(self.indent() + 'else:\n')

	def op_else_if(self):
		self.write(self.indent() + 'el')

	def stmt(self, text):
		self.write(self.indent() + text + '\n')

	def op_assign(self, id, val, op):
		text = '%s %s %s' % (id, op, val)
		return text

	def op_inc(self, id):
		return id + ' += 1';

	def op_dec(self, id):
		return id + ' -= 1';

	def op_call(self, text):
		self.write(self.indent() + text + '\n')

	def op_print(self, text):
		self.write(self.indent())
		self.write('print %s\n' % text)

	def op_printf(self, format, text):
		self.write(self.indent())
		if text == None:
			self.write('sys.stdout.write(%s)\n' % (format))
		else:
			self.write('sys.stdout.write(%s %% (%s))\n' % (format, text))

	def op_while(self, expr):
		self.write('\n')
		self.write(self.indent())
		self.write('while %s:\n' % expr)

	def op_do_while_enter(self):
		self.write('\n')
		self.write(self.indent())
		self.write('while True:\n')

	def op_do_while_leave(self, expr):
		self.write('\n')
		self.block_depth += 1
		self.write(self.indent())
		self.write('if %s:\n' % expr)
		self.block_depth += 1
		self.write(self.indent())
		self.write('continue')
		self.block_depth -= 1
		self.write('\n')
		self.write(self.indent())
		self.write('break')
		self.block_depth -= 1

	def op_switch_enter(self, expr):
		self.write('\n')
		self.switch_expr_stack.append(expr)
		var = '_continue_%d' % len(self.switch_expr_stack)
		self.switch_continue_stack.append(var)

		self.write(self.indent() + '# {{{ switch: ' + expr + '\n')
		self.write(self.indent())
		self.write(var + ' = False\n')
		self.write(self.indent())
		self.write('while True:\n')
		self.block_depth += 1

	def op_switch_leave(self):
		self.write(self.indent() + 'break\n')
		var = self.switch_continue_stack[-1]
		self.write(self.indent())
		self.write('if %s:\n' % var)
		self.block_depth += 1
		self.write(self.indent())
		self.write('continue\n')
		self.block_depth -= 1
		self.block_depth -= 1
		self.write(self.indent() + '# }}} switch\n\n')

		self.switch_expr_stack.pop()
		self.switch_continue_stack.pop()

	def op_case_enter(self):
		self.write(self.indent())
		self.write('if False')
		self.block_depth += 1

	def op_case_test(self, expr):
		self.write(' or ((%s) == %s)' % (self.switch_expr_stack[-1], expr))

	def op_case(self):
		self.write(':\n')
		self.write(self.indent())
		self.write('pass\n')

	def op_case_leave(self):
		self.block_depth -= 1

	def op_break(self):
		self.write(self.indent())
		self.write('break\n')

	def op_continue(self):
		if self.switch_expr_stack:
			var = self.switch_continue_stack[-1]
			self.write(self.indent())
			self.write(var + ' = True\n')
			self.write(self.indent())
			self.write('break\n')
		else:
			self.write(self.indent())
			self.write('continue\n')

	def op_return(self, expr):
		self.write(self.indent())
		if expr == None: expr = ''
		self.write('return %s\n' % expr)


	def op_default_enter(self):
		self.write(self.indent() + '### default\n')

	def op_default_leave(self):
		pass

	def op_function(self, id, params):
		self.write('\n')
		if len(self.class_stack) > 0:
			# in class
			if params == None or params == '':
				params = 'this'
			else:
				params = 'this, ' + params
		else:
			if params == None:
				params = ''
		self.write(self.indent() + 'def ' + id + '(' + params + '):\n')

	def op_foreach(self, expr, k, vals):
		self.write('\n')
		tmp_var_ref = self.tmp_var('r')
		tmp_var_l = self.tmp_var('l')
		tmp_var_k = self.tmp_var('k')
		tmp_var_is_dict = self.tmp_var('b')
		self.write(self.indent())
		self.write('%s = %s = %s\n' %(tmp_var_ref, tmp_var_l, expr))

		self.write(self.indent())
		self.write('if type(%s).__name__ == \'dict\': %s=True; %s=%s.iterkeys()\n' %(tmp_var_ref, tmp_var_is_dict, tmp_var_l, tmp_var_ref))
		self.write(self.indent())
		self.write('else: %s=False;' %tmp_var_is_dict)
		if k != None:
			self.write('%s=-1' %k)
		self.write('\n')

		self.write(self.indent())
		self.write('for %s in %s:\n' %(tmp_var_k, tmp_var_l))
		if k == None:
			self.block_depth += 1
			self.write(self.indent())
			self.write('if %s: %s=%s[%s]\n' %(tmp_var_is_dict, vals, tmp_var_ref, tmp_var_k))
			self.write(self.indent())
			self.write('else: %s=%s\n' %(vals, tmp_var_k))
			self.block_depth -= 1
		else:
			self.block_depth += 1
			self.write(self.indent())
			self.write('if %s: %s=%s; %s=%s[%s]\n' %(tmp_var_is_dict, k, tmp_var_k, vals, tmp_var_ref, tmp_var_k))
			self.write(self.indent())
			self.write('else: %s += 1; %s=%s\n' %(k, vals, tmp_var_k))
			self.block_depth -= 1

	def op_throw(self, expr):
		self.write(self.indent())
		self.write('raise %s\n' % expr)

	def op_try(self):
		self.write(self.indent())
		self.write('try:\n')

	def op_catch(self, type, var):
		self.write(self.indent())
		if var == None:
			self.write('except %s:\n' % type)
		else:
			self.write('except %s , %s:\n' %(type, var))

	def op_finally(self):
		self.write(self.indent())
		self.write('finally:\n')

	def op_class_enter(self, name, parent):
		self.class_stack.append([])
		self.class_names.append(name)
		self.constructed = False;
		self.parent = parent;

		self.write(self.indent())
		if parent == None:
			self.write('class %s(object):\n' % name)
		else:
			self.write('class %s(%s):\n' % (name, parent))
		self.block_depth += 1
		self.write(self.indent())
		self.write('pass\n')

	def op_class_leave(self):
		if not self.constructed:
			self.op_construct('');
		self.class_stack.pop()
		self.class_names.pop()
		self.write('\n')
		self.block_depth -= 1

	def op_var_def(self, is_static, id, val):
		if is_static:
			self.write(self.indent())
			if val == None:
				s = '%s = None' % id
			else:
				s = '%s = %s' % (id, val)
			self.write(s)
		else:
			if val == None:
				s = 'this.%s = None' % id
			else:
				s = 'this.%s = %s' % (id, val)
			self.class_stack[-1].append(s)

	def op_construct(self, params):
		self.constructed = True;
		self.write('\n')

		self.op_function('__init__', params)
		self.block_depth += 1
		if self.parent and self.parent != 'object':
			self.write(self.indent())
			self.write('super(' + self.class_names[-1] + ', this).__init__(' + params + ')\n')
		for s in self.class_stack[-1]:
			self.write(self.indent())
			self.write(s + '\n')

		self.block_depth -= 1
