package migl.lisp;

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

import java.math.BigInteger;

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

import migl.lisp.operator.ComparatorOperator;
import migl.util.ConsList;
import migl.util.ConsListFactory;

public class LispEvalOwnTest {

	private Lisp interpreter;

	@Before
	public void init() {
		interpreter = LispFactory.makeIntepreter();
	}

	@Test(expected = LispError.class)
	public void testIncorrectNumberArgumentNot() throws LispError {
		interpreter.evaluate(interpreter.parse("(not #t #f"));
	}

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

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

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

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

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

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

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

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

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

	@Test
	public void testEvaluateSingleton() throws LispError {
		Object elt = interpreter.parse("8");
		assertEquals(LispElement.generate(8), interpreter.evaluate(elt));
	}
	
	@Test(expected = LispError.class)
	public void testUnknowOperator() throws LispError {
		interpreter.evaluate(interpreter.parse("(gl2 #t)"));
	}
	
	@Test(expected = LispError.class)
	public void testMalformedList() throws LispError {
		ConsList<Object> list = ConsListFactory.nil();
		list = list.prepend(new Object()).prepend("not");
		interpreter.evaluate(list);
	}
	
	@Test
	public void testOriginUnknowOperatorException() {
		try {
			interpreter.evaluate(interpreter.parse("(gl2 #t)"));
			fail("No exception thrown");
		} catch (LispError ex) {
			assertTrue(ex.getCause() instanceof UnsupportedOperationException);
		}
	}
	
	@Test
	public void testOriginMalformedListException() {
		ConsList<Object> list = ConsListFactory.nil();
		list = list.prepend(new Object()).prepend("not");
		try {
			interpreter.evaluate(list);
			fail("No exception thrown");
		} catch (LispError ex) {
			assertTrue(ex.getCause() instanceof IllegalArgumentException);
		}
	}
	
	@Test
	public void testOriginBadArgumentTypeException() {
		try {
			interpreter.evaluate(interpreter.parse("(not 8)"));
			fail("No exception thrown");
		} catch (LispError ex) {
			assertTrue(ex.getCause() instanceof IllegalStateException);
		}
	}
	
	@Test(expected = LispError.class)
	public void testEvaluateIncorrectObject() throws LispError {
		interpreter.evaluate(interpreter);
	}
	
	@Test
	public void testSubList() throws LispError {
		LispElement expected = LispElement.generate(9.8);
		LispElement result = (LispElement) interpreter.evaluate(interpreter.parse("(+ (- 8 4.2) (* 2 3))"));
		assertEquals(expected, result);
	}
	
	@Test
	public void testOperators1() 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(true), interpreter.eval("(> 8 5)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(> 8 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(> 5 8)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(>= 8 5)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(>= 8 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(>= 5 8)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(< 5 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(< 8 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(< 8 5)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(<= 5 8)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(<= 8 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(<= 8 5)"));
		assertEquals(LispElement.generate(true), interpreter.eval("(= 8 8)"));
		assertEquals(LispElement.generate(false), interpreter.eval("(= 8 5)"));
		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(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)"));
	}
	
	@Test
	public void testOperators2( ) throws LispError {
		assertEquals(LispElement.generate("(+ 8 4)"), interpreter.eval("(quote (+ 8 4))"));
		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(-11), interpreter.eval("(- 9 (* 4 5))"));
		assertEquals(LispElement.generate("(4 . 8)"), interpreter.eval("(cons 4 8)"));
		assertEquals(LispElement.generate("(())"), interpreter.eval("(cons () ())"));
		assertEquals(LispElement.generate("(8)"), interpreter.eval("(cons 8 ())"));
		assertEquals(LispElement.generate("(() . 8)"), interpreter.eval("(cons () 8)"));
		assertEquals(LispElement.generate("(8 4)"), interpreter.eval("(cons 8 (cons 4 ()))"));
	}
	
	@Test(expected = LispError.class)
	public void testComparatorOperatorException() throws LispError {
		LispOperator comp = new ComparatorOperator();
		ConsList<Object> cl = ConsListFactory.nil();
		cl = cl.prepend(new BigInteger("8"));
		cl = cl.prepend(4.2);
		comp.apply("_", cl);
	}

}
