package fr.umlv.tpnote2;

import static org.junit.Assert.*;

import java.util.ArrayList;

import org.junit.Test;

import fr.umlv.tpnote2.Pool.Promise;

@SuppressWarnings("static-method")
public class PoolTest {

  // Q1 & Q2
  
  @Test
  public void simplePool() {
    Pool pool = Pool.newSimplePool();
    Promise<String> promise = pool.execute(() -> {
      return "hello";
    });
    promise.register(value -> {
      assertEquals("hello", value);
    });
  }
  
  @Test
  public void simplePool2() {
    Pool pool = Pool.newSimplePool();
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    promise.register(value -> {
      assertTrue(value >= 500);
    });
  }
  
  
  // Q3
  
  @Test(timeout = 4000)
  public void waitIfNotAvailableAndGet() throws InterruptedException {
    Pool pool = Pool.newSimplePool();
    Promise<String> promise = pool.execute(() -> {
      return "hello";
    });
    String value = promise.waitIfNotAvailableAndGet();
    assertEquals("hello", value);
  }
  
  @Test(timeout = 4000)
  public void waitIfNotAvailableAndGet2() throws InterruptedException {
    Pool pool = Pool.newSimplePool();
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    Long value = promise.waitIfNotAvailableAndGet();
    assertTrue(value >= 500);
  }
  
  
  // Q4
  
  @Test
  public void fixedSizePool() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<String> promise = pool.execute(() -> {
      return "hello";
    });
    promise.register(value -> {
      assertEquals("hello", value);
    });
  }
  
  
  @Test
  public void fixedSizePool2() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    promise.register(value -> {
      assertTrue(value >= 500);
    });
  }
  
  @Test(timeout = 4000)
  public void waitIfNotAvailableAndGetWithFixedSizePool() throws InterruptedException {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<String> promise = pool.execute(() -> {
      return "hello";
    });
    String value = promise.waitIfNotAvailableAndGet();
    assertEquals("hello", value);
  }
  
  @Test(timeout = 4000)
  public void waitIfNotAvailableAndGet2WithFixedSizePool() throws InterruptedException {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    Long value = promise.waitIfNotAvailableAndGet();
    assertTrue(value >= 500);
  }
  
  
  @Test
  public void fixedSizePoolALot() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      pool.execute(() -> {
        return id;
      }).register(value -> {
        assertEquals(id, (int)value);
      });
    }
  }
  
  @Test(timeout = 1000)
  public void waitIfNotAvailableAndGetWithFixedSizePoolALot() throws InterruptedException {
    Pool pool = Pool.newFixedSizePool(2, 500);
    ArrayList<Promise<Integer>> list = new ArrayList<>();
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      list.add(pool.execute(() -> {
        return id;
      }));
    }
    for(int i = 0; i < 1_000; i++) {
      assertEquals(i, (int)list.get(i).waitIfNotAvailableAndGet());
    }
  }
  
  // Q5
  
  @Test
  public void close() {
    Pool pool = Pool.newSimplePool();
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    pool.close();
    promise.register(value -> {
      assertTrue(value >= 500);
    });
  }
  
  @Test(timeout = 4000)
  public void closeWaitIfNotAvailable() throws InterruptedException {
    Pool pool = Pool.newSimplePool();
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    pool.close();
    Long value = promise.waitIfNotAvailableAndGet();
    assertTrue(value >= 500);
  }
  
  @Test(expected = IllegalStateException.class)
  public void closeAndExecute() {
    Pool pool = Pool.newSimplePool();
    pool.close();
    pool.execute(() -> "boom");
  }
  
  @Test
  public void close2() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    pool.close();
    promise.register(value -> {
      assertTrue(value >= 500);
    });
  }
  
  @Test(timeout = 4000)
  public void closeWaitIfNotAvailable2() throws InterruptedException {
    Pool pool = Pool.newFixedSizePool(2, 500);
    Promise<Long> promise = pool.execute(() -> {
      long start = System.currentTimeMillis();
      try {
        Thread.sleep(1_000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      long end = System.currentTimeMillis();
      return end - start;
    });
    pool.close();
    Long value = promise.waitIfNotAvailableAndGet();
    assertTrue(value >= 500);
  }
  
  @Test(expected = IllegalStateException.class)
  public void closeAndExecute2() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    pool.close();
    pool.execute(() -> "boom");
  }
  
  
  // Q6
  
  @Test
  public void simplePoolFilter() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      pool.execute(() -> {
        return id;
      })
      .filter(value -> true)
      .register(value -> {
        assertEquals(id, (int)value);
      });
    }
  }
  
  @Test
  public void simplePoolFilter2() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      pool.execute(() -> {
        return id;
      })
      .filter(value -> false)
      .register(value -> {
        fail();
      });
    }
  }
  
  @Test
  public void simplePoolMap() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      pool.execute(() -> {
        return id;
      })
      .map(value -> value)
      .register(value -> {
        assertEquals(id, (int)value);
      });
    }
  }
  
  @Test
  public void simplePoolMap2() {
    Pool pool = Pool.newFixedSizePool(2, 500);
    for(int i = 0; i < 1_000; i++) {
      int id = i;
      pool.execute(() -> {
        return "" + id;
      })
      .map(Integer::parseInt)
      .register(value -> {
        assertEquals(id, (int)value);
      });
    }
  }
}
