package com.redhat.ceylon.compiler.typechecker.analyzer;

import java.util.List;

import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;

/**
 * Validates the position in which covariant and contravariant
 * type parameters appear in the schemas of declarations.
 * 
 * @author Gavin King
 *
 */
public class TypeArgumentVisitor extends Visitor {
    
    private boolean contravariant = false;
    private Declaration parameterizedDeclaration;
    
    private void flip() {
        contravariant = !contravariant;
    }
    
    @Override public void visit(Tree.ParameterList that) {
        flip();
        super.visit(that);
        flip();
    }
    
    @Override public void visit(Tree.TypeConstraint that) {
        super.visit(that);
        TypeParameter dec = that.getDeclarationModel();
        if (dec!=null) {
            parameterizedDeclaration = dec.getDeclaration();
            flip();
            if (that.getSatisfiedTypes()!=null) {
                for (Tree.Type type: that.getSatisfiedTypes().getTypes()) {
                    check(type, false, null);
                }
            }
            flip();
            parameterizedDeclaration = null;
        }
    }
    
    @Override public void visit(Tree.ParameterDeclaration that) {
        boolean topLevel = parameterizedDeclaration==null;
        if (topLevel) {
            parameterizedDeclaration = that.getParameterModel().getDeclaration();
        }
        super.visit(that);
        check(that.getTypedDeclaration().getType(), false, parameterizedDeclaration);
        if (topLevel) {
            parameterizedDeclaration = null;
        }
    }
    
    @Override public void visit(Tree.InitializerParameter that) {
        boolean topLevel = parameterizedDeclaration==null;
        if (topLevel) {
            parameterizedDeclaration = that.getParameterModel().getDeclaration();
        }
        super.visit(that);
        //TODO!!!!!!!!
//        check(that.getTypedDeclaration().getType(), false, parameterizedDeclaration);
        if (topLevel) {
            parameterizedDeclaration = null;
        }
    }
    
    @Override public void visit(Tree.TypedDeclaration that) {
        super.visit(that);
        if (!(that instanceof Tree.Variable)) {
            check(that.getType(), 
                    that.getDeclarationModel().isVariable(), 
                    that.getDeclarationModel());
        }
    }
    
    @Override public void visit(Tree.ClassOrInterface that) {
        super.visit(that);
        if (that.getSatisfiedTypes()!=null) {
            for (Tree.Type type: that.getSatisfiedTypes().getTypes()) {
                check(type, false, null);
            }
        }
    }
    
    @Override public void visit(Tree.ClassDeclaration that) {
        super.visit(that);
        if (that.getClassSpecifier()!=null) {
            check(that.getClassSpecifier().getType(), false, null);
        }
    }
    
    @Override public void visit(Tree.InterfaceDeclaration that) {
        super.visit(that);
        if (that.getTypeSpecifier()!=null) {
            check(that.getTypeSpecifier().getType(), false, null);
        }
    }
    
    @Override public void visit(Tree.TypeAliasDeclaration that) {
        super.visit(that);
        if (that.getTypeSpecifier()!=null) {
            check(that.getTypeSpecifier().getType(), false, null);
        }
    }
    
    @Override public void visit(Tree.AnyClass that) {
        super.visit(that);
        if (that.getExtendedType()!=null) {
            check(that.getExtendedType().getType(), false, null);
        }
    }
    
    @Override public void visit(Tree.FunctionArgument that) {}

    private void check(Tree.Type that, boolean variable, Declaration d) {
        if (that!=null && (d==null || d.isShared() || d.getOtherInstanceAccess())) {
            ProducedType type = that.getTypeModel();
            if (type!=null) {
                List<TypeParameter> errors = type.checkVariance(!contravariant && !variable, 
                        contravariant && !variable, parameterizedDeclaration);
                displayErrors(that, type, errors);
            }
        }
    }

    private void displayErrors(Tree.Type that, ProducedType type,
            List<TypeParameter> errors) {
        for (TypeParameter td: errors) {
            String var; String loc;
            if ( td.isContravariant() ) {
                var = "contravariant (in)";
                loc = "covariant";
            }
            else if ( td.isCovariant() ) {
                var = "covariant (out)";
                loc = "contravariant";
            }
            else {
                throw new RuntimeException();
            }
            that.addError(var + " type parameter " + td.getName() + 
                    " appears in " + loc + " location in type: " + 
                    type.getProducedTypeName(that.getUnit()));
        }
    }
    
}
@@ -273,16 +273,73 @@
 ameter;%0A
+import com.redhat.ceylon.compiler.typechecker.tree.Node;%0A
 import c
@@ -387,16 +387,16 @@
 e.Tree;%0A
-
 import c
@@ -2221,33 +2221,8 @@
 t);%0A
-        //TODO!!!!!!!!%0A//
     
@@ -2239,32 +2239,30 @@
 that.get
-TypedDeclaration
+ParameterModel
 ().getTy
@@ -2290,32 +2290,38 @@
 rizedDeclaration
+, that
 );%0A        if (t
@@ -4037,123 +4037,244 @@
 null
- && (d==null %7C%7C d.isShared() %7C%7C d.getOtherInstanceAccess())) %7B%0A            ProducedType type = that.getTypeModel();
+) %7B%0A            check(that.getTypeModel(), variable, d, that);%0A        %7D%0A    %7D%0A%0A    private void check(ProducedType type, boolean variable, Declaration d, Node that) %7B%0A        if (d==null %7C%7C d.isShared() %7C%7C d.getOtherInstanceAccess()) %7B
 %0A   
@@ -4586,24 +4586,19 @@
 yErrors(
-Tree.Typ
+Nod
 e that, 

fix variance checking for split dec params