package edu.neu.ccs.demeterf.inline;

import edu.neu.ccs.demeterf.Control;
import edu.neu.ccs.demeterf.TU;
import edu.neu.ccs.demeterf.Traversal;
import edu.neu.ccs.demeterf.demfgen.ClassHier.InhrtPair;
import edu.neu.ccs.demeterf.demfgen.ClassHier.InhrtStr;
import edu.neu.ccs.demeterf.demfgen.classes.*;
import edu.neu.ccs.demeterf.demfgen.DemFGenMain;
import edu.neu.ccs.demeterf.lib.List;
import edu.neu.ccs.demeterf.lib.Option;
import edu.neu.ccs.demeterf.lib.ident;

public class SubTyping{
    public static String ObjectName = "Object", WildName = "$$";
    static TypeUse obj = new TypeUse(new ident(ObjectName),new EmptyUseParams()),
                   wild = new TypeUse(new ident(WildName),new EmptyUseParams());
    
    static boolean JAVA_HACK = false;
    static boolean STRICT = false;
    
    static void pl(String s){ System.out.println(s); }
    static void p(String s){ System.out.print(s); }

    static String[][] prims = new String[][]{
        {"Integer","int"}, {"Long","long"}, {"Short","short"},
        {"Double","double"}, {"Float","float"}, {"Boolean","boolean"}
    };
    static List<FieldOrSyntax> nofields = List.create();
    static List<TypeUse> nouses = List.create();
    static List<String> nopars = List.create();
    static List<InhrtPair> primSubs =
        List.<String[]>create(prims).fold(new List.Fold<String[], List<InhrtPair>>(){
        public List<InhrtPair> fold(String[] ex, List<InhrtPair> r){
            return r.push(new InhrtPair(new InhrtStr(ex[0],nopars,nofields),ex[1],nouses))
                    .push(new InhrtPair(new InhrtStr(ex[1],nopars,nofields),ex[0],nouses)); }
    },List.<InhrtPair>create());
    
    List<InhrtPair> tenv;
    public SubTyping(List<CDFile> CD){ tenv = DemFGenMain.subtypes(addFieldClasses(CD)); }
    
    /** Add the subtype relationships for the (implied) to be generated Field Classes for
     *    checking and generating calles for "update" methods */
    public static List<CDFile> addFieldClasses(List<CDFile> CDs){
        List<String> fields = new Traversal(new TU<List<String>>(){
            List<String> mt = List.create();
            public List<String> combine(){ return mt; }
            public List<String> fold(List<String> a, List<String> b){ return a.append(b); }
            
            public List<String> combine(ClassDef c, DoGen d, final ident id, TypeDefParams p, PESubtypeList sts, List<String> fs){
                return fs.map(new List.Map<String, String>(){
                    public String map(String s){ return id+s; }
                });    
            }
            List<String> combine(Field f){ return mt.push("$"+f.getName()); }
        }, Control.bypass(ClassDef.class).removeBypassing(ClassDef.class,"fields")).traverse(CDs);
        try{
            ClassDef fcs = ClassDef.parse(" Fields$any = "+fields.toString(" | ", "")+".");
            return CDs.pop().push(CDs.top().addType(fcs));
        }catch(ParseException pe){
            throw new RuntimeException("Unable to Create Field Class Defs: "+pe.getMessage());
        }
    }
    
    public String toString(){ return "subtypeing{\n"+tenv.toString("\n", "    ")+"\n}"; }
    /** Find the super class of the given Type */
    public TypeUse supertype(final TypeUse t){
        List<InhrtPair> sups = tenv.filter(new List.Pred<InhrtPair>(){
            public boolean huh(InhrtPair p){
                return p.child().equals(""+t.getName());
            }
        });
        if(sups.isEmpty())return obj;
        return TypeUse.makeType(sups.top().parent()+t.getTparams().print());
    }
    
    public TypeUse make__Type(String s){
        try{ return TypeUse.parse(s); }catch(Exception e){
            throw new RTParseException(e.getMessage());    
        }
    }
    
    /** Is the first a subtype (<=) of the second? */
    public boolean subtype(TypeUse t, TypeUse u){
        if(subtype(""+t.getName(),""+u.getName()) &&
                (JAVA_HACK || t.getTparams().length() == u.getTparams().length())){
            if(JAVA_HACK)return true;
            List<TypeUse> ups = u.getTparams().toTUList();
            for(TypeUse tu:t.getTparams().toTUList()){
                if(!subtype(tu,ups.top()))return false;
                ups = ups.pop();
            }
            return true;
        }
        return false;
    }
    public boolean subtype(List<TypeUse> ts, List<TypeUse> us){
        if(ts.isEmpty())return us.isEmpty();
        if(us.isEmpty())return false;
        return (subtype(ts.top(),us.top()) && subtype(ts.pop(),us.pop()));
    }
    /** Are the given actual argument types applicable to the formal types? */
    public boolean applicable(List<TypeUse> form, List<TypeUse> act){
        return applicableStar(form,act.map(new List.Map<TypeUse, Option<TypeUse>>(){
            public Option<TypeUse> map(TypeUse u){ return Option.some(u); } 
        }));
    }
    /** Are the given actual argument types applicable to the formal types? */
    public boolean applicableStar(List<TypeUse> form, List<Option<TypeUse>> act){
        if(form.isEmpty())return !STRICT || act.isEmpty();
        if(act.isEmpty())return false;
        return ((!act.top().isSome() || subtype(act.top().inner(),form.top())) &&
                applicableStar(form.pop(),act.pop()));
    }
    /** Are the given actual argument types applicable to the formal types? */
    public boolean possible(List<TypeUse> form, List<TypeUse> act){
        return possibleStar(form,act.map(new List.Map<TypeUse, Option<TypeUse>>(){
            public Option<TypeUse> map(TypeUse u){ return Option.some(u); } 
        }));
    }
    /** Are the given actual argument types applicable to the formal types? */
    public boolean possibleStar(List<TypeUse> form, List<Option<TypeUse>> act){
        if(form.isEmpty())return !STRICT || act.isEmpty();
        if(act.isEmpty())return false;
        return ((!act.top().isSome() || 
                subtype(act.top().inner(),form.top()) || subtype(form.top(),act.top().inner())) &&
                possibleStar(form.pop(),act.pop()));
    }

    /** Check if a given type name is a subtype of another */
    private boolean subtype(String t, String u){
        if(u.equals(ObjectName) || u.equals(WildName) || t.equals(u))return true;
        ChildMatch mtch = new ChildMatch(t);
        if(primSubs.contains(mtch))
            return u.equals(primSubs.find(mtch).parent());
        
        // Subtype edges *to* the type "t" 
        List<InhrtPair> app = tenv.filter(mtch);
        // See if "u" is one of the *from* types
        return (app.contains(new ParMatch(u)) ||
                // Or check recursively... Note: this is only the primary type, not
                //   any type arguments
                app.contains(new RecMatch(u,this)));
    }
    static class ChildMatch extends List.Pred<InhrtPair>{
        String str;
        ChildMatch(String s){ str = s; }
        public boolean huh(InhrtPair p){ return p.child().equals(str); }
    }
    static class ParMatch extends ChildMatch{
        ParMatch(String s){ super(s); }
        public boolean huh(InhrtPair p){ return p.parent().equals(str); }
    }
    static class RecMatch extends ChildMatch{
        SubTyping sub;
        RecMatch(String s, SubTyping st){ super(s); sub = st; }
        public boolean huh(InhrtPair p){ return sub.subtype(p.parent(),str); }
    }
}