/** 
 * Copyright (c) 2009-2011, The HATS Consortium. All rights reserved. 
 * This file is licensed under the terms of the Modified BSD License.
 */
package org.abs_models.frontend.typesystem;

import static org.junit.Assert.assertEquals;

import org.abs_models.frontend.FrontendTest;
import org.abs_models.frontend.analyser.ErrorMessage;
import org.abs_models.frontend.ast.ClassDecl;
import org.abs_models.frontend.ast.ExpFunctionDef;
import org.abs_models.frontend.ast.FieldDecl;
import org.abs_models.frontend.ast.Model;
import org.abs_models.frontend.ast.ParametricFunctionDecl;
import org.abs_models.frontend.ast.ReturnStmt;
import org.abs_models.frontend.ast.TypeParameterDecl;
import org.abs_models.frontend.typechecker.DataTypeType;
import org.abs_models.frontend.typechecker.KindedName;
import org.abs_models.frontend.typechecker.Type;
import org.abs_models.frontend.typechecker.TypeParameter;
import org.abs_models.frontend.typechecker.UnionType;
import org.junit.Test;

public class TypingTest extends FrontendTest {

    @Test
    public void testContextDecl() {
        Model m = assertParse("class C implements I { I m() { return this; } } interface I { }");
        ClassDecl d = (ClassDecl)m.lookup(new KindedName(KindedName.Kind.CLASS, "C"));
        ReturnStmt s = (ReturnStmt) d.getMethod(0).getBlock().getStmt(0);
        assertEquals(d, s.getRetExp().getContextDecl());
    }

    @Test
    public void testThisTyping() {
        Model m = assertParse("class C implements I { I m() { return this; } } interface I { }");
        ClassDecl d = (ClassDecl) m.lookupModule("UnitTest").getDecl(0);
        ReturnStmt s = (ReturnStmt) d.getMethod(0).getBlock().getStmt(0);
        assertEquals(m.lookupModule("UnitTest").getDecl(1),
                     ((UnionType) s.getRetExp().getType()).getType(0).getDecl());
    }

    @Test
    public void testInterfaceType() {
        Model m = assertParse("interface I { } class C {} { I i = new local C(); I i2 = i; }");
        assertEquals(m.lookupModule("UnitTest").getDecl(0).getType(), getTypeOfNthAssignment(m, 2));
    }

    @Test
    public void testDataTypeBoolLit() {
        Model m = assertParse("{ Bool i = True; }");
        assertEquals(m.getBoolType(), getTypeOfFirstAssignment(m));
    }

    @Test
    public void testDataTypeIntLit() {
        Model m = assertParse("{ Int i = 5; }");
        assertEquals(m.getIntType(), getTypeOfFirstAssignment(m));
    }

    @Test
    public void testDataTypeStringLit() {
        Model m = assertParse("{ String i = \"5\"; }");
        assertEquals(m.getStringType(), getTypeOfFirstAssignment(m));
    }

    @Test
    public void testLetExp() {
        Model m = assertParse("def Bool f() = let (Bool b) = True in b;");
        assertEquals(m.getBoolType(), getFirstFunctionExpr(m).getType());
    }

    @Test
    public void testCase() {
        Model m = assertParse("def Bool f(Bool x) = case x { True => False; False => True; };");
        assertEquals(m.getBoolType(), getFirstFunctionExpr(m).getType());
    }

    @Test
    public void testCase2() {
        Model m = assertParse("def Bool f(Bool x) = case x { True => x; False => x; };");
        assertEquals(m.getBoolType(), getFirstFunctionExpr(m).getType());
    }

    @Test
    public void testFnApp() {
        Model m = assertParse("def Bool f() = f();");
        assertEquals(m.getBoolType(), getFirstFunctionExpr(m).getType());
    }

    @Test
    public void testNew() {
        Model m = assertParse("interface I {} class C implements I {} { I i; i = new local C(); }");
        assertEquals(m.lookup(new KindedName(KindedName.Kind.TYPE_DECL, "UnitTest.I")).getType(),
                ((UnionType) getTypeOfFirstAssignment(m)).getType(0));
    }

    @Test
    public void testFieldUse() {
        Model m = assertParse(" class C { Bool f; Bool m() { return this.f; } }");
        ClassDecl d = (ClassDecl) m.lookup(new KindedName(KindedName.Kind.CLASS, "UnitTest.C"));
        FieldDecl f = d.getField(0);
        ReturnStmt s = (ReturnStmt) d.getMethod(0).getBlock().getStmt(0);
        assertEquals(f.getType(), s.getRetExp().getType());
    }

    @Test
    public void testSyncCall() {
        Model m = assertParse(" interface I { Bool m(); } { I i; i.m(); }");
        assertEquals(m.getBoolType(), getSecondExp(m).getType());
    }

    @Test
    public void testAsyncCall() {
        Model m = assertParse(" interface I { Bool m(); } { I i; i!m(); }");
        assertEquals(m.getFutType(m.getBoolType()), getSecondExp(m).getType());
    }

    @Test
    public void functionTypeParams() {
        Model m = assertParse(" def A f<A>(A a) = a ;");
        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        assertEquals(d.getTypeParameter(0),
                ((TypeParameter) ((ExpFunctionDef) d.getFunctionDef()).getRhs().getType()).getDecl());
    }

    @Test
    public void functionTypeArgs() {
        Model m = assertParse(" def Maybe<A> f<A>() = None ;");
        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        DataTypeType t = (DataTypeType) d.getTypeUse().getType();
        TypeParameter typeArg = (TypeParameter) t.getTypeArg(0);
        assertEquals(typeArg.getDecl(), d.getTypeParameter(0));
    }

    @Test
    public void functionTypeArgs2() {
        Model m = assertParse(" def Maybe<A> f<A>(Maybe<A> o) = o ;");
        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        assertEquals(d.getTypeUse().getType(), ((ExpFunctionDef) d.getFunctionDef()).getRhs().getType());
    }

    @Test
    public void functionTypeArgs3() {
        Model m = assertParse(" def A f<A>(Maybe<A> o) = case o { Just(a) => a; } ;");
        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        TypeParameterDecl typeParameter = d.getTypeParameter(0);
        TypeParameter type = (TypeParameter) ((ExpFunctionDef) d.getFunctionDef()).getRhs().getType();
        TypeParameterDecl decl = type.getDecl();
        assertEquals(typeParameter, decl);
    }

    @Test
    public void functionTypeArgs4() {
        Model m = assertParse(" data Foo<A> = Bar(A,Bool); "
            + "def Bool f<A>(Foo<A> o) = case o { Bar(a,b) => b; } ;");

        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        Type type = ((ExpFunctionDef) d.getFunctionDef()).getRhs().getType();
        assertEquals(m.getBoolType(), type);
    }

    @Test
    public void functionTypeArgs5() {
        Model m = assertParse("def B nth<B>(List<B> list, Int n) = nth(tail(list), n-1) ; ");
        ParametricFunctionDecl d = getLastParametricFunctionDecl(m);
        TypeParameterDecl typeParameter = d.getTypeParameter(0);
        TypeParameter type = (TypeParameter) ((ExpFunctionDef) d.getFunctionDef()).getRhs().getType();
        assertEquals(typeParameter.getName(), type.getDecl().getName());
    }

    @Test
    public void testFutST1() {
        assertTypeOK("interface A {} interface B extends A {} interface I { A mA(); B mB(); } { I o = null; Fut<A> f = o!mB(); }");
    }
    @Test
    public void testFutST2() {
        assertTypeOK("interface A {} interface B extends A {} interface I { A mA(); B mB(); } { I o = null; A f = o.mB(); }");
    }
    @Test
    public void testFutST3() {
        assertTypeOK("interface A {} interface B extends A {} interface I { A mA(); B mB(); } { I o = null; Fut<B> f = o!mB(); A a = f.get;}");
    }

    @Test
    public void test_DuplicateFeature() {
        assertTypeErrors("productline Bar; features A,A;", ErrorMessage.DUPLICATE_FEATURE);
    }
}
