package AST;

import java.util.*;

public class Tracer {

  public static void traceCached(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CACHED, node, attr, params, value);
  }

  public static void traceCacheAbort(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CACHE_ABORT, node, attr, params, value);
  }

  public static void traceEnterRewriteCase1(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.REWRITE_CASE1_START, node, attr, params, value);
  }

  public static void traceRewriteChange(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.REWRITE_CHANGE, node, attr, params, value);
  }

  public static void traceExitRewriteCase1(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.REWRITE_CASE1_RETURN, node, attr, params, value);
  }

  public static void traceExitRewriteCase2(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.REWRITE_CASE2_RETURN, node, attr, params, value);
  }

  public static void traceExitRewriteCase3(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.REWRITE_CASE3_RETURN, node, attr, params, value);
  }


  public static void traceEnterCircularNTACase1(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE1_START, node, attr, params, value);
  }
  public static void traceCircularNTACase1Change(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE1_CHANGE, node, attr, params, value);
  }
  public static void traceExitCircularNTACase1(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE1_RETURN, node, attr, params, value);
  }

  public static void traceEnterCircularNTACase2(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE2_START, node, attr, params, value);
  }
  public static void traceCircularNTACase2Change(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE2_CHANGE, node, attr, params, value);
  }
  public static void traceExitCircularNTACase2(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE2_RETURN, node, attr, params, value);
  }

  public static void traceExitCircularNTACase3(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.CIRCULAR_NTA_CASE3_RETURN, node, attr, params, value);
  }

  public static void traceCopy(ASTNode node, String value) {
    trace(TraceEvent.COPY_NODE, node, "ASTNode.copy", "", value);
  }

  public static void traceInhAttrEval(ASTNode node, String attr, String params, String value) {
    trace(TraceEvent.INH_ATTR_EVAL, node, attr, params, value);
  }

  public static void printTrace() {
    int indent = 0;
    for (TraceObject obj : list) {
      System.out.println(indentToStr(indent) + obj);
      if (incIndent(obj.event)) indent++;
      else if (decIndent(obj.event)) indent--;
    }
  }

  public static void printComputeReport() {
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        return obj.event == TraceEvent.CACHE_ABORT || obj.event == TraceEvent.CACHED;
      }
      public String desc() {
        return "computed(i.e.,CACHE_ABORT||CACHED)";
      } 
    });
  }

  public static void printCacheAbortReport() {
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        return obj.event == TraceEvent.CACHE_ABORT;
      }
      public String desc() {
        return "CACHE_ABORT";
      } 
    });
  }

  public static void printCacheReport() {
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        return obj.event == TraceEvent.CACHED;
      }
      public String desc() {
        return "CACHED";
      } 
    });
  }

  public static void printCopyReport() {
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        return obj.event == TraceEvent.COPY_NODE;
      }
      public String desc() {
        return "COPY_NODE";
      } 
    });
  }

  public static void printInhAttrEvalReport() {
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        return obj.event == TraceEvent.INH_ATTR_EVAL;
      }
      public String desc() {
        return "INH_ATTR_EVAL";
      } 
    });
  }

  public static void printNestingReport() {
    class NestingCounter {
      int nesting = 0;
      int maxNesting = 0;
      public void push() {
        nesting++;
        if (nesting > maxNesting) {
          maxNesting = nesting;
        }
      }
      public void pop() {
        nesting--;
      }
      public int max() { return maxNesting; }
    }
    final NestingCounter counter = new NestingCounter();
    printReport(new TraceFilter() {
      public boolean include(TraceObject obj) {
        boolean res = false;
        if (obj.event == TraceEvent.CIRCULAR_NTA_CASE1_START || 
            obj.event == TraceEvent.CIRCULAR_NTA_CASE2_START) {
          res = true;
          counter.push();
        }
        if (obj.event == TraceEvent.CIRCULAR_NTA_CASE1_RETURN || 
            obj.event == TraceEvent.CIRCULAR_NTA_CASE2_RETURN) {
          counter.pop();
        }
        return res;
      }
      public String desc() {
        return "CIRCULAR_NTA_CASE*_START";
      } 
    });
    System.out.println("Max nesting: " + counter.max() + "\n");
  }


  // ------------------- Private stuff

  private static void printReport(TraceFilter filter) {
    // Report model: attr->param->node:count
    Map<String,Map<String,Map<String,Integer>>> attrMap = new HashMap<String,Map<String,Map<String,Integer>>>();
    for (TraceObject obj : list) {
      if (filter.include(obj)) {
        if (!attrMap.containsKey(obj.attr)) {
          attrMap.put(obj.attr, new HashMap<String,Map<String,Integer>>());
        }
        Map<String,Map<String,Integer>> paramMap = attrMap.get(obj.attr);
        if (!paramMap.containsKey(obj.params)) {
          paramMap.put(obj.params, new HashMap<String,Integer>());
        }
        Map<String,Integer> nodeMap = paramMap.get(obj.params);
        if (!nodeMap.containsKey(obj.node)) {
          nodeMap.put(obj.node, new Integer(0));
        }
        Integer nodeCount = nodeMap.get(obj.node);
        nodeMap.put(obj.node, new Integer(nodeCount + 1));
      }
    }
    // Create result string
    int totalCount = 0;
    StringBuffer res = new StringBuffer();
    for (String attr : attrMap.keySet()) {
      int attrCount = 0;
      StringBuffer paramBuf = new StringBuffer();
      Map<String,Map<String,Integer>> paramMap = attrMap.get(attr);
      for (String param : paramMap.keySet()) {
        int paramCount = 0;
        StringBuffer nodeBuf = new StringBuffer();
        Map<String,Integer> nodeMap = paramMap.get(param);
        for (Map.Entry<String,Integer> e : nodeMap.entrySet()) {
          nodeBuf.append(indentToStr(3) + e.getKey() + " - " + e.getValue() + "\n");
          paramCount += e.getValue();
        }
        paramBuf.append((!param.equals("") ? indentToStr(2) + "[" + param + "] - " + paramCount + "\n" : "") + nodeBuf.toString());
        attrCount += paramCount;
      }
      res.append(indentToStr(1) + attr + " - " + attrCount + "\n" + paramBuf.toString());
      totalCount += attrCount;
    }
    System.out.println("[report:" + filter.desc() + "] - " + totalCount + "\n" + res.toString());
  }

  private interface TraceFilter {
    public boolean include(TraceObject obj);
    public String desc();
  }

  private static void trace(TraceEvent evt, ASTNode node, String attr, String params, String value) {
    list.add(new TraceObject(evt, node.toString(), attr, params, value));
  }

  static java.util.List<TraceObject> list = new ArrayList<TraceObject>();

  private static class TraceObject {
    TraceEvent event;
    String node;
    String attr;
    String params;
    String value;
    public TraceObject(TraceEvent e, String n, String a, String p, String v) {
      event = e; node = n; attr = a; params = p; value = v;
    }
    public String toString() {
      return "[trace: " + event + ", " + attr + "[" + params + "]=" + value + " - " + node + "]";
    }
  }

  private enum TraceEvent { 
    CACHED, 
    CACHE_ABORT, 
    REWRITE_CASE1_START, 
    REWRITE_CHANGE,
    REWRITE_CASE1_RETURN,
    REWRITE_CASE2_RETURN,
    REWRITE_CASE3_RETURN,
    CIRCULAR_NTA_CASE1_START,
    CIRCULAR_NTA_CASE1_CHANGE,
    CIRCULAR_NTA_CASE1_RETURN,
    CIRCULAR_NTA_CASE2_START,
    CIRCULAR_NTA_CASE2_CHANGE,
    CIRCULAR_NTA_CASE2_RETURN,
    CIRCULAR_NTA_CASE3_RETURN,
    COPY_NODE,
    INH_ATTR_EVAL
  }

  private static boolean incIndent(TraceEvent evt) {
    switch (evt) {
    case REWRITE_CASE1_START: 
    case CIRCULAR_NTA_CASE1_START: 
    case CIRCULAR_NTA_CASE2_START: 
      return true;
    default: 
      return false;
    }
  }

  private static boolean decIndent(TraceEvent evt) {
    switch (evt) {
    case REWRITE_CASE1_RETURN: 
    case CIRCULAR_NTA_CASE1_RETURN: 
    case CIRCULAR_NTA_CASE2_RETURN: 
      return true;
    default: 
      return false;
    }
  }

  private static String INDENT_STEP = " ";
  private static String indentToStr(int indent) {
    StringBuffer buf = new StringBuffer();
    for(int i = 0; i < indent; i++) {
      buf.append(INDENT_STEP);
    }
    return buf.toString();
  }

}
