#!/usr/bin/env python

#This is the loquis reference interpreter
import inspect
from collections import namedtuple
import os,os.path
from functools import partial
import importlib
from platform import system as sysname

platform=sysname()
platform_type=None
if(platform in ['Linux','Windows','FreeBSD','MacOS']):
	platform_type='Desktop'


class LoquisException(Exception):
	pass

class Stack(object):
	def __init__(self):
		self.sdata=[]
		
	def push(self,x):
		self.sdata.append(self.filt(x))
	def pop(self):
		return self.sdata.pop()
	def peek(self,index=-1):
		return self.sdata[-1]

	def extend(self,lst):
		self.sdata.extend([self.filt(x) for x in lst])

	def filt(self,x):
		try:
			return int(x)
		except:
			return x
	def __str__(self):
		return str(self.sdata)
	def __len__(self):
		return len(self.sdata)

Where=namedtuple('Where',['key','value'])
class Interpreter(object):
	def __init__(self,language='en',defaultcontext={},verbose=False):
		self.context=defaultcontext
		self.stack=Stack()
			#todo make the interpreter keep track of its token state for streaming execution
		self.verbose=verbose
		self.language='en'
		self.context['stack']=self.stack.sdata
		self.context['language']=language
		self.context['interpreter']=self
		#load the standard library using the import mechanism
		self.load_python_module('std')
		self._find_modules()

	def run(self,scripttext):
		k=self.tokenize(scripttext)
		p=self.parse(k)
		self.execute(p)

	def options(self,query,options=["Yes","No"]):
		pass

	def load_module(self,modname):
		try:
			self.load_python_module(modname) #check also current directory
		except:
			self.load_loquis_module(modname)
		
		
	def load_python_module(self,modname):
		lang=self.language
	
		m=importlib.import_module('modules.'+modname)
		if('languages' in dir(m) and lang in m.languages):
			importdict=m.languages[lang]
		else:
			importdict={}
			#importdict={}
			#for k,v in dir(m).items():
			#	if(v.importdict[k]=v
		for k,v in importdict.items():
			self.context[k]=v

	def load_loquis_module(self,modname,directory=""):
		fn=os.path.join(directory,modname+'.lq')		
		try:
			t=open(fn).read()
		except:
			raise Exception("Failure to open loquis module "+fn)
		return self.run(t)

	def _find_modules(self):
		thisdir=os.path.dirname(os.path.realpath(__file__))
		moduledir=os.path.join(thisdir,'modules')
		for root, dirs, files in os.walk(moduledir):
			for d in dirs:
				self.load_python_module(d)
			for f in files:
				se=os.path.splitext(f)
				if(se[1]=='.py' and se[0] !='std'):
					self.load_python_module(se[0])
				if(se[1]=='.lq'):
					self.load_loquis_module(se[0],moduledir)
			
	
	def tokenize(self,text):
		quotegroups=text.split('"')
		wordsout=[]
		for i in range(len(quotegroups)):
			g=quotegroups[i]
			if(i % 2 == 1):
				wordsout.append(g.format()) #TODO figure out control characters
			else:
				wl=reduce(lambda gr,rs: gr.replace(rs,' ; '),['\n',';','.','then',','],g)
				#g=g.replace(';',' ; ')
				#g=g.replace('.',' ; ')
				#g=g.replace('then',' ; ');
				#g=g.replace(',',' ; ');
				wordsout.extend(wl.split())
		return wordsout

	def one_token(self,one):
		pass

	def parse(self,tokens):
		tokens.append(';')  #always has a semicolon for find.
	
		outw=[]
		curst=[]
		i=0
		while(i<len(tokens)):
			w=tokens[i]
			if(w=='procedure'):
				try:
					psize=tokens[i:].index('end')
				except:
					raise "Error, no corresponding 'end' found for procedure!"
				pname=tokens[i+1]
				beginprocedure=i+2
				endprocedure=i+psize
				i+=psize+1
				ptokens=self.parse(tokens[beginprocedure:endprocedure])
				self.context[pname]=partial(self.execute,ptokens)
				continue
				
				
			if(w==';'):
				outw.extend(curst[::-1])
				curst=[]
			else:
				curst.append(w)
			i+=1

		return outw


	def _verbprint(self,s):
		if(self.verbose):
			print(s)

	def execute(self,tokens,context=None,stack=None):
		if(not context):
			context=self.context
		if(not stack):
			stack=self.stack
		#push all tokens on the stack.  Should we look them up contextually first? YES for executing RPN this is REQUIRED 
		#Should also figure out control flow stuff here...function definitions at least and looping...
		i=0;
		while(i<len(tokens)):
			T=tokens[i]
			
			self._verbprint("Now processing: "+T)
			
			if(T.lower() in context):
				n=T.lower()
				T=context[T.lower()]
				self._verbprint("Assuming '"+n+"' means "+str(T))
		
			if(hasattr(T, '__call__')):
				try:
					retval=T(context=context,stack=stack)
					if(retval):
						if(not isinstance(retval,list)):
							retval=[retval]
						stack.extend(retval)
				except LoquisException as lq:
					print(lq)
			else:
				stack.push(T)
	
			self._verbprint(stack)
			i+=1


#all loquis ops are defined to return a list...this is a decorator
def command(f):
	aspec=inspect.getargspec(f)
	args=aspec[0]
	varargs=aspec[1]
	kwargs=aspec[2]
	defaults=aspec[3]	#todo: doesn't handle defaults I don't think

	def func(context,stack):
		#todo: how greedy is kwargs?		
		na=0
		cargs=[]
		kwargs={}
		if(defaults):
			ds=list(defaults)
			ds=ds[::-1]
		while(na < len(args)):
			while(len(stack) == 0):
				if(len(ds)):
					stack.push(ds.pop())
				else:
					stack.push("What is "+args[na]+"?")
					context['_ask'](context,stack)

			s=stack.pop()
			if(isinstance(s,Where)):
				kwargs[s.key]=s.value
			else:
				cargs.append(s)
			na+=1
		if(varargs):
			cargs.extend(stack.sdata[::-1])
		r=f(*cargs,**kwargs)
		stack.extend(r if r else [])
		return []
	func.is_loquis_command=True;
	return func


if(__name__=="__main__"):
	import sys
	interp=Interpreter(language='en',defaultcontext={},verbose=False)
	if(len(sys.argv) > 1):
		t=interp.load_loquis_module(sys.argv[1])
	else:
		print("Loquis REPL mode: Type 'quit' to exit.")
		quit=False;
		while(not quit):
			t=raw_input(":D>> ")
			if(t=='quit'):
				break
			else:
				interp.run(t)
				
	
