/*
 * Copyright 2003-2012 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.typesystem;

import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.util.Pair;
import jetbrains.mps.util.performance.IPerformanceTracer;

import java.util.*;
import java.util.Map.Entry;


public class TypeSystemReporter {
  private static TypeSystemReporter instance = null;
  private boolean isEnabled = false;
  private Map<String, Pair<Long, Long>> myGetTypeOfTime = new HashMap<String, Pair<Long, Long>>();
  private Map<String, Pair<Long, Long>> myIsSubTypeTime = new HashMap<String, Pair<Long, Long>>();
  private Map<String, Pair<Long, Long>> myCoerceTime = new HashMap<String, Pair<Long, Long>>();

  private TypeSystemReporter() {

  }

  public static TypeSystemReporter getInstance() {
    if (instance == null) {
      instance = new TypeSystemReporter();
    }
    return instance;
  }

  public void reset() {
    isEnabled = true;
    myGetTypeOfTime.clear();
    myIsSubTypeTime.clear();
    myCoerceTime.clear();
  }

  public void reportTypeOf(SNode node, long time) {
    if (!isEnabled) return;
    String conceptFqName = node.getConcept().getQualifiedName();
    report(time, conceptFqName, myGetTypeOfTime);
  }

  private void report(long time, String conceptFqName, Map<String, Pair<Long, Long>> map) {
    if (!isEnabled) return;
    Pair<Long, Long> value = map.get(conceptFqName);
    if (value == null) {
      value = new Pair<Long, Long>(0L, 0L);
      map.put(conceptFqName, value);
    }
    value.o1 += time;
    value.o2++;
  }

  public void reportIsSubType(SNode subType, SNode superType, long time) {
    if (!isEnabled || null == subType || null == superType) return;
    String conceptFqName = subType.getConcept().getQualifiedName() + "   " + superType.getConcept().getQualifiedName();
    report(time, conceptFqName, myIsSubTypeTime);
  }

  public void reportCoerce(SNode subType, SAbstractConcept concept, long time) {
    if (!isEnabled || null == subType) {
      return;
    }
    String conceptFqName = subType.getConcept().getQualifiedName() + "   " + concept.getQualifiedName();
    report(time, conceptFqName, myCoerceTime);
  }

  public void printReport(int numTop, IPerformanceTracer tracer) {
    tracer.addText("Time spent on getTypeOf operation");
    printMapReport(myGetTypeOfTime, numTop, tracer);
    tracer.addText("Time spent on isSubType operation");
    printMapReport(myIsSubTypeTime, numTop, tracer);
    tracer.addText("Time spent on coerce operation");
    printMapReport(myCoerceTime, numTop, tracer);
    isEnabled = false;
  }

  public void printMapReport(Map<String, Pair<Long, Long>> map, int numTop, IPerformanceTracer tracer) {
    if (!isEnabled) return;
    ArrayList<Entry<String, Pair<Long, Long>>> list = new ArrayList<Entry<String, Pair<Long, Long>>>();
    list.addAll(map.entrySet());

    Collections.sort(list, new Comparator<Entry<String, Pair<Long, Long>>>() {
      @Override
      public int compare(Entry<String, Pair<Long, Long>> o1, Entry<String, Pair<Long, Long>> o2) {
        return o2.getValue().o1 > o1.getValue().o1 ? 1 : -1;
      }
    });
    long sum = 0;
    int i = 0;
    for (Entry<String, Pair<Long, Long>> entry : list) {
      if (i++ >= numTop) break;
      sum += entry.getValue().o1;
      StringBuilder sb = new StringBuilder();
      boolean isFirst = true;
      for (String name : entry.getKey().split("   ")) {
        if (!isFirst) {
          sb.append(" :< ");
        }
        isFirst = false;
        int beginIndex = name.lastIndexOf('.')+1;
        sb.append(name.substring(beginIndex));
       
      }
      tracer.addText(String.format(sb.toString() + " %.3f s,  %d times", entry.getValue().o1 * 1.0e-9, entry.getValue().o2));
    }
    tracer.addText("Total: " + sum * 1.0e-9);
  }


}
