package net.yak.stalk;

import java.util.HashMap;
import java.util.TreeSet;

import net.yak.stalk.Cls.Meth;
import net.yak.stalk.Pro.Blk;
import net.yak.stalk.Talk.Terp.Frame;

public abstract class Expr extends Talk {

    abstract Pro eval(Frame f);

    static class Top extends Expr {
        int numLocals;  // Counts "self" and the args.
        int numArgs;  // Does not count "self".
        HashMap<String, Integer> localVars;
        HashMap<String, String> localVarSpelling;
        Cls cls;
        String methName;
        Expr body;

        public Top(int numVars, int numArgs, HashMap<String, Integer> localVars,
                HashMap<String, String> localVarSpelling, Cls cls, String methName, Expr body) {
            super();
            this.numLocals = numVars;
            this.numArgs = numArgs;
            this.localVars = localVars;
            this.localVarSpelling = localVarSpelling;
            this.cls = cls;
            this.methName = methName;
            this.body = body;
        }
        @Override
        Pro eval(Frame f) {
            // TODO Auto-generated method stub
            return body.eval(f);
        }
        public String toString() {
            return fmt("\"TOP %s.%s nv%d na%d\"  %s ", cls, methName, numLocals, numArgs, body);
        }
    }

    static class PutInstVar extends Expr {
        String name;
        Expr expr;
        int index;

        public PutInstVar(String name, int index, Expr expr) {
            super();
            this.name = name;
            this.index = index;
            this.expr = expr;
        }
        Pro eval(Frame f) {
            Pro x = expr.eval(f);
            if (f.self.cls.trace) {
                say("PutInstVar: %s#%s \"i%d\" %s := %s#%s", f.self.cls.name, f.self.instId, index, name, x.cls.name, x
                        .toString());
            }
            say("PutInstVar [%s.%s] self=<%s> len=%d name=%s i=%d <<< %s", f.top.cls, f.top.methName, f.self, f.self.instVars.length, name, index, x);
            f.self.instVars[index] = x;
            return x;
        }
        public String toString() {
            return fmt("\"i%d\" %s= %s", index, name, expr);
        }
    }

    static class GetInstVar extends Expr {
        String name;
        int index;

        public GetInstVar(String name, int index) {
            super();
            this.name = name;
            this.index = index;
        }
        Pro eval(Frame f) {
            Pro z = f.self.instVars[index];
            say("GetInstVar [%s.%s] self=<%s> len=%d name=%s i=%d >>> %s",  f.top.cls, f.top.methName, f.self, f.self.instVars.length, name, index, z);
            return z;
        }
        public String toString() {
            return fmt("%s \"i%d\" ", name, index);
        }
    }

    static class GetLocalVar extends Expr {
        String name;
        int index;

        GetLocalVar(String key, int index) {
            this.name = key;
            this.index = index;
        }
        Pro eval(Frame f) {
            Pro z = f.get(name);
            if (z == null) {
                toss("Local Variable <%s> was never set.", name);
            }
            return z;
        }
        public String toString() {
            return fmt("%s\"GL%d\" ", name, index);
        }
    }

    static class GetGlobalVar extends Expr {
        String key;

        GetGlobalVar(String key) {
            this.key = key;
        }
        Pro eval(Frame f) {
            Pro z = f.terp().clss.get(key);
            if (z == null) {
                toss("Global Variable <%s> was never set.", key);
            }
            return z;
        }
        public String toString() {
            return fmt("%s \"GC\" ", key);
        }
    }

    static class PutLocalVar extends Expr {
        String key;
        int index;
        Expr expr;

        PutLocalVar(String key, int index, Expr expr) {
            this.key = key;
            this.index = index;
            this.expr = expr;
        }
        Pro eval(Frame f) {
            Pro value = expr.eval(f);
            f.put(key, value);
            // say("PutLocalVar: Putting <%s> to %s.", value, key);
            return value;
        }
        public String toString() {
            return fmt("\"PL%d\"%s= %s", index, key, expr);
        }
    }

    // static class PutGlobalVar extends Expr {
    // String key;
    // Expr expr;
    //
    // PutGlobalVar(String key, Expr expr) {
    // this.key = key;
    // this.expr = expr;
    // }
    // Pro eval(Frame f) {
    // Pro value = expr.eval(f);
    // f.globals().put(key, value);
    // return value;
    // }
    // public String toString() {
    // return fmt("%s= %s", key, expr);
    // }
    // }

    static class Send extends Expr {
        Expr rcvr;
        String msg;
        Expr args[];

        Send(Expr rcvr, String msg, Expr args[]) {
            this.rcvr = rcvr;
            this.msg = msg;
            this.args = args;
        }

        Meth findMeth(Pro r) {
            for (Cls c = r.cls; c != null; c = c.supercls) {
                Meth m = c.meths.get(msg);
                if (m != null)
                    return m;
            }
            // If above fails, list the possibilities.
            for (Cls c = r.cls; c != null; c = c.supercls) {
                say("Visiting class <%s>", c.name);
                for (String k : c.meths.keySet()) {
                    say("Method <%s> exits on class <%s>", k, c.name);
                }
            }
            say("Couldn't find method <%s> on class <%s>", msg, r.cls.name);
            return null;
        }

        @Override
        Pro eval(Frame f) {
            Pro r = rcvr.eval(f);
            assert r != null;
            Pro a[] = new Pro[args.length];
            for (int i = 0; i < args.length; i++) {
                a[i] = args[i].eval(f);
                assert a[i] != null;
            }
            Meth m = findMeth(r);
            if (m == null) {
                toss("MessageNotUnderstood: message %s to instance of class %s: <%s>", msg, r.cls.name, r);
            }
            if (r.cls.trace || m.trace) {
                say("Sending message %s to %s#%s with %s", m.name, r.cls.name, r.toString(), arrayToString(args));
            }
            Pro z = null;
            try {
                z = m.apply(f, r, a);
            } catch (TalkException ex) {
                String what = fmt("\n  * During SEND <%s ^ %s> TO %s WITH %s", r.cls.name, m.name, r.toString(),
                        arrayToString(a));
                toss(ex.toString() + what);
            }
            if (r.cls.trace || m.trace) {
                say("Message %s to %s#%s returns %s#%s", m.name, r.cls.name, r.toString(), z.cls.name, z.toString());
            }
            return z;
        }

        public String toString() {
            if (args.length == 0) {
                return fmt("( %s ) %s ", rcvr, msg);
            } else {
                StringBuffer sb = new StringBuffer();
                sb.append(fmt("( %s ) ", rcvr.toString()));
                String[] words = msg.split(":");
                for (int i = 0; i < args.length; i++) {

                    sb.append(fmt("%s: ( %s ) ", words[i], args[i].toString()));
                }
                return sb.toString();
            }
        }
    }

    static class Block extends Expr {
        Expr body;
        String[] params;

        public Block(Expr body, String[] params) {
            this.body = body;
            this.params = params;
        }
        @Override
        Pro eval(Frame f) {
            return new Blk(this, f);
        }
        public String toString() {
            String plist = "";
            for (String param : params) {
                plist += ":" + param + " ";
            }
            return "[ " + plist + body.toString() + "] ";
        }
    }

    static class Seq extends Expr {
        Expr[] body; // Eval these in order; return value of last.

        public Seq(Expr[] body) {
            this.body = body;
        }
        Pro eval(Frame f) {
            Pro z = f.terp().instNil;
            for (Expr expr : body) {
                z = expr.eval(f);
                assert z != null;
            }
            return z;
        }
        public String toString() {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < body.length; i++) {
                sb.append(body[i].toString());
                sb.append("; ");
            }
            return sb.toString();
        }
    }

    static class Lit extends Expr {
        Pro value; // Literal value.

        public Lit(Pro value) {
            this.value = value;
        }
        Pro eval(Frame f) {
            return value;
        }
        public String toString() {
            return fmt("%s", value);
        }
    }
}
