// A Lisp Driver to be embedded in Java Applications

// The contents of this file are subject to the Mozilla Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is JAKLD code, released November 26, 2002.
//
// The Initial Developer of the Original Code is Taiichi Yuasa.
// Portions created by Taiichi Yuasa are Copyright (C) 2002
// Taiichi Yuasa. All Rights Reserved.
//
// Contributor(s): Taiichi Yuasa <yuasa@kuis.kyoto-u.ac.jp>

import java.util.Hashtable;
import java.util.Enumeration;

final class Tracer extends Function {

  Symbol name;
  Function fun;

  Tracer(Symbol name, Function fun) {
    this.name = name;
    this.fun = fun;
  }

  private static int traceLevel = 0;

  public Object invoke0(List args) {
    int prevLevel = traceLevel;
    try {
      traceLevel++;
      for (int i = 0; i < traceLevel; i++) IO.print("  ");
      IO.print(traceLevel + "> ");
      IO.println(new Pair(name, args));

      Object val = fun.invoke0(args);
      List tailCalls = List.nil;
      int nTailCalls = 0;
      try {
        while (val instanceof Call) {
          Eval.checkInterrupt();
          Call call = (Call) val;
          if (call.fun instanceof Tracer) {
            Tracer tracer = (Tracer) call.fun;
            for (int i = 0; i < traceLevel; i++) IO.print("  ");
            IO.print(traceLevel + ": ");
            IO.println(new Pair(tracer.name, call.args));
            val = tracer.fun.invoke0(call.args);
          } else {
            val = call.fun.invoke0(call.args);
          }
          if (nTailCalls <= 8) {
            if (nTailCalls == 8)
              ((Pair) ((List) ((List) tailCalls.cdr).cdr).cdr).car = null;
            tailCalls = new Pair(call, tailCalls);
            nTailCalls++;
          } else
            for (Pair tc = (Pair) tailCalls; tc.car != null;
                  tc = (Pair) tc.cdr) {
              Call next = (Call) tc.car;
              tc.car = call;
              call = next;
            }
        }
      } catch (RuntimeException e) {
        if (e == Eval.backtraceToken) {
          while (tailCalls != List.nil) {
            Call call = (Call) List.car(tailCalls);
            IO.print(" < " + (call == null ? "..."
                                           : "[" + call.fun.getName() + "]"));
            tailCalls = (List) List.cdr(tailCalls);
          }
          IO.print(" < [" + name + "]");
        }
        throw e;
      }
      for (int i = 0; i < traceLevel; i++) IO.print("  ");
      IO.print("<" + traceLevel + " ");
      IO.println(List.list(name, val));
      return val;
    } finally {
      traceLevel = prevLevel;
    }
  }

  private final static Hashtable<Symbol,Tracer> traceSet
  = new Hashtable<Symbol,Tracer>();

  static List trace(List syms) {
    List traced = List.nil;
    for (; syms != List.nil; syms = (List) syms.cdr) {
      Symbol sym = (Symbol) syms.car;
      if (sym.kind == Symbol.SKordinary && sym.value instanceof Function) {
        Tracer tracer = traceSet.get(sym);
        if (tracer != null && tracer.fun == sym.value) {
          IO.println("function " + sym + " is already traced");
          continue;
        }
        tracer = new Tracer(sym, (Function) sym.value);
        traceSet.put(sym, tracer);
        sym.value = tracer;
        traced = new Pair(sym, traced);
      } else
        IO.println("function " + sym + " is not defined");
    }
    return traced;
  }

  static List untrace(List syms) {
    List untraced = List.nil;
    if (syms == List.nil) {
      Enumeration keys = traceSet.keys();
      while (keys.hasMoreElements()) {
        Symbol sym = (Symbol) keys.nextElement();
        Tracer tracer = traceSet.get(sym);
        if (tracer == sym.value) {
          sym.value = tracer.fun;
          untraced = new Pair(sym, untraced);
        }
      }
      traceSet.clear();
    } else
      for (List ss = syms; ss != List.nil; ss = (List) ss.cdr) {
        Symbol sym = (Symbol) ss.car;
        Tracer tracer = traceSet.get(sym);
        if (tracer == null)
          IO.println("function " + sym + " is not traced");
        else {
          if (tracer == sym.value) {
            sym.value = tracer.fun;
            untraced = new Pair(sym, untraced);
          } else
            IO.println("function " + sym + " has been redefined");
          traceSet.remove(sym);
        }
      }
    return untraced;
  }

  public String getName() {
    return name.toString();
  }

  public String toString() {
    return "#<traced function " + name + ">";
  }

  static void init() {}
}
