package com.github.mustachejava;

import com.github.mustachejavabenchmarks.BenchmarkTest;
import org.junit.Test;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

import static com.github.mustachejavabenchmarks.BenchmarkTest.skip;
import static junit.framework.Assert.assertEquals;

/**
 * Inspired by an unconfirmed bug report.
 */
public class ConcurrencyTest {

  static Random r = new SecureRandom();

  private static class TestObject {
    final int a;
    final int b;
    final int c;

    int aa() throws InterruptedException {
      Thread.sleep(r.nextInt(10));
      return a;
    }

    int bb() throws InterruptedException {
      Thread.sleep(r.nextInt(10));
      return b;
    }

    int cc() throws InterruptedException {
      Thread.sleep(r.nextInt(10));
      return c;
    }

    Callable<Integer> calla() throws InterruptedException {
      return new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
          Thread.sleep(r.nextInt(10));
          return a;
        }
      };
    }

    Callable<Integer> callb() throws InterruptedException {
      return new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
          Thread.sleep(r.nextInt(10));
          return b;
        }
      };
    }

    Callable<Integer> callc() throws InterruptedException {
      return new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
          Thread.sleep(r.nextInt(10));
          return c;
        }
      };
    }

    private TestObject(int a, int b, int c) {
      this.a = a;
      this.b = b;
      this.c = c;
    }
  }

  // Alternate render
  static String render(TestObject to) {
    return to.a + ":" + to.b + ":" + to.c;
  }

  @Test
  public void testConcurrentExecution() throws InterruptedException {
    if (skip()) return;
    String template = "{{aa}}:{{bb}}:{{cc}}";
    final Mustache test = new DefaultMustacheFactory().compile(new StringReader(template), "test");
    ExecutorService es = Executors.newCachedThreadPool();
    final AtomicInteger total = new AtomicInteger();
    final Semaphore semaphore = new Semaphore(100);
    for (int i = 0; i < 100000; i++) {
      semaphore.acquire();
      es.submit(new Runnable() {
        @Override
        public void run() {
          try {
          TestObject testObject = new TestObject(r.nextInt(), r.nextInt(), r.nextInt());
          StringWriter sw = new StringWriter();
            test.execute(sw, testObject).close();
            if (!render(testObject).equals(sw.toString())) {
              total.incrementAndGet();
            }
          } catch (IOException e) {
            // Can't fail
            e.printStackTrace();
            System.exit(1);
          } finally {
            semaphore.release();
          }
        }
      });
    }
    // Wait for them all to complete
    semaphore.acquire(100);
    assertEquals(0, total.intValue());
  }

  @Test
  public void testConcurrentExecutionWithConcurrentTemplate() throws InterruptedException {
    if (skip()) return;
    String template = "{{calla}}:{{callb}}:{{callc}}";
    ExecutorService es = Executors.newCachedThreadPool();
    DefaultMustacheFactory dmf = new DefaultMustacheFactory();
    dmf.setExecutorService(es);
    final Mustache test = dmf.compile(new StringReader(template), "test");
    final AtomicInteger total = new AtomicInteger();
    final Semaphore semaphore = new Semaphore(100);
    for (int i = 0; i < 100000; i++) {
      semaphore.acquire();
      es.submit(new Runnable() {
        @Override
        public void run() {
          try {
            TestObject testObject = new TestObject(r.nextInt(), r.nextInt(), r.nextInt());
            StringWriter sw = new StringWriter();
            test.execute(sw, testObject).close();
            if (!render(testObject).equals(sw.toString())) {
              total.incrementAndGet();
            }
          } catch (IOException e) {
            // Can't fail
            e.printStackTrace();
            System.exit(1);
          } finally {
            semaphore.release();
          }
        }
      });
    }
    // Wait for them all to complete
    semaphore.acquire(100);
    assertEquals(0, total.intValue());
  }
}
