package nxm.sys.test.junit.lib;

import static nxm.sys.lib.CallStackUtil.getCaller;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;

import java.lang.reflect.Method;
import java.util.logging.Level;

import org.junit.Test;

import nxm.sys.lib.CallStackUtil;
import nxm.sys.lib.CallStackUtil.IgnoreStackElement;
import nxm.sys.lib.LogUtilities;
import nxm.sys.lib.Midas;
import nxm.sys.test.junit.NMTestCase;

/** Tests for {@link CallStackUtil}.
 *  @author J. Czechowski
 *  @since  NeXtMidas 3.9.0
 */
public class CallStackUtilTest extends NMTestCase {
  /** public constructor to allow running this test case directly with JUnit.
   *  @param methodName The name of the method being called.
   */
  public CallStackUtilTest (String methodName) {
    this(methodName, null);
  }

  private static int logCount = 1;
  /** This constructor is required by NeXtMidas Unit Testing framework.
   *  @param methodName The name of the method being called.
   *  @param midas      The midas context to use when running the tests.
   */
  public CallStackUtilTest (String methodName, Midas midas) {
    super(methodName, midas);
    if(midas== null) midas = getMidas();
  }

  static StackTraceElement heWhoCalledMe () {
    return getCaller(CallStackUtilTest.class);
  }

  @Test
  public void testGetCaller () throws Exception {
    if(isUnittestMetrics("running metrics for " + this.getName()  + "()")) logCount=100000; // if we are going quick, do not do heavy testing, change the count to 1 to confirm the test works
    (new Wrapper1()).runTest(this);
//    long start = System.currentTimeMillis();

    for(int i = 0;i<logCount;i++) {
      // java 9 ~2300 ms
      // java 8 ~2300 ms
      (new Wrapper2()).runTest(this);

    }
//    System.out.println("The second run with hidden items runs in "+(System.currentTimeMillis()-start));

  }
  static String thisClassName =  CallStackUtilTest.class.getName();
  /**
   *  This would be the real world scenario of logging metrics. In this case, we are just calling the static log method
   */
  public void testLogSpeedMetrics() {
    if(isUnittestMetrics("running metrics for " + this.getName()  + "()")) logCount=100000; // if we are going quick, do not do heavy testing, change the count to 1 to confirm the test works
//    System.out.println("Running in "+ (CallStackUtil.isJava9 ? "Java 9" : "Java 8") + " mode");
    String propsFileName = "propsFileName";
    String keyStoreFile = "keyStoreFile";
    String trustStoreFile = "trustStoreFile";
    String keyStorePasswd = "keyStorePasswd";
    String trustStorePasswd = "trustStorePasswd";
    String keyStorePasswdFile = "keyStorePasswdFile";
    String capiEnabled = "capiEnabled";
    String nssEnabled = "nssEnabled";
    String pkcs11Config = "pkcs11Config";
    String pksc11Provider = "pksc11Provider";
    String sslContextType = "sslContextType";
    String keyStoreType = "keyStoreType";
    String trustStoreType = "trustStoreType";
    String keyMgrAlgorithm = "keyMgrAlgorithm";
    String trustMgrAlgorithm = "trustMgrAlgorithm";
    String certFilterClass = "certFilterClass";
    String certSelector = "certSelector";

      for(int j = 0;j<1;j++) {
//        long start = System.currentTimeMillis();

        for(int i = 0;i<logCount;i++) {
        metricsWrapperSupplier(Level.FINEST, propsFileName, keyStoreFile, trustStoreFile, keyStorePasswd,
            trustStorePasswd, keyStorePasswdFile, capiEnabled, nssEnabled, pkcs11Config, pksc11Provider, sslContextType,
            keyStoreType, trustStoreType, keyMgrAlgorithm, trustMgrAlgorithm, certFilterClass, certSelector);
        }
//        System.out.println((System.currentTimeMillis()-start));

      }
  }

  private static void metricsWrapperSupplier (Level logLevel, String propsFileName, String keyStoreFile,
      String trustStoreFile, String keyStorePasswd, String trustStorePasswd, String keyStorePasswdFile,
      String capiEnabled, String nssEnabled, String pkcs11Config, String pksc11Provider,
      String sslContextType, String keyStoreType, String trustStoreType, String keyMgrAlgorithm,
      String trustMgrAlgorithm, String certFilterClass, String certSelector) {
    propsFileName = "TEST";
    String[] loggingValues = { propsFileName, keyStoreFile, trustStoreFile, keyStorePasswd,
        trustStorePasswd, keyStorePasswdFile, String.valueOf(capiEnabled), String.valueOf(nssEnabled),
        pkcs11Config, pksc11Provider, sslContextType, keyStoreType, trustStoreType, keyMgrAlgorithm,
        trustMgrAlgorithm, certFilterClass, certSelector };
    LogUtilities.log(CallStackUtilTest.class, logLevel,
        () -> supplyLog(loggingValues[0], loggingValues[1], loggingValues[2],
            loggingValues[3], loggingValues[4], loggingValues[5], loggingValues[6], loggingValues[7],
            loggingValues[8], loggingValues[9], loggingValues[10], loggingValues[11], loggingValues[12],
            loggingValues[13], loggingValues[14], loggingValues[15], loggingValues[16]));

  }

  private static String supplyLog (String propsFileName, String keyStoreFile,
      String trustStoreFile, String keyStorePasswd, String trustStorePasswd, String keyStorePasswdFile,
      String capiEnabled, String nssEnabled, String pkcs11Config, String pksc11Provider,
      String sslContextType, String keyStoreType, String trustStoreType, String keyMgrAlgorithm,
      String trustMgrAlgorithm, String certFilterClass, String certSelector) {
    StringBuilder debugStr = new StringBuilder(32768);
    debugStr.append("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    debugStr.append("Java System Properties:").append("toStr(System.getProperties())" ).append('\n');
    debugStr.append("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    debugStr.append("SSLUtilities.initSSLContext(..) initializing with:\n");
    debugStr.append("  props              = ").append(propsFileName                   ).append('\n');
    debugStr.append("  keyStoreFile       = ").append(keyStoreFile                    ).append('\n');
    debugStr.append("  trustStoreFile     = ").append(trustStoreFile                  ).append('\n');
    debugStr.append("  keyStorePasswd     = ").append("passwdToStr(keyStorePasswd)"   ).append('\n');
    debugStr.append("  trustStorePasswd   = ").append("passwdToStr(trustStorePasswd)" ).append('\n');
    debugStr.append("  keyStorePasswdFile = ").append(keyStorePasswdFile              ).append('\n');
    debugStr.append("  capiEnabled        = ").append(capiEnabled                     ).append('\n');
    debugStr.append("  nssEnabled         = ").append(nssEnabled                      ).append('\n');
    debugStr.append("  pkcs11Config       = ").append(pkcs11Config                    ).append('\n');
    debugStr.append("  pksc11Provider     = ").append(pksc11Provider                  ).append('\n');
    debugStr.append("  sslContextType     = ").append(sslContextType                  ).append('\n');
    debugStr.append("  keyStoreType       = ").append(keyStoreType                    ).append('\n');
    debugStr.append("  trustStoreType     = ").append(trustStoreType                  ).append('\n');
    debugStr.append("  keyMgrAlgorithm    = ").append(keyMgrAlgorithm                 ).append('\n');
    debugStr.append("  trustMgrAlgorithm  = ").append(trustMgrAlgorithm               ).append('\n');
    debugStr.append("  certFilterClass    = ").append(certFilterClass                 ).append('\n');
    debugStr.append("  certSelector       = ").append(certSelector                    ).append('\n');
    debugStr.append("  -------------------------------------------------------------------------\n");
    debugStr.append("  userQuery          = ").append("userQuery"                     ).append('\n');
    debugStr.append("  groupQuery         = ").append("groupQuery"                    ).append('\n');
    debugStr.append("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    return debugStr.toString();
  }


  void runTestFromWrapper1 () {
    StackTraceElement caller;

    // java 8 ~1500 ms
    // java 9+ ~1150 ms
//    caller = getCaller(Integer.class);
//    assertThat(caller,                  nullValue());

    // java8 ~550ms
    // java9 ~950ms
    caller = getCaller(Wrapper1.class);
    assertThat(caller.getClassName(),   is(CallStackUtilTest.class.getName()));
    assertThat(caller.getFileName(),    is("CallStackUtilTest.java"));
    assertThat(caller.getMethodName(),  is("testGetCaller"));
    assertThat(caller.isNativeMethod(), is(false));

    // Passing Wrapper should not change anything here...
    // Java 8 metrics - ~2000 ms
    // java 9 metrics - ~1100 ms
    caller = getCaller(Wrapper1.class, Wrapper.class);
    assertThat(caller.getClassName(),   is(CallStackUtilTest.class.getName()));
    assertThat(caller.getFileName(),    is("CallStackUtilTest.java"));
    assertThat(caller.getMethodName(),  is("testGetCaller"));
    assertThat(caller.isNativeMethod(), is(false));

//    // This should save on Wrapper and return that when Integer is not found...
    // Java 8 metrics - ~2000 ms
    // java 9 metrics - ~1150 ms
    caller = getCaller(Wrapper1.class, Integer.class);
    assertThat(caller.getClassName(),   is(CallStackUtilTest.class.getName()));
    assertThat(caller.getFileName(),    is("CallStackUtilTest.java"));
    assertThat(caller.getMethodName(),  is("testGetCaller"));
    assertThat(caller.isNativeMethod(), is(false));
  }

  void runTestFromWrapper2 () {
    StackTraceElement caller;

    caller = getCaller(Integer.class);
    assertThat(caller,                  nullValue());

    caller = getCaller(Wrapper2.class);
    assertThat(caller.getClassName(),   is(CallStackUtilTest.class.getName()));
    assertThat(caller.getFileName(),    is("CallStackUtilTest.java"));
    assertThat(caller.getMethodName(),  is("testGetCaller"));
    assertThat(caller.isNativeMethod(), is(false));
  }

  static abstract class Wrapper {
    abstract void runTest (CallStackUtilTest test) throws Exception;
  }

  static class Wrapper1 extends Wrapper {
    @Override
    void runTest (CallStackUtilTest test) throws Exception {
      // Normal...
//      System.out.println("Running in "+ (CallStackUtil.isJava9Plus ? "Java 9" : "Java 8") + " mode");
      for (int i = 0; i < 5; i++) {
//        long start = System.currentTimeMillis();

        for (int j = 0; j < logCount; j++) {
          test.runTestFromWrapper1();
        }
//        System.out.println(System.currentTimeMillis() - start);
      }
      // Via reflection...
      Method m = CallStackUtilTest.class.getDeclaredMethod("runTestFromWrapper1");
      m.invoke(test);
    }
  }

  private static class Wrapper2 extends Wrapper {
    @Override
    void runTest (CallStackUtilTest test) throws Exception {
      // This doesn't quite get the hidden thing right...
      (new Hidden()).runTest(test);
    }
  }

  @IgnoreStackElement
  private static class Hidden {
    void runTest (CallStackUtilTest test) {
      test.runTestFromWrapper2();
    }
  }
}
