package zemberek.core.math;

import com.google.common.base.Stopwatch;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

public class LogMathTest {

  @Test
  public void logSumTest() throws IOException {
    double[] aLinear = new double[1000];
    double[] bLinear = new double[1000];

    for (int i = 0; i < bLinear.length; i++) {
      aLinear[i] = (double) i / 1000d;
      bLinear[i] = aLinear[i];
    }

    LogMath.LogSumLookup logSumE = LogMath.LOG_SUM;

    for (double a : aLinear) {
      for (double b : bLinear) {
        double logSumExpected = Math.log(a + b);
        Assert.assertEquals(logSumExpected, logSumE.lookup(Math.log(a), Math.log(b)), 0.001);
      }
    }
  }

  @Test
  public void logSumFloatTest() throws IOException {
    float[] aLinear = new float[1000];
    float[] bLinear = new float[1000];

    for (int i = 0; i < bLinear.length; i++) {
      aLinear[i] = (float) i / 1000f;
      bLinear[i] = aLinear[i];
    }

    for (float a : aLinear) {
      for (float b : bLinear) {
        float logSumExpected = (float) Math.log(a + b);
        Assert
            .assertEquals(logSumExpected, LogMath.LOG_SUM.lookup(Math.log(a), Math.log(b)), 0.001);
      }
    }
  }

  @Test
  public void logSumExactTest() throws IOException {
    double[] aLinear = new double[1000];
    double[] bLinear = new double[1000];

    int j = 0;
    for (int i = 0; i < bLinear.length; i++) {
      aLinear[j] = (double) i / 1000d;
      bLinear[j] = aLinear[j];
      j++;
    }

    for (double a : aLinear) {
      for (double b : bLinear) {
        double logSumExact = LogMath.logSum(Math.log(a), Math.log(b));
        Assert.assertEquals("a=" + a + " b=" + b + " lin=" + Math.log(a + b),
            logSumExact,
            LogMath.LOG_SUM.lookup(Math.log(a), Math.log(b)), 0.001);
      }
    }
  }

  @Test
  @Ignore("Not a unit test")
  public void logSumPerf() throws IOException {
    double[] loga = new double[15000];
    double[] logb = new double[15000];

    int j = 0;
    for (int i = 0; i < logb.length; i++) {
      loga[j] = Math.log((double) i / 100000d);
      logb[j] = loga[j];
      j++;
    }

    Stopwatch sw = Stopwatch.createStarted();
    for (double a : loga) {
      for (double b : logb) {
        LogMath.logSum(a, b);
      }
    }
    System.out.println("Exact: " + sw.elapsed(TimeUnit.MILLISECONDS));
    sw.reset().start();
    for (double a : loga) {
      for (double b : logb) {
        LogMath.LOG_SUM.lookup(a, b);
      }
    }
    System.out.println("Lookup: " + sw.elapsed(TimeUnit.MILLISECONDS));
    sw.stop();
  }

  @Test
  public void logSumError() throws IOException {

    int VALS = 1000000;

    double[] logA = new double[VALS];

    for (int a = 0; a < VALS; a++) {
      if (a == 0) {
        logA[a] = LogMath.LOG_ZERO;
      } else {
        logA[a] = Math.log((double) a / VALS);
      }
    }

    Stopwatch sw = Stopwatch.createStarted();

    double maxError = 0;
    double a = 0;
    double b = 0;

    for (int i = 0; i < logA.length; i++) {
      double la = logA[i];
      double lb = logA[logA.length - i - 1];
      double exact = LogMath.logSum(la, lb);
      double approx = LogMath.LOG_SUM.lookup(la, lb);
      double error = Math.abs(exact - approx);
      if (error > maxError) {
        maxError = error;
        a = la;
        b = lb;
      }
    }

    System.out.println("Max error: " + maxError);
    System.out.println("Max error values: " + a + ":" + b);
    Assert.assertTrue(maxError < 0.0005);
    System.out.println(sw.elapsed(TimeUnit.MILLISECONDS));
    sw.stop();
  }

  @Test
  public void logSumErrorFloat() {

    int VALS = 1000000;

    float[] logA = new float[VALS];

    for (int a = 0; a < VALS; a++) {
      if (a == 0) {
        logA[a] = LogMath.LOG_ZERO_FLOAT;
      } else {
        logA[a] = (float) Math.log((double) a / VALS);
      }
    }

    Stopwatch sw = Stopwatch.createStarted();

    float maxError = 0;
    float a = 0;
    float b = 0;

    for (int i = 0; i < logA.length; i++) {
      float la = logA[i];
      float lb = logA[logA.length - i - 1];
      float exact = (float) LogMath.logSum(la, lb);
      float approx = LogMath.LOG_SUM_FLOAT.lookup(la, lb);
      float error = Math.abs(exact - approx);
      if (error > maxError) {
        maxError = error;
        a = la;
        b = lb;
      }
    }

    System.out.println("Max error: " + maxError);
    System.out.println("Max error values: " + a + ":" + b);
    Assert.assertTrue(maxError < 0.007);
    System.out.println(sw.elapsed(TimeUnit.MILLISECONDS));
    sw.stop();
  }

  @Test
  public void testLog2() {
    Assert.assertEquals(2, LogMath.log2(4), 0.0001);
    Assert.assertEquals(3, LogMath.log2(8), 0.0001);
    Assert.assertEquals(10, LogMath.log2(1024), 0.0001);
    Assert.assertEquals(-1, LogMath.log2(0.5), 0.0001);
  }

  @Test
  public void linearToLogTest() {
    LogMath.LinearToLogConverter converter = new LogMath.LinearToLogConverter(Math.E);
    Assert.assertEquals(Math.log(2), converter.convert(2), 0.00000001d);
    converter = new LogMath.LinearToLogConverter(10d);
    Assert.assertEquals(Math.log10(2), converter.convert(2), 0.00000001d);

  }
}
