package edu.neu.ccs.demeterf.demfgen;

import edu.neu.ccs.demeterf.ID;
import edu.neu.ccs.demeterf.Control;
import edu.neu.ccs.demeterf.util.CLI;
import edu.neu.ccs.demeterf.demfgen.classes.*;
import edu.neu.ccs.demeterf.demfgen.ClassHier.*;
import edu.neu.ccs.demeterf.lib.List;
import edu.neu.ccs.demeterf.lib.ident;

import java.io.File;


/** This class represents the differences between Java and C#, and a few
 *    other DemFGen constants that are useful across Languages. */
public class Diff{
    // Date updated frequently: MM/DD/YYYY
    public static String buildDate = "05/23/2010";
    public static boolean isJava(){ return d.isJava(); }
    public static boolean isCS(){ return d.isCS(); }
    
    public static String SEP = File.separator;
    
    public static DiffJava d = new DiffJava();
    public static void setDiff(DiffJava dd){ d = dd; }

    /** Option Specs... */
    public final static String
        noparse = "--noparse",
        noparsecc = "--noparsecc",
        nogen = "--nogen",
        noequals = "--noequals",
        concretes = "--concretes",
        noshell = "--noshell",
        mutable = "--mutable",
        dgp = "--dgp",
        pcdgp = "--pcdgp",
        lib = "--lib",
        build = "--build",
        graph = "--graph",
        help = "--help",
        noctrl = "--nocontrol",
        parallel = "--parallel",
        windows = "--windows",
        showcd = "--show",
        pubfields = "--publicfields",
        reference = "--reference",
        quiet = "--quiet",
        flatdirs = "--flatdirs",
        /** New Inlined ClassGen... */
        newinlined = "--newinlined",
        threshhold= "--threshhold";
    
    public final static List<String> validOptions = List.create(
            noparse,noparsecc,nogen,noequals,concretes,noshell,mutable,dgp,pcdgp,lib,
            build,graph,help,noctrl,parallel,windows,showcd,pubfields,reference,quiet,
            newinlined,flatdirs);
    
    /** Global access to the options */
    static List<String> options;
    /** Set the global DemFGen Options */
    public static void storeOptions(List<String> opt){ options = opt; }
    /** See if the DemFGen option is set */
    public static boolean optionSet(String opt){
        return options.contains(opt);
    }
    /** Get the ':' separated list attached to the given Optionoption */
    public static String[] getOptionList(String opt){
        return CLI.separateOption(opt, options);
    }
    /** Get a list of DLL references from a command line */
    public static String getReferences(){
        return List.create(CLI.separateOption(Diff.reference, Diff.options))
                      .push("DemeterF.dll").toString(" ","-r:");
    }
    /** If C#, then leave the given class name capitalized, for Java we make the first
     *    character lower case to match the normal case */
    public static String capName(String s){
        return (Diff.isJava()?Character.toLowerCase(s.charAt(0)):
            Character.toUpperCase(s.charAt(0)))+s.substring(1);
    }
    
    public static class DiffJava{
        public String
            inherit = "extends",
            fieldClassMod = " static",
            fieldClassPost = "",
            fieldImport = ".Fields",
            pubPriv = "public",
            protPriv = "protected",
            fieldImmut = "final",
            equalsRet = "boolean",
            equalsMethod = "equals",
            hashRet = "int",
            hashMethod = "hashCode",
            fileSuffix = "java",
            override = "",
            toStringMeth = "toString",
            packageStmt = "package",
            importStmt = "import",
            runtimeException = "RuntimeException",
            stringInput = "java.io.StringReader",
            inputStream = "java.io.InputStream",
            inputReader = "java.io.Reader",
            parserGen = "JavaCC",
            compilerName = "JavaC",
            packagePrefix = ";",
            classEnd= "",
            basicImport = "",
            parserImport = importAs("edu.neu.ccs.demeterf","Fields")+
                           importAs("edu.neu.ccs.demeterf.lib","ident")+
                           importAs("edu.neu.ccs.demeterf.lib","verbatim"),
            parserPreamble = Preamble.parserJava,
            parserBody = Preamble.parserBodyJava,
            escapeMethods = Preamble.escapeMethodsJava;

        public char genericSep = '$';
        
        public boolean isJava(){ return true; }
        public boolean isCS(){ return false; }
        
        public List<String> primitives = List.create("byte","short","int","long","float","double",
                "char","boolean","String","ident","verbatim");
        public List<String> boxed = List.create("Byte", "Short", "Integer", "Long", "Float", "Double",
                "Character", "Boolean","String","ident","verbatim");

        public List<String> builtIns = primitives
            .append(boxed)
            .append(List.create("ID", "TP", "TU", "FC"));

        public String unbox(String t){
            if(!Diff.d.boxed.contains(t))return t;
            return Diff.d.primitives.lookup(Diff.d.boxed.index(t));
        }
        public String box(String t){
            if(!Diff.d.primitives.contains(t))return t;
            return Diff.d.boxed.lookup(Diff.d.primitives.index(t));
        }
        public String parseException(String pkg){ return "throws "+pkg+"ParseException"; }
        
        public String instanceCheck(String name, String tp){ return " instanceof "+name; }
        public String classType(String cls, String tp){ return cls+".class"; }
        public String paramMethod(String meth, String p){ return "<"+box(p)+">"+meth; }
        public String paramMethodDef(String meth, String ret, String p){ return "<"+box(p)+"> "+ret+" "+meth; }
        public String varArgs(String t, String arg){ return t+" ... "+arg; }
        
        // Separated class/interface inheritance
        public String extensions(String type, List<InhrtPair> extnd, List<InhrtPair> intfc, List<String> params){
            String 
                exStr = (extnd.isEmpty()?"":" extends ")+extnd.toString(new InhrtHelp(type,params)),
                inStr = (intfc.isEmpty()?"":" implements ")+intfc.toString(new InhrtHelp(type,params));
            return exStr+inStr;
        }
        public String makeParseMsg = " ** Running JavaCC...";
        public String makeBuildMsg = " ** Running JavaC...";
        public String makeInfo = "Run JavaC after generating the files";

        public String importAs(String pkg, String cls){ return importStmt+" "+pkg+"."+cls+";\n"; }
        public String importAll(String pkg){ return importStmt+" "+pkg+".*;\n"; }
        
        public String[] mkParseCmd(String dir){
            return new String[]{"bash","-c","javacc -OUTPUT_DIRECTORY=\""+dir+"\" \""+dir+"\"/theparser.jj"};
        }
        public String[] mkParseCmdNoShell(String dir){
            return new String[]{"java","javacc","-OUTPUT_DIRECTORY="+dir,dir+SEP+"theparser.jj"};
        }
        static class AddSuff extends List.Map<String,String>{
            String suff; boolean quote;
            AddSuff(String s, boolean q){ suff = s; quote = q; }
            public String map(String s){ return "\""+s+"\""+suff; }
        }
        public String addSuff(List<String> dirs, String suff, boolean quote){
            return dirs.map(new AddSuff(suff,quote)).toString(" ","");
        }
        public String[] buildCmd(List<String> dirs, boolean lib, String name){
            return new String[]{"bash","-c","javac "+addSuff(dirs,"/*.java",true)+
                    (lib?" && jar -cf "+name+" "+addSuff(dirs,"/*.class",true):"")};
        }
        public String[] mkWinParseCmd(String dir){
            return new String[]{"java","javacc", "-OUTPUT_DIRECTORY="+dir+"",""+dir+"\\theparser.jj"};
        }
        public String[] buildWinCmd(List<String> dirs, boolean lib, String name){
            return new String[]{"cmd","/C","javac "+addSuff(dirs,"\\*.java",false)+
                    (lib?" && jar -cf "+name+" "+addSuff(dirs,"\\*.class",false):"")};
        }
        
        public ConstrStr constrStr = new ConstrStr();
        public BoundStr bndStr = new BoundStr();
        public BoundStr useStr = new UseStr();
        
        public static class ConstrStr extends ID{
            String str(TypeDefParams dp){ return ""; }
        }
        public static class BoundStr extends ConstrStr{
            String combine(NoBound b){ return ""; }
            String combine(ClassBound b, TypeUse i){ return " extends "+i; }
            String combine(NameDef d, ident i, String b){ return ""+i+b; }
            String combine(NameCons c, String i, String e){ return ", "+i+e; }
            String combine(NENameList c, String i, String e){ return "<"+i+e; }
            String combine(NameEmpty e){ return ">"; }
            
            String combine(DefParams dp, String s){ return s; }
            String combine(EmptyDefParams e){ return ""; }
            String str(TypeDefParams dp){ return Factory.newTraversal(this,
                    Control.builtins(TypeUse.class)).traverseTypeDefParams(dp); }
        }
        public static class UseStr extends BoundStr{
            String combine(ClassBound b, TypeUse i){ return ""; }
        }
    }

    public static class DiffCS extends DiffJava{
        public DiffCS(){
            inherit = ":";
            fieldClassMod = "";
            fieldClassPost = "F";
            fieldImport = "";
            fieldImmut = "readonly";
            equalsRet = "bool";
            equalsMethod = "Equals";
            hashRet = "int";
            hashMethod = "GetHashCode";
            fileSuffix = "cs";
            override = " override";
            toStringMeth = "ToString";
            packageStmt = "namespace";
            importStmt = "using";
            runtimeException = "Exception";
            stringInput = "System.IO.StringReader";
            inputStream = "System.IO.Stream";
            inputReader = "System.IO.TextReader";
            parserGen = "CSJavaCC";
            compilerName = "GMCS";
            packagePrefix = "{";
            classEnd= "}";
            basicImport = importAll("System");
            parserImport = importAll("System")+
                           importAs("edu.neu.ccs.demeterf","Fields")+
                           importAs("edu.neu.ccs.demeterf.lib","ident")+
                           importAs("edu.neu.ccs.demeterf.lib","verbatim");

            parserPreamble = Preamble.parserCS;
            parserBody = Preamble.parserBodyCS;
            escapeMethods = Preamble.escapeMethodsCS;

            genericSep = '_';
            
            primitives = List.create("byte","short","int","long","float","double",
                    "char","bool","string","ident","verbatim");
            boxed = List.create("Byte", "Int16", "Int32", "Int64", "Single", "Double",
                    "Char","Boolean","String","ident","verbatim");

            builtIns = primitives
                .append(boxed)
                .append(List.create("ID", "TP", "TU", "FC"));

            makeParseMsg = " ** Running CSJavaCC...";
            makeBuildMsg = " ** Running GMCS...";
            makeInfo = "Run CSJavaCC & GMCS after generating the files";
            
            constrStr = new CSConstrStr();
            bndStr = useStr;
        }
        /** Generates the constraints for generaic type bounds */
        static class CSConstrStr extends ConstrStr{
            NoBound combine(NoBound b){ return b; }
            String combine(ClassBound b, TypeUse i){ return " : "+i; }
            String combine(NameDef d, ident i, NoBound b){ return ""; }
            String combine(NameDef d, ident i, String b){ return "\n     where "+i+b; }
            String combine(NameCons c, String i, String e){ return ""+i+e; }
            String combine(NENameList c, String i, String e){ return ""+i+e; }
            String combine(NameEmpty e){ return "\n"; }
            String combine(DefParams dp, String s){ return s; }
            String combine(EmptyDefParams e){ return ""; }
            String str(TypeDefParams dp){ return Factory.newTraversal(this,
                    Control.builtins(TypeUse.class)).traverseTypeDefParams(dp); }
        }
        public boolean isJava(){ return false; }
        public boolean isCS(){ return true; }
        
        public String importAs(String pkg, String cls){ return importStmt+" "+cls+" = "+pkg+"."+cls+";\n"; }
        public String importAll(String pkg){ return importStmt+" "+pkg+";\n"; }
        
        public String[] mkParseCmd(String dir){
            return new String[]{"bash","-c","java csjavacc -OUTPUT_DIRECTORY=\""+dir+"\" \""+dir+"\"/theparser.jj"};
        }
        public String[] mkParseCmdNoShell(String dir){
            return new String[]{"java","csjavacc","-OUTPUT_DIRECTORY="+dir,dir+SEP+"theparser.jj"};
        }
        public String[] buildCmd(List<String> dirs, boolean lib, String name){
            String out = (lib?"-out:"+name+" -target:library":"-out:MAIN.exe -target:exe");
            return new String[]{"bash","-c","gmcs -warn:1 "+out+" "+Diff.getReferences()+" "+addSuff(dirs,"/*.cs",true)};
        }
        public String[] mkWinParseCmd(String dir){
            return new String[]{"cmd","/C","java csjavacc -OUTPUT_DIRECTORY="+dir+" "+dir+"\\theparser.jj"};
        }
        public String[] buildWinCmd(List<String> dirs, boolean lib, String name){
            String out = (lib?"/out:"+name+" /target:library":"/out:MAIN.exe /target:exe");
            return new String[]{"cmd","/C","csc /warn:1 "+out+" /reference:"+Diff.getReferences()+
                    " "+addSuff(dirs,"\\*.cs",false)};
        }
        
        public String box(String t){ return t; }
        
        public String parseException(String pkg){ return ""; }
        public String instanceCheck(String name, String tp){ return " is "+name+tp; }
        public String classType(String cls, String tp){ return "typeof("+cls+tp+")"; }
        public String paramMethod(String meth, String p){ return meth+"<"+p+">"; }
        public String paramMethodDef(String meth, String ret, String p){ return ret+" "+meth+"<"+p+">"; }
        public String varArgs(String t, String arg){ return "params "+t+"[] "+arg; }
        
        // Single list of inheritance... Both a class and possibly multiple interfaces
        public String extensions(String type, List<InhrtPair> extnd, List<InhrtPair> intfc, List<String> params){
            List<InhrtPair> inhrtL =  extnd.append(intfc);
            return ((inhrtL.isEmpty()?"":" : ")+
                    inhrtL.toString(new InhrtHelp(type, params)));
        }
    }
    
    static class InhrtHelp extends List.Stringer<InhrtPair>{
        String type;
        List<String> params;
        InhrtHelp(String t, List<String> p){ type = t; params = p; }
        public String toString(InhrtPair ip, List<InhrtPair> r){
            // Extensions need to map the type parameters of the child definition to
            //   the supertype, so that we can correctly rename fields/etc.
            String pars = (ip.inhrt.extend()?binds(bindingsFromUse(type,ip), params):ip.useParams());
            return (ip.inhrt.parent()+pars+(r.isEmpty()?"":", "));
        }
        public String binds(List<String> def, List<String> use){
            if(use.isEmpty())return "";
            return "<"+use.toString(", ","")+">";
        }
        /** Grab (and check) the matching bindings used for an Extension
         *     like:   A(X) = B(X) | C(X).  They have to be the same currently. */
        public static List<String> bindingsFromUse(final String type, InhrtPair inhrt){
            return inhrt.getUParams().map(new List.Map<TypeUse, String>(){
                public String map(TypeUse tu){
                    if(!tu.getTparams().isEmpty())
                        throw new TE("Type parameters for extensions must be simple bindings."+
                                "\n     Error in definition of: "+type+" with parameter "+tu);
                    return ""+tu.getName();
                }
            });
        }
        /** Rebind the fields based on the child's definition (parameters) and the parents
         *    definition, since type parameters can be renamed in separate definitions. */
        public static List<FieldOrSyntax> rebindFields(String type, List<String> subtpars, InhrtPair inhrt){
            List<String> uses = bindingsFromUse(type,inhrt);
            return DemFGenMain.instantiate(inhrt.fields(), uses, subtpars);
        }
    }
}