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

import static com.redhat.ceylon.compiler.typechecker.tree.Util.name;

import java.util.ArrayList;
import java.util.List;

import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;

/**
 * Bucket for some helper methods used by various
 * visitors.
 * 
 * @author Gavin King
 *
 */
class Util extends Visitor {
    
    static TypedDeclaration getBaseDeclaration(Tree.BaseMemberExpression bme) {
        return (TypedDeclaration) bme.getScope().getMemberOrParameter(bme.getUnit(), 
                name(bme.getIdentifier()));
    }
    
    static TypeDeclaration getBaseDeclaration(Tree.BaseType bt) {
        return (TypeDeclaration) bt.getScope().getMemberOrParameter(bt.getUnit(), 
                name(bt.getIdentifier()));
    }
    
    static TypeDeclaration getBaseDeclaration(Tree.BaseTypeExpression bte) {
        return (TypeDeclaration) bte.getScope().getMemberOrParameter(bte.getUnit(), 
                name(bte.getIdentifier()));
    }
    
    static void checkTypeBelongsToContainingScope(ProducedType type,
            Scope scope, Node that) {
        //TODO: this does not account for types 
        //      inherited by a containing scope!
        //TODO: what if the type arguments don't match?!
        while (scope!=null) {
            if (type.getDeclaration().getContainer()==scope) {
                return;
            }
            scope=scope.getContainer();
        }
        that.addError("illegal use of qualified type outside scope of qualifying type: " + 
                type.getProducedTypeName());
    }

    static List<ProducedType> getTypeArguments(Tree.TypeArguments tal) {
        List<ProducedType> typeArguments = new ArrayList<ProducedType>();
        if (tal instanceof Tree.TypeArgumentList) {
            for (Tree.Type ta: ( (Tree.TypeArgumentList) tal ).getTypes()) {
                ProducedType t = ta.getTypeModel();
                if (t==null) {
                    ta.addError("could not resolve type argument");
                    typeArguments.add(null);
                }
                else {
                    typeArguments.add(t);
                }
            }
        }
        return typeArguments;
    }
    
    static Tree.Statement getLastExecutableStatement(Tree.ClassBody that) {
        List<Tree.Statement> statements = that.getStatements();
        for (int i=statements.size()-1; i>=0; i--) {
            Tree.Statement s = statements.get(i);
            if (s instanceof Tree.ExecutableStatement) {
                return s;
            }
            else {
                if (s instanceof Tree.AttributeDeclaration) {
                    if ( ((Tree.AttributeDeclaration) s).getSpecifierOrInitializerExpression()!=null ) {
                        return s;
                    }
                }
                if (s instanceof Tree.MethodDeclaration) {
                    if ( ((Tree.MethodDeclaration) s).getSpecifierExpression()!=null ) {
                        return s;
                    }
                }
                if (s instanceof Tree.ObjectDefinition) {
                    Tree.ObjectDefinition o = (Tree.ObjectDefinition) s;
                    if (o.getExtendedType()!=null) {
                        ProducedType et = o.getExtendedType().getType().getTypeModel();
                        if (et!=null 
                                && !et.getDeclaration().equals(that.getUnit().getObjectDeclaration())
                                && !et.getDeclaration().equals(that.getUnit().getIdentifiableObjectDeclaration())) {
                            return s;
                        }
                    }
                    if (o.getClassBody()!=null) {
                        if (getLastExecutableStatement(o.getClassBody())!=null) {
                            return s;
                        }
                    }
                }
            }
        }
        return null;
    }
            
    static void checkAssignable(ProducedType type, ProducedType supertype, 
            Node node, String message) {
        if (type==null||supertype==null) {
            node.addError(message);
        }
        else if (!type.isSubtypeOf(supertype)) {
            node.addError(message + ": " + type.getProducedTypeName() + 
                    " is not assignable to " + supertype.getProducedTypeName());
        }
    }

    static void checkAssignable(ProducedType type, ProducedType supertype, 
            TypeDeclaration td, Node node, String message) {
        if (type==null||supertype==null) {
            node.addError(message);
        }
        else if (!type.isSubtypeOf(supertype, td)) {
            node.addError(message + ": " + type.getProducedTypeName() + 
                    " is not assignable to " + supertype.getProducedTypeName());
        }
    }

    static void checkIsExactly(ProducedType type, ProducedType supertype, 
            Node node, String message) {
        if (type==null||supertype==null) {
            node.addError(message + ": type not known");
        }
        else if (!type.isExactly(supertype)) {
            node.addError(message + ": " + type.getProducedTypeName() + 
                    " is not exactly " + supertype.getProducedTypeName());
        }
    }

}

@@ -173,16 +173,81 @@
 .List;%0A%0A
+import com.redhat.ceylon.compiler.typechecker.model.Declaration;%0A
 import c
@@ -903,21 +903,8 @@
     
-return (Typed
 Decl
@@ -910,17 +910,25 @@
 laration
-)
+ result =
  bme.get
@@ -1008,32 +1008,182 @@
 Identifier()));%0A
+        if (result instanceof TypedDeclaration) %7B%0A        %09return (TypedDeclaration) result;%0A        %7D%0A        else %7B%0A        %09return null;%0A        %7D%0A
     %7D%0A    %0A    s
@@ -1247,36 +1247,24 @@
 ) %7B%0A        
-return (Type
 Declaration)
@@ -1262,17 +1262,25 @@
 laration
-)
+ result =
  bt.getS
@@ -1357,32 +1357,180 @@
 Identifier()));%0A
+        if (result instanceof TypeDeclaration) %7B%0A        %09return (TypeDeclaration) result;%0A        %7D%0A        else %7B%0A        %09return null;%0A        %7D%0A
     %7D%0A    %0A    s
@@ -1609,28 +1609,16 @@
         
-return (Type
 Declarat
@@ -1620,17 +1620,25 @@
 laration
-)
+ result =
  bte.get
@@ -1722,24 +1722,172 @@
 tifier()));%0A
+        if (result instanceof TypeDeclaration) %7B%0A        %09return (TypeDeclaration) result;%0A        %7D%0A        else %7B%0A        %09return null;%0A        %7D%0A
     %7D%0A    %0A 

fix #63