package fr.umlv.safe;

import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.junit.Test;

@SuppressWarnings("static-method")
public class TPNoteTest {
  // Q1
  
  @Test
  public void testUser() {
    User bob = new User("bob");
    assertEquals("bob", bob.toString());
  }
  @Test
  public void testSafe() {
    User sally = new User("sally");
    Safe<Integer> safe = new Safe<>(sally, 12);
    assertEquals(12, (int)safe.getValue());
  }
  @Test
  public void testSafe2() {
    User dolly = new User("dolly");
    Safe<String> safe = new Safe<>(dolly, "hello");
    assertEquals("hello", safe.getValue());
  }
  @Test
  public void testSafeAuthorised() {
    User john = new User("john");
    Safe<Integer> safe = new Safe<>(john, 12);
    assertTrue(safe.isAuthorized(john));
    assertFalse(safe.isAuthorized(new User("dan")));
  }
  
  
  // Q2
  /*
  @Test
  public void testUserManager() {
    UserManager manager1 = UserManager.getUserManager();
    UserManager manager2 = UserManager.getUserManager();
    assertTrue(manager1 == manager2);
  }
  @Test
  public void testUserManager2() {
    assertEquals(0, UserManager.class.getConstructors().length);
  }
  @Test
  public void testUserManagerCreate() {
    UserManager manager = UserManager.getUserManager();
    User job = manager.createUser("job");
    assertEquals("job", job.toString());
  }
  @Test(expected = IllegalStateException.class)
  public void testUserManagerCreateUserSame() {
    UserManager manager = UserManager.getUserManager();
    manager.createUser("janie");
    manager.createUser("janie");
  }
  @Test(expected = NullPointerException.class)
  public void testUserManagerCreateUserNull() {
    UserManager.getUserManager().createUser(null);
  }
  
  
  // Q3
  
  @Test
  public void testGroup() {
    Group group = new Group("group1");
    assertEquals("group1", group.toString());
  }
  @Test
  public void testGroupAddWithSafe() {
    UserManager manager = UserManager.getUserManager();
    Group group = new Group("group2");
    User jane = manager.createUser("jane");
    group.add(jane);
    Safe<Integer> safe = new Safe<>(group, 1234); 
    assertTrue(safe.isAuthorized(jane));
  }
  @Test
  public void testGroupAddWithSafe2() {
    UserManager manager = UserManager.getUserManager();
    Group group = new Group("group2");
    User jack = manager.createUser("jack");
    Safe<Integer> safe = new Safe<>(group, 1234); 
    assertFalse(safe.isAuthorized(jack));
  }
  @Test
  public void testGroupAddALot() {
    UserManager manager = UserManager.getUserManager();
    Group group = new Group("group3");
    User jill = manager.createUser("arthur");
    for(int i = 0; i < 100_000_000; i++) {
      group.add(jill);
    }
  }
  @Test
  public void testGroupAdd() {
    List<Method> adds = Arrays.stream(Group.class.getMethods()).filter(m -> m.getName().equals("add")).collect(toList());
    assertEquals(1, adds.size());
    assertFalse(adds.get(0).getParameterTypes()[0].isAssignableFrom(Group.class));
  }
  @Test
  public void testSafeConstructor() {
    assertEquals(1, Safe.class.getConstructors().length);
  }
  @Test
  public void testGroupTwiceSameName() {
    Group g1 = new Group("g");
    Group g2 = new Group("g");
    assertEquals("g", g1.toString());
    assertEquals("g", g2.toString());
  }
  @Test(expected = NullPointerException.class)
  public void testGroupNull() {
    new Group(null);
  }
  
  
  // Q4
  
  @Test
  public void testThreadSafe() {
    Group group = new Group("group");
    UserManager manager = UserManager.getUserManager();
    List<Thread> threads = IntStream.range(0, 4).mapToObj(i -> {
      return new Thread(() -> {
        for(int j = 0; j < 100_000; j++) {
          group.add(manager.createUser("user" + i + "/" + j));
        }
      });
    }).collect(Collectors.toList());
    
    threads.forEach(Thread::start);
    threads.forEach(t -> {
      try {
        t.join();
      } catch(InterruptedException e) {
        throw new AssertionError(e);
      }
    });
    
    assertEquals(100_000 * 4, group.size());
  }
  
  @Test
  public void testThreadSafe2() {
    UserManager manager = UserManager.getUserManager();
    List<Thread> threads = IntStream.range(0, 4).mapToObj(i -> {
      return new Thread(() -> {
        for(int j = 0; j < 10_000; j++) {
          String name = "foo" + i + "/" + j;
          manager.createUser(name);
        }
      });
    }).collect(Collectors.toList());
    
    threads.forEach(Thread::start);
    threads.forEach(t -> {
      try {
        t.join();
      } catch(InterruptedException e) {
        throw new AssertionError(e);
      }
    });
    
    for(int i = 0; i < 4; i++) {
      for(int j = 0; j < 10_000; j++) {
        String name = "foo" + i + "/" + j;
        try {
          manager.createUser(name);
          fail();
        } catch(IllegalStateException e) {
          // ok !
        }
      }
    }
  }
  
  
  // Q5
  
  @Test
  public void testCurrentUser() {
    UserManager manager = UserManager.getUserManager();
    User phil = manager.createUser("phil");
    assertTrue(!User.getCurrentUser().isPresent());
    phil.run(() -> {
      assertEquals("phil", User.getCurrentUser().get().toString());
      assertTrue(phil == User.getCurrentUser().get());
    });
    assertTrue(!User.getCurrentUser().isPresent());
  }
  
  @Test
  public void testCurrentUser2() {
    UserManager manager = UserManager.getUserManager();
    User jake = manager.createUser("jake");
    User phoebe = manager.createUser("phoebe");
    phoebe.run(() -> {
      assertTrue(phoebe == User.getCurrentUser().get());
      jake.run(() -> {
        assertTrue(jake == User.getCurrentUser().get());
      });
      assertTrue(phoebe == User.getCurrentUser().get());
    });
  }
  
  @Test
  public void testCurrentUser3() {
    UserManager manager = UserManager.getUserManager();
    User june = manager.createUser("june");
    try {
      june.run(() -> {
        throw new RuntimeException();
      });
      fail();
    } catch(RuntimeException e) {
      // ok !
    }
    User.getCurrentUser().ifPresent(__ -> fail());
  }
  
  
  // Q6
  
  @Test
  public void testSafeUserGetValue() {
    UserManager manager = UserManager.getUserManager();
    User missy = manager.createUser("missy");
    Safe<Integer> safe = new Safe<>(missy, 42);
    missy.run(() -> {
       assertEquals(42, (int)safe.getValue());
    });
  }
  @Test
  public void testSafeGroupGetValue() {
    UserManager manager = UserManager.getUserManager();
    User eliot = manager.createUser("eliot");
    Group group = new Group("group of eliot");
    group.add(eliot);
    Safe<Integer> safe = new Safe<>(group, -123);
    eliot.run(() -> {
       assertEquals(-123, (int)safe.getValue());
    });
  }
  @Test(expected = IllegalStateException.class)
  public void testSafeNoUserGetValue() {
    UserManager manager = UserManager.getUserManager();
    User fanny = manager.createUser("fanny");
    Safe<Integer> safe = new Safe<>(fanny, -189);
    safe.getValue();
  }
  
  @Test(expected = IllegalStateException.class)
  public void testSafeWrongUserGetValue() {
    UserManager manager = UserManager.getUserManager();
    User zorah = manager.createUser("zorah");
    User eleonor = manager.createUser("eleonor");
    Safe<Integer> safe = new Safe<>(zorah, 56);
    eleonor.run(safe::getValue);
  }
  
  
  // Q8
  
  @Test
  public void testWaitUntilAutorized() {
    User paul = UserManager.getUserManager().createUser("paul");
    Group group = new Group("mygroup");
    boolean[] result = { false };
    new Thread(() -> {
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
      result[0] = true;
      group.add(paul);
    }).start();
    try {
      group.waitUntilAuthorized(paul);
    } catch (InterruptedException e) {
      throw new AssertionError(e);
    }
    assertTrue(result[0]);
  }
  
  @Test(timeout = 5000)
  public void testWaitUntilAutorized2() {
    Group group = new Group("anotherGoup");
    boolean[] result = { false };
    Thread thread = new Thread(() -> {
      try {
        group.waitUntilAuthorized(UserManager.getUserManager().createUser("franky"));
        fail();
      } catch (InterruptedException e) {
        result[0] = true;
      }
    });
    thread.start();
    try {
      Thread.sleep(2000);
      thread.interrupt();
      thread.join();
    } catch (InterruptedException e) {
      throw new AssertionError(e);
    }
    assertTrue(result[0]);
  }*/
}
