package migl.lisp;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.Before;
import org.junit.Test;

import migl.util.ConsList;
import migl.util.ConsListFactory;

public class LispEvalOwnTest {
	
	private LispImpl interpreter;
	private LispEval eval;

	@Before
	public void init() {
		interpreter = (LispImpl) LispFactory.makeIntepreter();
		eval = interpreter.getEval();
	}
	
	@Test
	public void testConstructWithNull() throws LispError {
		assertNull(eval.evaluate());
	}
	
	@Test(expected = IllegalArgumentException.class)
	public void testSetLispWithNull() throws LispError {
		eval.setLisp(null);
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentQuote() throws LispError {
		eval.setLisp(interpreter.parse("(quote #t #f"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentNot() throws LispError {
		eval.setLisp(interpreter.parse("(not #t #f"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentSup() throws LispError {
		eval.setLisp(interpreter.parse("(> #t)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentSupEqu() throws LispError {
		eval.setLisp(interpreter.parse("(>= #f)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentInf() throws LispError {
		eval.setLisp(interpreter.parse("(< #t)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentInfEqu() throws LispError {
		eval.setLisp(interpreter.parse("(<= #f)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentEqu() throws LispError {
		eval.setLisp(interpreter.parse("(= #t)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentSubstract1() throws LispError {
		eval.setLisp(interpreter.parse("(- 8 5 4)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentSubstract2() throws LispError {
		eval.setLisp(interpreter.parse("(-)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentDivide1() throws LispError {
		eval.setLisp(interpreter.parse("(/ 8)"));
		eval.evaluate();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentDivide2() throws LispError {
		eval.setLisp(interpreter.parse("(/)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentDivide3() throws LispError {
		eval.setLisp(interpreter.parse("(/ 8 5 4)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testDivideByZero() throws LispError {
		eval.setLisp(interpreter.parse("(/ 8 0)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentAbs() throws LispError {
		eval.setLisp(interpreter.parse("(abs 8 8)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentPow() throws LispError {
		eval.setLisp(interpreter.parse("(pow 8)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentMap() throws LispError {
		eval.setLisp(interpreter.parse("(map 8)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testMapException1() throws LispError {
		eval.setLisp(interpreter.parse("(map 8 4)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testMapException2() throws LispError {
		eval.setLisp(interpreter.parse("(define f (lambda (x) (- x x)))"));
		eval.evaluate();
		eval.setLisp(interpreter.parse("(map f 8)"));
		eval.evaluate();
	}
	
	/* --- */
	
	@Test
	public void testEvaluateSingleton() throws LispError {
		eval.setLisp(interpreter.parse("8"));
		assertEquals(LispElement.generate(8), eval.evaluate());
	}
	
	@Test(expected = LispError.class)
	public void testUnknowOperator() throws LispError {
		eval.setLisp(interpreter.parse("(gl2 #t)"));
		eval.evaluate();
	}
	
	@Test(expected = LispError.class)
	public void testMalformedList() throws LispError {
		ConsList<Object> list = ConsListFactory.nil();
		list = list.prepend(new Object()).prepend("not");
		eval.setLisp(list);
		eval.evaluate();
	}
	
	@Test
	public void testOriginMalformedListException() {
		ConsList<Object> list = ConsListFactory.nil();
		list = list.prepend(new Object()).prepend("not");
		try {
			eval.setLisp(list);
			eval.evaluate();
			fail("No exception thrown");
		} catch (LispError ex) {
			assertTrue(ex.getCause() instanceof IllegalArgumentException);
		}
	}
	
	@Test
	public void testOriginBadArgumentTypeException() {
		try {
			eval.setLisp(interpreter.parse("(not 8)"));
			eval.evaluate();
			fail("No exception thrown");
		} catch (LispError ex) {
			assertTrue(ex.getCause() instanceof IllegalStateException);
		}
	}
	
	@Test(expected = LispError.class)
	public void testEvaluateIncorrectObject() throws LispError {
		eval.setLisp(interpreter);
		eval.evaluate();
	}
	
	@Test
	public void testSubList() throws LispError {
		eval.setLisp(interpreter.parse("(+ (- 8 4.2) (* 2 3))"));
		assertEquals(LispElement.generate(9.8), eval.evaluate());
	}
	
	@Test(expected = LispError.class)
	public void testForbiddenName1() throws LispError {
		eval.verifyForbiddenName("#t");
	}
	
	@Test(expected = LispError.class)
	public void testForbiddenName2() throws LispError {
		eval.verifyForbiddenName("8");
	}
	
	@Test(expected = LispError.class)
	public void testForbiddenName3() throws LispError {
		eval.verifyForbiddenName("8.4");
	}
	
	@Test(expected = LispError.class)
	public void testForbiddenName4() throws LispError {
		eval.verifyForbiddenName("+");
	}
	
	@Test
	public void testForbiddenName5() throws LispError {
		eval.verifyForbiddenName("var");
	}
	
	@Test
	public void testEvaluateList() throws LispError {
		ConsList<Object> cl = ConsListFactory.nil();
		cl = cl.prepend(8).prepend(4).prepend("+");
		assertEquals(LispElement.generate(12), eval.evaluateList(cl));
	}
	
	@Test
	public void testEvaluateListLambda() throws LispError {
		LispParser parser = new LispParser("(define fonction (lambda (x) (+ x 2)))");
		eval.setLisp(parser.parse());
		eval.evaluate();
		ConsList<Object> cl = ConsListFactory.nil();
		cl = cl.prepend(8).prepend("fonction");
		assertEquals(LispElement.generate(10), eval.evaluateList(cl));
	}
	
	@Test(expected = LispError.class)
	public void testEvaluateListError() throws LispError {
		ConsList<Object> cl = ConsListFactory.nil();
		cl = cl.prepend(8).prepend("undef");
		assertEquals(LispElement.generate(10), eval.evaluateList(cl));
	}
	
	@Test
	public void testVarNameGetElement() throws LispError {
		LispParser parser = new LispParser("(define . 8)");
		eval.setLisp(parser.parse());
		eval.evaluate();
		assertEquals(LispElement.generate("-"), eval.getElement("-"));
		assertEquals(LispElement.generate(8), eval.getElement("."));
	}
	
	@Test
	public void testOperators() throws LispError {
		assertEquals(LispElement.generate(true), interpreter.eval("(not #f)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(not #t)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(and #t #t)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(and #f #t)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(or #f #t)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(or #f #f)"));
		assertEquals(LispElement.generate(4.2), interpreter.eval("(if #f 8 4.2)"));
		assertEquals(LispElement.generate(8), interpreter.eval("(if #t 8 4.2)"));
		assertEquals(LispElement.generate(8), interpreter.eval("(+ 5 3)"));
		assertEquals(LispElement.generate(8.4), interpreter.eval("(+ 5 3.4)"));
		assertEquals(LispElement.generate(8.8), interpreter.eval("(+ 4.4 4.4)"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(+ 4 4 0.0)"));
		assertEquals(LispElement.generate(16), interpreter.eval("(* 4 2 2)"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(* 4.0 2)"));
		assertEquals(LispElement.generate(10.0), interpreter.eval("(* 2.0 5.0)"));
		assertEquals(LispElement.generate(-8), interpreter.eval("(- 8)"));
		assertEquals(LispElement.generate(-8.4), interpreter.eval("(- 8.4)"));
		assertEquals(LispElement.generate(8), interpreter.eval("(- 16 8)"));
		assertEquals(LispElement.generate(10.0), interpreter.eval("(- 18.0 8)"));
		assertEquals(LispElement.generate(2.0), interpreter.eval("(- 10.0 8.0)"));
		assertEquals(LispElement.generate(-11), interpreter.eval("(- 9 (* 4 5))"));
		assertEquals(LispElement.generate(2), interpreter.eval("(/ 8 4)"));
		assertEquals(LispElement.generate(4.0), interpreter.eval("(/ 12 3.0)"));
		assertEquals(LispElement.generate(10.0), interpreter.eval("(/ 80.0 8.0)"));
		assertEquals(LispElement.generate("(+ 8 4)"), interpreter.eval("(quote (+ 8 4))"));
		assertEquals(LispElement.generate(8), interpreter.eval("(abs 8)"));
		assertEquals(LispElement.generate(8), interpreter.eval("(abs -8)"));
		assertEquals(LispElement.generate(8.4), interpreter.eval("(abs 8.4)"));
		assertEquals(LispElement.generate(8.4), interpreter.eval("(abs -8.4)"));
		assertEquals(LispElement.generate(8.4), interpreter.eval("(abs (- 8.4))"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(pow 2 3)"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(pow 2.0 3)"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(pow 2 3.0)"));
		assertEquals(LispElement.generate(8.0), interpreter.eval("(pow 2.0 3.0)"));
	}
	
	@Test
	public void testMapOperator() throws LispError {
		interpreter.eval("(define g (lambda (x) (+ x x)))");
		assertEquals(LispElement.generate(LispList.nil()), interpreter.eval("(map g (- 8 0))"));
		interpreter.eval("(define h (lambda (x y) (list x y)))");
		assertEquals(LispList.valueOf("(8 16)").toString(), interpreter.eval("(map g (h 4 8))").toString());
	}

}
