package org.robolectric.integrationtests.nativegraphics;

import static android.os.Build.VERSION_CODES.O;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import android.graphics.Interpolator;
import android.graphics.Interpolator.Result;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(minSdk = O)
public class ShadowNativeInterpolatorTest {
  private static final int DEFAULT_KEYFRAME_COUNT = 2;
  private static final float TOLERANCE = 0.1f;

  @Test
  public void testConstructor() {
    Interpolator interpolator = new Interpolator(10);
    assertEquals(10, interpolator.getValueCount());
    assertEquals(DEFAULT_KEYFRAME_COUNT, interpolator.getKeyFrameCount());

    interpolator = new Interpolator(15, 20);
    assertEquals(15, interpolator.getValueCount());
    assertEquals(20, interpolator.getKeyFrameCount());
  }

  @Test
  public void testReset1() {
    final int expected = 100;
    Interpolator interpolator = new Interpolator(10);
    assertEquals(DEFAULT_KEYFRAME_COUNT, interpolator.getKeyFrameCount());
    interpolator.reset(expected);
    assertEquals(expected, interpolator.getValueCount());
    assertEquals(DEFAULT_KEYFRAME_COUNT, interpolator.getKeyFrameCount());
  }

  @Test
  public void testReset2() {
    int expected1 = 100;
    int expected2 = 200;
    // new the Interpolator instance
    Interpolator interpolator = new Interpolator(10);
    interpolator.reset(expected1, expected2);
    assertEquals(expected1, interpolator.getValueCount());
    assertEquals(expected2, interpolator.getKeyFrameCount());
  }

  @Test
  public void testTimeToValues2() {
    Interpolator interpolator = new Interpolator(1);
    interpolator.setKeyFrame(0, 2000, new float[] {1.0f});
    interpolator.setKeyFrame(1, 4000, new float[] {2.0f});
    verifyValue(1000, 1.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(6000, 2.0f, Result.FREEZE_END, interpolator);

    // known bug: time argument is unsigned 32bit in graphics library
    verifyValue(-1000, 2.0f, Result.FREEZE_END, interpolator);

    interpolator.reset(1, 3);
    interpolator.setKeyFrame(0, 2000, new float[] {1.0f});
    interpolator.setKeyFrame(1, 4000, new float[] {2.0f});
    interpolator.setKeyFrame(2, 6000, new float[] {4.0f});
    verifyValue(0, 1.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(8000, 4.0f, Result.FREEZE_END, interpolator);

    final int valueCount = 2;
    final int validTime = 0;
    interpolator.reset(valueCount);
    assertEquals(valueCount, interpolator.getValueCount());
    try {
      // value array too short
      interpolator.timeToValues(validTime, new float[valueCount - 1]);
      fail("should throw out ArrayStoreException");
    } catch (ArrayStoreException e) {
      // expected
    }

    interpolator.reset(2, 2);
    interpolator.setKeyFrame(0, 4000, new float[] {1.0f, 1.0f});
    interpolator.setKeyFrame(1, 6000, new float[] {2.0f, 4.0f});
    verifyValues(2000, new float[] {1.0f, 1.0f}, Result.FREEZE_START, interpolator);
    verifyValues(5000, new float[] {1.5f, 2.5f}, Result.NORMAL, interpolator);
    verifyValues(8000, new float[] {2.0f, 4.0f}, Result.FREEZE_END, interpolator);
  }

  @Test
  public void testSetRepeatMirror() {
    Interpolator interpolator = new Interpolator(1, 3);
    interpolator.setKeyFrame(0, 2000, new float[] {1.0f});
    interpolator.setKeyFrame(1, 4000, new float[] {2.0f});
    interpolator.setKeyFrame(2, 6000, new float[] {4.0f});

    verifyValue(1000, 1.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(7000, 4.0f, Result.FREEZE_END, interpolator);

    // repeat once, no mirror
    interpolator.setRepeatMirror(2, false);
    verifyValue(1000, 4.0f, Result.FREEZE_END, interpolator); // known bug
    verifyValue(3000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(7000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(9000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(11000, 4.0f, Result.FREEZE_END, interpolator);

    // repeat once, mirror
    interpolator.setRepeatMirror(2, true);
    verifyValue(1000, 4.0f, Result.FREEZE_END, interpolator); // known bug
    verifyValue(3000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(7000, 3.0f, Result.NORMAL, interpolator);
    verifyValue(9000, 1.5f, Result.NORMAL, interpolator);
    verifyValue(11000, 4.0f, Result.FREEZE_END, interpolator);
  }

  @Test
  public void testSetKeyFrame() {
    final float[] aZero = new float[] {0.0f};
    final float[] aOne = new float[] {1.0f};

    Interpolator interpolator = new Interpolator(1);
    interpolator.setKeyFrame(0, 2000, aZero);
    interpolator.setKeyFrame(1, 4000, aOne);
    verifyValue(1000, 0.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 0.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 1.0f, Result.FREEZE_END, interpolator);

    final float[] linearBlend = new float[] {0.0f, 0.0f, 1.0f, 1.0f};
    final float[] accelerateBlend =
        new float[] {
          // approximate circle at PI/6 and PI/3
          0.5f, 1.0f - 0.866f, 0.866f, 0.5f
        };
    final float[] decelerateBlend =
        new float[] {
          // approximate circle at PI/6 and PI/3
          1.0f - 0.866f, 0.5f, 0.5f, 0.866f
        };

    // explicit linear blend should yield the same values
    interpolator.setKeyFrame(0, 2000, aZero, linearBlend);
    interpolator.setKeyFrame(1, 4000, aOne, linearBlend);
    verifyValue(1000, 0.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 0.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 1.0f, Result.FREEZE_END, interpolator);

    // blend of end key frame is not used
    interpolator.setKeyFrame(0, 2000, aZero);
    interpolator.setKeyFrame(1, 4000, aOne, accelerateBlend);
    verifyValue(1000, 0.0f, Result.FREEZE_START, interpolator);
    verifyValue(3000, 0.5f, Result.NORMAL, interpolator);
    verifyValue(5000, 1.0f, Result.FREEZE_END, interpolator);

    final float[] result = new float[1];

    interpolator.setKeyFrame(0, 2000, aZero, accelerateBlend);
    interpolator.setKeyFrame(1, 4000, aOne);
    verifyValue(1000, 0.0f, Result.FREEZE_START, interpolator);
    assertEquals(Result.NORMAL, interpolator.timeToValues(3000, result));
    assertThat(result[0]).isLessThan(0.5f); // exact blend algorithm not known
    verifyValue(5000, 1.0f, Result.FREEZE_END, interpolator);

    interpolator.setKeyFrame(0, 2000, aZero, decelerateBlend);
    interpolator.setKeyFrame(1, 4000, aOne);
    verifyValue(1000, 0.0f, Result.FREEZE_START, interpolator);
    assertEquals(Result.NORMAL, interpolator.timeToValues(3000, result));
    assertThat(result[0]).isGreaterThan(0.5f); // exact blend algorithm not known
    verifyValue(5000, 1.0f, Result.FREEZE_END, interpolator);

    final int validTime = 0;
    final int valueCount = 2;
    interpolator.reset(valueCount);
    assertEquals(valueCount, interpolator.getValueCount());
    try {
      // value array too short
      interpolator.setKeyFrame(0, validTime, new float[valueCount - 1]);
      fail("should throw ArrayStoreException");
    } catch (ArrayStoreException e) {
      // expected
    }

    try {
      // index too small
      interpolator.setKeyFrame(-1, validTime, new float[valueCount]);
      fail("should throw IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
      // expected
    }

    try {
      // index too large
      interpolator.setKeyFrame(2, validTime, new float[valueCount]);
      fail("should throw IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
      // expected
    }

    try {
      // blend array too short
      interpolator.setKeyFrame(0, validTime, new float[valueCount], new float[3]);
      fail("should throw ArrayStoreException");
    } catch (ArrayStoreException e) {
      // expected
    }
  }

  private void verifyValue(
      int time, float expected, Result expectedResult, Interpolator interpolator) {
    float[] values = new float[1];
    assertEquals(expectedResult, interpolator.timeToValues(time, values));
    assertEquals(expected, values[0], TOLERANCE);
  }

  private void verifyValues(
      int time, float[] expected, Result expectedResult, Interpolator interpolator) {
    float[] values = new float[expected.length];
    assertEquals(expectedResult, interpolator.timeToValues(time, values));
    assertArrayEquals(expected, values, TOLERANCE);
  }
}
