package com.linkedin.parseq;

import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

import org.testng.annotations.Test;

import com.linkedin.parseq.trace.ResultType;
import com.linkedin.parseq.trace.Trace;


/**
 * @author Jaroslaw Odzga (jodzga@linkedin.com)
 */
public class TestFusionTask extends AbstractTaskTest {

  @Test
  public void testFlatMapFuncReturnNulll() {
    super.testFlatMapFuncReturnNulll();
  }

  @Test
  public void testFlattenTaskReturnNulll() {
    super.testFlattenTaskReturnNulll();
  }

  @Test
  public void testRecoverWithFuncReturnNulll() {
    super.testRecoverWithFuncReturnNulll();
  }

  @Test
  public void testWithSideEffectFuncReturnNulll() {
    super.testWithSideEffectFuncReturnNulll();
  }

  @Test
  public void testMap() {
    testMap(3);
  }

  @Test
  public void testFlatMap() {
    testFlatMap(5);
  }

  @Test
  public void testAndThenConsumer() {
    testAndThenConsumer(3);
  }

  @Test
  public void testAndThenTask() {
    testAndThenTask(3);
  }

  @Test
  public void testRecover() {
    testRecover(4);
  }

  @Test
  public void testRecoverWithExceptionFilter() {
    testRecoverWithExceptionFilter(4);
  }

  @Test
  public void testCancelledRecover() {
    testCancelledRecover(4);
  }

  @Test
  public void testNoRecover() {
    testNoRecover(3);
  }

  @Test
  public void testTry() {
    testToTry(4);
  }

  @Test
  public void testRecoverWithSuccess() {
    testRecoverWithSuccess(2);
  }

  @Test
  public void testRecoverWithFailure() {
    testRecoverWithFailure(3);
  }

  @Test
  public void testRecoverWithCancelled() {
    testRecoverWithCancelled(2);
  }

  @Test
  public void testRecoverWithRecoverd() {
    testRecoverWithRecoverd(3);
  }

  @Test
  public void testWithSideEffectFullCompletion() throws Exception {
    testWithSideEffectFullCompletion(4);
  }

  @Test
  public void testWithSideEffectFailure() throws Exception {
    testWithSideEffectFailure(3);
  }

  @Test
  public void testOnFailure() {
    testOnFailure(3);
  }

  @Test
  public void testOnFailureWhenCancelled() {
    testOnFailureWhenCancelled(3);
  }

  @Test
  public void testWithTimeoutAsLastOperation() {
    Task<String> task = delayedValue("value", 250, TimeUnit.MILLISECONDS).map(x -> x + 1).map(x -> TASK_VALUE)
        .withTimeout(5, TimeUnit.MILLISECONDS);

    try {
      runAndWait("TestFusionTask.testWithTimeoutAsLastOperation", task);
      fail("should have failed!");
    } catch (Exception ex) {
      assertSame(ex.getCause().getClass(), Exceptions.TIMEOUT_EXCEPTION.getClass());
    }
  }

  @Test
  public void testWithTimeoutAsMiddleOperation() {
    Task<String> task = delayedValue("value", 250, TimeUnit.MILLISECONDS).map("first", x -> x + 3)
        .withTimeout(5, TimeUnit.MILLISECONDS).map("second", x -> TASK_VALUE);

    try {
      runAndWait("TestFusionTask.testWithTimeoutAsMiddleOperation", task);
      fail("should have failed!");
    } catch (Exception ex) {
      assertSame(ex.getCause().getClass(), Exceptions.TIMEOUT_EXCEPTION.getClass());
    }
  }


  @Test
  public void testTraceCompletness() throws InterruptedException {
    final Task<String> task = delayedValue("value", 10, TimeUnit.MILLISECONDS).map("duplicate", x -> x + x);
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicReference<Trace> trace = new AtomicReference<>();
    task.addListener(s -> {
      trace.set(task.getTrace());
      latch.countDown();
      });
    runAndWait("TestFusionTask.testTraceCompletness", task);
    assertTrue(latch.await(100, TimeUnit.MILLISECONDS), "trace was not abtained in time");
    assertTrue(trace.get().getTraceMap().values().stream()
        .allMatch(shallowTrace -> shallowTrace.getResultType().equals(ResultType.SUCCESS)),
        "all tasks in the trace should have ResultType=SUCCESS");
  }

  @Override
  Task<String> getSuccessTask() {
    return Task.value("success", TASK_VALUE);
  }

  @Override
  Task<String> getFailureTask() {
    return Task.failure("failure", new RuntimeException(TASK_ERROR_MESSAGE));
  }

  @Override
  Task<String> getCancelledTask() {
    return Task.failure("cancelled", new CancellationException(new TimeoutException()));
  }

}
