package org.cache2k.benchmark;

/*
 * #%L
 * Benchmarks: implementation variants
 * %%
 * Copyright (C) 2013 - 2017 headissue GmbH, Munich
 * %%
 * 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.
 * #L%
 */

import org.junit.After;
import org.cache2k.benchmark.util.AccessTrace;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author Jens Wilke; created: 2013-12-08
 */
public class BenchmarkingBase {

  static Map<String,String> benchmarkName2csv = new TreeMap<>();
  static HashSet<String> onlyOneResult = new HashSet<>();
  protected BenchmarkCacheFactory factory = new Cache2kFactory();

  BenchmarkCache<Integer, Integer> cache = null;

  public BenchmarkCache<Integer, Integer> freshCache(AccessTrace t, int _maxElements) {
    return freshCache(_maxElements);
  }

  public BenchmarkCache<Integer, Integer> freshCache(int _maxElements) {
    if (cache != null) {
      throw new IllegalStateException("Two caches in one test? Please call destroyCache() first");
    }
    return cache = factory.create(_maxElements);
  }

  public void destroyCache() {
    if (cache != null) {
      cache.close();
      cache = null;
    }
  }

  @After
  public void tearDown() {
    destroyCache();
  }

  public final long runBenchmark(BenchmarkCache<Integer, Integer> c, AccessTrace t) {
    Integer[] _trace = t.getObjectTrace();
    if (c instanceof SimulatorPolicy) {
      SimulatorPolicy p = (SimulatorPolicy) c;
      for (Integer k : _trace) {
        ((SimulatorPolicy) c).record(k);
      }
      return p.getMissCount();
    }
    long _missCount =  0;
    for (Integer k : _trace) {
      Integer v = c.getIfPresent(k);
      if (v == null) {
        c.put(k, k);
        _missCount++;
      }
    }
    return _missCount;
  }

  public final int runBenchmark(AccessTrace t, int _cacheSize) {
    BenchmarkCache<Integer, Integer> c;
    c = freshCache(t, _cacheSize);
    long _missCount = runBenchmark(c, t);
    logHitRate(c, t, _missCount);
    c.close();
    return
      ((t.getTraceLength() - (int) _missCount) * 10000 + t.getTraceLength() / 2) / t.getTraceLength();
  }

  public void logHitRate(BenchmarkCache c, AccessTrace _trace, long _missCount) {
    int _optHitRate = -1;
    int _optHitCount = -1;
    String _testName = extractTestName();
    if (onlyOneResult.contains(_testName)) {
      return;
    }
    onlyOneResult.add(_testName);
    long _usedMem = -1;
    saveHitRate(_testName, c.getCacheSize(), _trace, _optHitRate,_optHitCount, _missCount, _usedMem);
    String _cacheStatistics = c.toString();
    System.out.println(_cacheStatistics);
    System.out.flush();
  }

  void saveHitRate(String _testName, int _cacheSize, AccessTrace _trace, int _optHitRate, int _optHitCount, long _missCount, long _usedMem) {
    double _hitRateTimes100 =
      (_trace.getTraceLength() - _missCount) * 100D / _trace.getTraceLength();
    String _hitRate = String.format("%.2f", _hitRateTimes100);
    String s = "";
    if (_cacheSize > 0) {
      s += "size=" + _cacheSize + ", ";
    }
    s += "accessCount=" + _trace.getTraceLength();
    s += ", missCount=" + _missCount + ", hitRatePercent=" + _hitRate;
    if (_optHitRate >= 0) {
      s += ", optHitRatePercent=" + String.format("%.2f", _optHitRate * 1D / 100);
      s += ", optHitCount=" + _optHitCount;
    }
    s += ", uniqueValues=" + _trace.getValueCount();
    System.out.println(_testName + ": " + s);
    int idx = _testName.lastIndexOf('.');
    String _cacheImplementation = _testName.substring(0, idx);
    String _benchmarkName = _testName.substring(idx + 1);
    String _csvLine =
      _benchmarkName + "|" +  // 1
      _cacheImplementation + "|" + // 2
        String.format("%.2f", _hitRateTimes100) + "|" + // 3
        _cacheSize + "|" + // 4
      _trace.getTraceLength() + "|" + // 5
      _trace.getValueCount(); // 6

    if (!benchmarkName2csv.containsKey(_testName)) {
      benchmarkName2csv.put(_testName, _csvLine);
      writeCsv(_csvLine);
    }
  }

  void writeCsv(String _csvLine) {
    String s = System.getProperty("cache2k.benchmark.result.csv");
    if (s == null) {
      return;
    }
    try {
      PrintWriter w = new PrintWriter(new FileWriter(s, true));
      w.println(_csvLine);
      w.close();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  String extractTestName() {
    Exception e = new Exception();
    int idx = 1;
    StackTraceElement[] _stackTrace = e.getStackTrace();
    do {
      String n = _stackTrace[idx].getMethodName();
      idx++;
      if (n.startsWith("benchmark") || n.startsWith("test")) {
        return this.getClass().getName() + "." + _stackTrace[idx - 1].getMethodName();
      }
    } while (true);
  }

}

@@ -4362,16 +4362,48 @@
  + // 4%0A
+        _missCount + %22%7C%22 + // 5%0A
       _t
@@ -4427,33 +4427,33 @@
 th() + %22%7C%22 + // 
-5
+6
 %0A      _trace.ge
@@ -4470,17 +4470,17 @@
 t(); // 
-6
+7
 %0A%0A    if

add miss count to result