package edu.neu.ccs.demeterf.demfgen;

import edu.neu.ccs.demeterf.demfgen.classes.*;
import edu.neu.ccs.demeterf.util.Util;
import edu.neu.ccs.demeterf.demfgen.ClassHier.*;
import edu.neu.ccs.demeterf.demfgen.pcdgp.PCDGPFunc;
import edu.neu.ccs.demeterf.inline.SubTyping;
import edu.neu.ccs.demeterf.lib.List;
import edu.neu.ccs.demeterf.lib.ident;
import edu.neu.ccs.demeterf.demfgen.ClassGen.PackDesc;
import edu.neu.ccs.demeterf.*;
import edu.neu.ccs.demeterf.demfgen.traversals.Travs;

/** Class that encapsulates the class generation portion of DemFGen */
public class ClassGen extends PackGen{
    ClassGen(List<InhrtPair> il, List<BehDef> b, String dg, List<String> opts, List<PCDGPFunc> pcs, PackageDef pkg)
    { super(il,b,dg,opts,pcs,pkg); }
    
    static boolean quiet = Diff.optionSet(Diff.quiet);
    static void p(String s){{ if(!quiet)System.err.print(s); }}

    /** Generate the class (source) files for the given CD */
    public static List<InhrtPair> genClasses(List<CDFile> CD, List<BehDef> BEH, String dir, String dgp,
            List<String> opts, List<InhrtPair> inhrt, List<PCDGPFunc> pcdgps, PackageDef basepkg){
        quiet = Diff.optionSet(Diff.quiet);
        ClassGen cgen = new ClassGen(inhrt, BEH, dgp, opts, pcdgps, basepkg);

        p("     [");
        Travs.TheFactory.makeClassGenTrav(cgen).traverse(CD, PackDesc.start(dir));
        p("]\n");
        return inhrt;
    }

    public static String unlocal(ident id){ return unlocal(id.toString()); }
    public static String unlocal(TypeUse tu){ return unlocal(""+tu); }
    public static String unlocal(TypeDefParams dp){ return unlocal(dp.toString()); }
    
    public static String unlocal(String s){
        return (s.replaceAll("[ ]+", " ").replace('(','<').replace(')','>')
                .replace(SubTyping.WildName,"?").replace('$','.'));    
    }
    
    static List<ArityPair> Primitives = 
        Diff.d.builtIns.map(new List.Map<String,ArityPair>(){
            public ArityPair map(String s){ return new ArityPair(s,0); }
        });

    public static class PackDesc{
        private String dir, header;
        private PackageDef pkg;
        public static PackDesc start(String d){ return new PackDesc(d,new NoPackage(),"",true); }
        private PackDesc(String d, PackageDef p, String head, boolean gen){
            dir = DemFGenMain.pkgdir(d, p, gen); pkg = p;
            header = Preamble.header+head+"\n";
        }
        public PackDesc update(PackageDef p, ImportList i, boolean gen){
            return new PackDesc(dir, p, p+""+i, gen);
        }
        public boolean hasPkg(){ return pkg.hasPkg(); }
        public String getDir(){ return dir; }
        public String nameDot(){ return hasPkg()?pkg.name()+".":""; }
        public String toString(){ return header+"\n"; }
    }

    static class EqStringer extends List.Stringer<String>{
        public String toString(String f, List<String> r){
            return "("+(Diff.isJava()?"((Object)"+f+")":f)+"."+Diff.d.equalsMethod+
            "(oo."+f+"))"+(r.isEmpty()?"":"&&");
        }
    }

    static class HashStringer extends List.Stringer<String>{
        static List<Integer> hashes = List.create(1,3,5,7,13,17,19,23);
        public String toString(String f, List<String> r){
            int which = r.length()%hashes.length();
            return ("("+hashes.lookup(which)+"*("+
                    (Diff.isJava()?"((Object)"+f+")":f)+"."+Diff.d.hashMethod+"()))"+
                    (r.isEmpty()?"":"+"));
        }
    }

    public static class ArityPair{
        String name;
        int arity;
        public ArityPair(String name, int arity){ this.name = name; this.arity = arity; }
        public boolean equals(Object o){
            if(!(o instanceof ArityPair))return false;
            ArityPair oo = (ArityPair)o;
            return name.equals(oo.name);
        }
        public String toString(){ return name+"("+arity+")"; }
    }

    public static class ToList extends FC{
        public ArityPair combine(NameDef d){ return new ArityPair(""+d.getId(), 0); }
        public List<ArityPair> combine(EmptyList e){ return List.<ArityPair>create(); }
        public List<ArityPair> combine(ConsList c, ArityPair a, List<ArityPair> r){
            return r.push(a);
        }
        public List<ArityPair> combine(Object o, List<ArityPair> r){ return r; }
    }
    public static class ToTDList extends ToList{
        public ArityPair combine(TypeDef td){
            return new ArityPair(""+td.name(), td.typeParams().length());
        }
    }
    public static class InhrtFor extends List.Pred<InhrtPair>{
        String cls;
        InhrtFor(String c){ cls = c; }
        public boolean huh(InhrtPair p){ return cls.equals(p.chld); }
    }
    public static class Extnds extends List.Pred<InhrtPair>{
        public boolean huh(InhrtPair p){ return p.extend(); }
    }
    public static class Intfcs extends Extnds{
        public boolean huh(InhrtPair p){ return !super.huh(p); }
    }
}

class PackGen extends Gen{
    PackGen(List<InhrtPair> il, List<BehDef> b, String dg, List<String> opts, List<PCDGPFunc> pcs, PackageDef pkg)
    { super(il,b, dg, opts, pcs, pkg); }

    public PackDesc update(CDFile m, Fields.any f, PackDesc p){
        return p.update(m.getPkg(), m.getImports(), m.getTypes().anyGen());
    }
    public String combine(CDFile m){ return ""; }
    public String combine(List<?> l, String f, String r){ return ""; }
    public String combine(List<?> l){ return ""; }
    public String combine(Impl imp){ return ""; }
}

class Gen extends FC{
    List<InhrtPair> inhrt;
    List<BehDef> beh;
    String dgpMeths;
    boolean parser;
    boolean mutable;
    String fieldPriv;
    List<String> opts;
    List<PCDGPFunc> pcdgps;
    PackageDef basepkg;

    Gen(List<InhrtPair> il, List<BehDef> b, String dg, List<String> ops, List<PCDGPFunc> pcs, PackageDef pkg){
        inhrt = il; beh = b; dgpMeths = dg; opts = ops; pcdgps = pcs;
        parser = !Diff.optionSet(Diff.noparse);
        mutable = Diff.optionSet(Diff.mutable);
        fieldPriv = Diff.optionSet(Diff.pubfields)?Diff.d.pubPriv:Diff.d.protPriv;
        basepkg = pkg;
    }

    String writeFile(String cName, String cls, String header, PackDesc pd){
        ClassGen.p(cName+", ");
        return Util.writeFile(cName, "."+Diff.d.fileSuffix, cls, pd.getDir(), header);
    }
    public String combine(TypeUse e, ident i, String s){ return i+s; }

    //ident combine(Ident i){ return new ident(""+i); }
    public EmptyList combine(EmptyList e){ return e; }

    //StrLTrip combine(ProdType p, StrLTrip lt){ return lt; }
    public StrLTrip combine(FieldList l, StrLTrip.StrTrip tr, StrLTrip tl){
        return tl.add(tr.f, tr.a, tr.t);
    }
    public StrLTrip combine(FieldList l, Syntax s, StrLTrip tl){ return tl; }
    public StrLTrip.StrTrip combine(Field f, ident n, String t){
        return new StrLTrip.StrTrip(t+" "+n, "this."+n+" = "+n+";",""+n);
    }
    public String extensions(String name, final List<String> params){
        List<InhrtPair> 
        inhrtL = inhrt.filter(new ClassGen.InhrtFor(name)),
        extnd = inhrtL.filter(new ClassGen.Extnds()),
        intfc = inhrtL.filter(new ClassGen.Intfcs());
        if(extnd.length() > 1)throw new TE("Too Many Extensions!! For: "+name+" ["+extnd+"]");
        return Diff.d.extensions(name, extnd, intfc, params);
    }

    public StrLTrip superTriples(String name, List<String> params){
        return ClassHier.superFields(params,inhrt,name).foldr(new List.Fold<Field, StrLTrip>(){
            public StrLTrip fold(Field f, StrLTrip st)
            { return st.add(""+f.getType()+" "+f.getName(), "", ""+f.getName());}
        }, new StrLTrip());
    }
    public StrLTrip combine(FieldEmpty e){ return new StrLTrip(); }

    String parseMethods(TypeDefParams p, String n, String tp, DoGen g){
        String method = ".parse_"+n+ParseGen.ProductGen.typeMethod(tp)+"();\n";
        String ovrd = Diff.d.isCS()?"new ":"";
        String parse = Diff.capName("parse(");
        String pkgpre = basepkg.nameDot();
        String newp = "        return new "+pkgpre+"TheParser(";
        return ((parser && g.doParse() && p.length() == 0)?
                "    /** Parse an instance of "+n+" from the given String */\n"+
                "    public static "+ovrd+n+tp+" "+parse+"String inpt) "+
                Diff.d.parseException(pkgpre)+"{\n"+newp+"new "+Diff.d.stringInput+"(inpt))"+method+"    }\n"+
                "    /** Parse an instance of "+n+" from the given Stream */\n"+
                "    public static "+ovrd+n+tp+" "+parse+Diff.d.inputStream+" inpt) "+
                Diff.d.parseException(pkgpre)+"{\n"+newp+"inpt)"+method+"    }\n"+
                "    /** Parse an instance of "+n+" from the given Reader */\n"+
                "    public static "+ovrd+n+tp+" "+parse+Diff.d.inputReader+" inpt) "+
                Diff.d.parseException(pkgpre)+"{\n"+newp+"inpt)"+method+"    }\n":"");
    }
    String pcdgpMethods(final TypeDef td, final List<String> superFs){
        try{
            final TypeDef ntd = ClassHier.addSuperFields(td, inhrt);
            return pcdgps.fold(new List.Fold<PCDGPFunc, String>(){
                public String fold(PCDGPFunc f, String r){
                    return r+new StaticTrav(f.functionObj(superFs), f.control()).<String>traverse(ntd);
                }
            },"");
        }catch(Exception e){
            ClassGen.p("\n !! PCDGPError: "+e.getMessage()+"\n\n");
            System.exit(1);
            return null;
        }
    }
    public String combine(ClassDef td, DoGen g, ident n, TypeDefParams dp, PESubtypeList stl, StrLTrip body, String imp, PackDesc pkg){
        String tpb = Diff.d.bndStr.str(dp),
        cp = Diff.d.constrStr.str(dp),
        up = Diff.d.useStr.str(dp);
        if(!g.doGen())return "";
        boolean abstr = !stl.isEmpty() && (!Diff.optionSet(Diff.concretes) || body.isEmpty());
        StrLTrip supFs = superTriples(""+n,dp.toList());
        StrLTrip allFs = body.append(supFs);
        String supConst = "("+supFs.fNames.toString(", ", "")+")";
        String cls = ("\n\n"+
                "/** Representation of "+n+up+" */\n"+
                "public "+(abstr?"abstract ":"")+"class "+n+tpb+extensions(""+n,dp.toList())+cp+"{\n"+
                body.fieldDefs(fieldPriv+(mutable?"":" "+Diff.d.fieldImmut))+
                "\n"+
                "    /** Construct a(n) "+n+up+" Instance */\n"+
                "    public "+n+"("+allFs.arguments()+")"+
                (!supFs.isEmpty()&&Diff.isCS()?" : base"+supConst:"")+
                "{\n"+(!supFs.isEmpty()&&Diff.isJava()?"        super"+supConst+";\n":"")+
                body.assignments()+(body.assigns.isEmpty()?"":"\n")+
                "    }\n"+
                (stl.isEmpty() && !Diff.optionSet(Diff.noequals)?
                        "    /** Is the given object Equal to this "+n+"? */\n"+
                        "    public"+Diff.d.override+" "+Diff.d.equalsRet+" "+Diff.d.equalsMethod+"(Object o){\n"+
                        "        if(!(o"+Diff.d.instanceCheck(""+n,up)+"))return false;\n"+
                        "        if(o == this)return true;\n"+
                        "        "+n+up+" oo = ("+n+up+")o;\n"+
                        "        return "+(allFs.fNames.isEmpty()?"true":
                            allFs.fNames.toString(new ClassGen.EqStringer()))+";\n"+
                            "    }\n":"")+
                            parseMethods(dp,""+n,up,g)+
                            body.fieldClasses(""+n)+"\n"+
                            DemFGenMain.behBody(this.beh, ""+n)+"\n"+dgpMeths+
                            pcdgpMethods(td,supFs.fNames)+"\n}\n"+
                            (pkg.hasPkg()?Diff.d.classEnd:"")+"\n");
        return writeFile(""+n, cls,""+pkg+Diff.d.basicImport,pkg);
    }

    public String combine(TypeDefList l, String f, String r){ return f+"\n"+r; }
    public String combine(TypeDefEmpty l){ return ""; }

    public String combine(TypeUseCons c, String t, String r){ return ", "+t+r; }
    public String combine(NETypeUseList tu, String t, String r){ return "<"+t+r; }
    public String combine(TypeUseEmpty e){ return ">"; }
    public String combine(UseParams dp, String s){ return s; }
    public String combine(EmptyUseParams dp){ return ""; }

    public String combine(IntfcDef id, DoGen g, ident n, TypeDefParams dp, PESubtypeList stl, PackDesc pkg){
        String tp = Diff.d.bndStr.str(dp);
        if(g.doGen())
            return writeFile(""+n,
                    "/** Interface representation of "+n+tp+" */\n"+
                    "public interface "+n+tp+
                    "{\n"+DemFGenMain.behBody(this.beh, ""+n)+
                    pcdgpMethods(id, List.<String>create())+"\n}\n"+
                    (pkg.hasPkg()?Diff.d.classEnd:"")+"\n",
                    ""+pkg+Diff.d.basicImport,pkg);
        return ""+n;
    }
}