package com.github.assisstion.SimpleInterpreter;

import java.util.ArrayList;
import java.util.List;

import com.github.assisstion.SimpleInterpreter.elements.EmptyToken;
import com.github.assisstion.SimpleInterpreter.elements.LazyOperation;
import com.github.assisstion.SimpleInterpreter.elements.LiteralToken;
import com.github.assisstion.SimpleInterpreter.elements.Operation;
import com.github.assisstion.SimpleInterpreter.elements.ProperToken;
import com.github.assisstion.SimpleInterpreter.elements.StringToken;
import com.github.assisstion.SimpleInterpreter.elements.Token;
import com.github.assisstion.SimpleInterpreter.elements.TokenOperation;
import com.github.assisstion.SimpleInterpreter.elements.VariableToken;

/**
 * A recursive parser for converting JavaLisp commands into Objects
 *
 * @author Markus Feng
 */
public class RecursiveParser{

	protected int i = 0;
	protected char[] ca;
	protected Scope scope;

	public RecursiveParser(Scope scope, char[] ca){
		this.scope = scope;
		this.ca = ca;
	}

	public boolean finishedCommand(){
		boolean b = true;
		int count = 0;
		for(char c : ca){
			if(c == '"'){
				b = !b;
			}
			if(b){
				if(c == '('){
					count++;
				}
				else if(c == ')'){
					count--;
				}
			}
		}
		if(count < 0){
			throw new IllegalArgumentException();
		}
		return count == 0;
	}

	public void append(char[] ca2){
		char[] ca1 = ca;
		ca = new char[ca1.length + ca2.length];
		System.arraycopy(ca1, 0, ca, 0, ca1.length);
		System.arraycopy(ca2, 0, ca, ca1.length, ca2.length);
	}

	public void resolve(){
		if(ca[ca.length - 1] == '\\'){
			char[] ca1 = ca;
			ca = new char[ca1.length - 1];
			System.arraycopy(ca1, 0, ca, 0, ca1.length - 1);
		}
		else{
			char[] ca1 = ca;
			ca = new char[ca1.length + 1];
			System.arraycopy(ca1, 0, ca, 0, ca1.length);
			ca[ca1.length] = ' ';
		}
	}

	/*
	 * Lisp-like notation
	 *
	 * (a b c) means function a on arguments b and c
	 * (a) means function a with no arguments
	 * e.g.
	 * (* 3 (+ 2 4 6))
	 */
	public Token parse(){
		//move forward until open paren
		//parse 1st argument as operation
		//move until close paren or another open paren
		//if another open paren, apply recursion
		//otherwise, end token
		if(ca.length == 0){
			return EmptyToken.instance();
		}
		while(ca[i] != '('){
			if(ca[i] == ')'){
				throw new IllegalArgumentException();
			}
			i++;
			if(i >= ca.length){
				return EmptyToken.instance();
			}
		}
		i++;
		while(SimpleInterpreterEnvironment.isWhiteSpace(ca[i])){
			tryIncrementI();
		}
		Operation o;
		if(ca[i] == '('){
			o = new TokenOperation(parse());
			if(i >= ca.length){
				throw new IllegalArgumentException();
			}
		}
		else if(ca[i] == ')'){
			return EmptyToken.instance();
		}
		else{
			StringBuilder sb = new StringBuilder();
			char c = ca[i];
			while(!SimpleInterpreterEnvironment.isWhiteSpace(c) && c != ')'){
				sb.append(c);
				tryIncrementI();
				c = ca[i];
			}
			if(sb.length() > 1 && sb.toString().startsWith("*")){
				o = new LazyOperation(sb.substring(1));
			}
			else{
				String name = scope.getVariableOrString(sb.toString()).toString();
				o = scope.getOperationFromName(
						name);
				if(o == Scope.NO_OPERATION_EXISTS){
					throw new UnsupportedOperationException(name);
				}
			}
		}
		int num = o.numberOfArgs();
		List<Token> tokens = new ArrayList<Token>(num >= 0 ? num * 4 / 3 : 4);
		while(true){
			while(SimpleInterpreterEnvironment.isWhiteSpace(ca[i])){
				tryIncrementI();
			}
			if(ca[i] == ')'){
				i++;
				if(o.literal() >= 0){
					return new LiteralToken(o, tokens.toArray(Token.EMPTY),
							o.literal());
				}
				else{
					return new ProperToken(o, tokens.toArray(Token.EMPTY));
				}
			}
			else if(ca[i] == '('){
				tokens.add(parse());
				if(i >= ca.length){
					throw new IllegalArgumentException();
				}
			}
			else if(ca[i] == '"'){
				tryIncrementI();
				StringBuilder sb = new StringBuilder();
				char c = ca[i];
				while(c != '"'){
					sb.append(c);
					tryIncrementI();
					c = ca[i];
				}
				tokens.add(new StringToken(sb.toString()));
				tryIncrementI();
			}
			else{
				StringBuilder sb = new StringBuilder();
				char c = ca[i];
				while(!SimpleInterpreterEnvironment.isWhiteSpace(c) && c != ')'){
					sb.append(c);
					tryIncrementI();
					c = ca[i];
				}
				tokens.add(new VariableToken(sb.toString()));
			}
		}
	}

	protected void tryIncrementI(){
		i++;
		if(i >= ca.length){
			throw new IllegalArgumentException();
		}
	}
}