package migl.lisp.operator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

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

import migl.lisp.LispElement;
import migl.lisp.LispError;
import migl.lisp.LispFactory;
import migl.lisp.LispImpl;
import migl.util.ConsList;
import migl.util.ConsListFactory;

public class DefineOperatorOwnTest {
	
	private LispImpl interpreter;

	@Before
	public void init() {
		interpreter = (LispImpl) LispFactory.makeIntepreter();
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberDefine() throws LispError {
		interpreter.eval("(define var)");
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberSet() throws LispError {
		interpreter.eval("(define var 8)");
		interpreter.eval("(define set!)");
	}
	
	@Test(expected = LispError.class)
	public void testIncorrectNumberLambda() throws LispError {
		interpreter.eval("(define l (lambda (x) (+ x 8)))");
		interpreter.eval("(l)");
	}
	
	@Test
	public void testDefineOperator() throws LispError {
		assertEquals(LispElement.generate(8), interpreter.eval("(define var 8)"));
		assertEquals(LispElement.generate(4.6), interpreter.eval("(define iable 4.6)"));
		assertEquals(LispElement.generate(8), interpreter.eval("var"));
		assertEquals(LispElement.generate(4.6), interpreter.eval("iable"));
		assertEquals(LispElement.generate(12.6), interpreter.eval("(+ var iable)"));
	}
	
	@Test(expected = LispError.class)
	public void testDefineOperatorException1() throws LispError {
		interpreter.eval("(define * 8)");
	}
	
	@Test(expected = LispError.class)
	public void testDefineOperatorException2() throws LispError {
		interpreter.eval("(define #f 8)");
	}
	
	@Test(expected = LispError.class)
	public void testDefineOperatorException3() throws LispError {
		interpreter.eval("(define var)");
	}
	
	@Test
	public void testSetOperator() throws LispError {
		assertEquals(LispElement.generate(8), interpreter.eval("(define var 8)"));
		assertEquals(LispElement.generate(8), interpreter.eval("var"));
		assertEquals(LispElement.generate(4.8), interpreter.eval("(set! var 4.8)"));
		assertEquals(LispElement.generate(4.8), interpreter.eval("var"));
	}
	
	@Test(expected = LispError.class)
	public void testSetOperatorException1() throws LispError {
		assertEquals(LispElement.generate(4.8), interpreter.eval("(set! var 4.8)"));
	}
	
	@Test(expected = LispError.class)
	public void testSetOperatorException2() throws LispError {
		assertEquals(LispElement.generate(4.8), interpreter.eval("(set! * 4.8)"));
	}
	
	@Test(expected = LispError.class)
	public void testSetOperatorException3() throws LispError {
		assertEquals(LispElement.generate(4.8), interpreter.eval("(set! #f 4.8)"));
	}
	
	@Test(expected = LispError.class)
	public void testSetOperatorException4() throws LispError {
		interpreter.eval("(define var 8)");
		interpreter.eval("(set! var)");
	}
	
	@Test
	public void testLambda() throws LispError {
		assertEquals(LispElement.generate("lambda (var) (+ var 1)"), interpreter.eval("(define plus1 (lambda (var) (+ var 1)))"));
		assertEquals(LispElement.generate(101), interpreter.eval("(plus1 100)"));
		assertEquals(LispElement.generate(1), interpreter.eval("(define n 1)"));
		assertEquals(LispElement.generate("lambda (var) (- var n)"), interpreter.eval("(define moinsn (lambda (var) (- var n)))"));
		assertEquals(LispElement.generate(99), interpreter.eval("(moinsn 100)"));
		assertEquals(LispElement.generate("lambda (var) (if (> var 0) (plus1 var) (moinsn var))"), interpreter.eval("(define plusmoins (lambda (var) (if (> var 0) (plus1 var) (moinsn var))))"));
		assertEquals(LispElement.generate(101), interpreter.eval("(plusmoins 100)"));
		assertEquals(LispElement.generate(-11), interpreter.eval("(plusmoins -10)"));
	}
	
	@Test(expected = LispError.class)
	public void testMalformedLambda1() throws LispError {
		assertEquals(LispElement.generate("lambda var (+ var 1)"), interpreter.eval("(define plus1 (lambda (var)))"));
		interpreter.eval("(plus1 100)");
	}
	
	@Test(expected = LispError.class)
	public void testMalformedLambda2() throws LispError {
		assertEquals(LispElement.generate("lambda var (+ var 1)"), interpreter.eval("(define plus1 (lambda var (+ var 1)))"));
		interpreter.eval("(plus1 100)");
	}
	
	@Test(expected = LispError.class)
	public void testLambdaException1() throws LispError {
		assertEquals(LispElement.generate("lambda (var) (+ var 1)"), interpreter.eval("(define plus1 (lambda (var) (+ var 1)))"));
		assertEquals(LispElement.generate(101), interpreter.eval("(plus1 100 101)"));
	}
	
	@Test(expected = LispError.class)
	public void testLambdaException2() throws LispError {
		assertEquals(LispElement.generate(101), interpreter.eval("(moins1 100)"));
	}
	
	@Test(expected = LispError.class)
	public void testLambdaException3() throws LispError {
		LispOperator lo = new DefineOperator();
		lo.apply(interpreter.getEval(), "nop", null);
	}
	
	@Test(expected = LispError.class)
	public void testLambdaException4() throws LispError {
		assertEquals(LispElement.generate("function (param) (* param 2)"), interpreter.eval("(define azerty (function (param) (* param 2)))"));
		interpreter.eval("(azerty 4)");
	}
	
	@Test
	public void testIsLambda1() throws LispError {
		interpreter.eval("(define var 8)");
		interpreter.eval("(define var2 (+ 2 6))");
		interpreter.eval("(define func (lambda  (x) (quote x)))");
		interpreter.eval("(define aaa (function (param) (* param 2)))");
		ConsList<Object> param = ConsListFactory.singleton("param");
		ConsList<Object> func = ConsListFactory.asList("*", "param", 2.0);
		ConsList<Object> lambda = ConsListFactory.asList(8.4, param, func);
		ConsList<Object> lisp = ConsListFactory.asList("define", "bbb", lambda);
		interpreter.evaluate(lisp);
		assertFalse(interpreter.getEval().getDefine().isLambda(new Object()));
		assertFalse(interpreter.getEval().getDefine().isLambda("var"));
		assertFalse(interpreter.getEval().getDefine().isLambda("var2"));
		assertTrue(interpreter.getEval().getDefine().isLambda("func"));
		assertFalse(interpreter.getEval().getDefine().isLambda("aaa"));
		assertFalse(interpreter.getEval().getDefine().isLambda("bbb"));
	}
	
	@Test
	public void testIsLambda2() throws LispError {
		LispOperator define = new DefineOperator();
		ConsList<Object> cl = ConsListFactory.asList("nullFunc", null);
		define.apply(interpreter.getEval(), "define", cl);
		assertFalse(interpreter.getEval().getDefine().isLambda("nullFunc"));
	}
	
	@Test
	public void testEval() throws LispError {
		ConsList<Object> cl = ConsListFactory.nil();
		cl = cl.prepend(8).prepend("x").prepend("lambda");
		cl = ConsListFactory.singleton(cl);
		assertEquals(LispElement.generate("lambda x 8"), interpreter.getEval().getDefine().eval(cl));
		assertEquals(LispElement.generate("lambda x 8"), interpreter.getEval().getDefine().eval((Object) cl));
	}
	
	@Test(expected = LispError.class)
	public void testEvalException() throws LispError {
		interpreter.getEval().getDefine().eval(new Double(4.8));
	}
	
	@Test
	public void testLCPF() throws LispError {
		interpreter.eval("(define lcpf (lambda (x y) (lambda (z) (* x (+ y z)))))");
		assertEquals(LispElement.generate(8), interpreter.eval("((lcpf 2 1) 3)"));
	}
	
	@Test
	public void testLCPF2() throws LispError {
		interpreter.eval("(define lcpf2 (lambda (x) (lambda (y) (lambda (z) (* x (+ y z))))))");
		assertEquals(LispElement.generate(8), interpreter.eval("(((lcpf2 2) 1) 3)"));
	}
	
	@Test
	public void testGenerateRandomName1() {
		DefineOperator define = new DefineOperator();
		String str1 = define.generateRandomName();
		String str2 = define.generateRandomName();
		assertEquals(8, str1.length());
		assertEquals(8, str2.length());
		assertNotEquals(str1, str2);
	}
	
	@Test
	public void testGenerateRandomName2() {
		DefineOperator define = new DefineOperator();
		for(int i = 0; i < 100; i++) {
			String str = define.generateRandomName();
			for(char c : str.toCharArray()) {
				assertTrue(c >= 'a');
				assertTrue(c <= 'z');
			}
		}

	}

}
