/*
 * Copyright 2003-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.generator.test;

import jetbrains.mps.generator.GenerationCanceledException;
import jetbrains.mps.generator.impl.GenerationFailureException;
import jetbrains.mps.generator.impl.GenerationTaskPool;
import jetbrains.mps.generator.impl.IGenerationTaskPool.GenerationTask;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Evgeny Gryaznov, Apr 7, 2010
 */
public class ParallelPoolTest extends TestCase {

  private static Logger LOG = LogManager.getLogger(ParallelPoolTest.class);

  private static class CustomTask implements GenerationTask {
    private AtomicBoolean isFinished = new AtomicBoolean(false);

    private final long amountOfWork;

    private CustomTask(long amountOfWork) {
      this.amountOfWork = amountOfWork;
    }

    public boolean isFinished() {
      return isFinished.get();
    }

    @Override
    public void run() throws GenerationCanceledException, GenerationFailureException {
      isFinished.set(false);
      long localCounter = amountOfWork;
      long fract = 1;
      while (fract < amountOfWork) {
        fract <<= 1;
      }
      fract = (fract >> 6);
      long fractCounter;
      long start = System.currentTimeMillis();
      while (localCounter > 0) {
        fractCounter = fract;
        while (fractCounter > 0) {
          fractCounter--;
        }
        localCounter -= fract;
      }
      long end = System.currentTimeMillis();
      LOG.info("Took " + (end - start) / 1000. + " secs");
      isFinished.set(true);
    }
  }

  private static class CancelTask extends CustomTask {
    public CancelTask(long delay) {
      super(delay);
    }

    @Override
    public void run() throws GenerationCanceledException, GenerationFailureException {
      super.run();
      throw new GenerationCanceledException();
    }
  }

  private static long get2SecsOperation() {
    int measureTime = 500;

    long count2secs = 0;
    long startTime = System.currentTimeMillis();
    long endTime = startTime + measureTime;
    while (System.currentTimeMillis() < endTime) {
      long localCounter = 0;
      while (localCounter < 10000000l) {
        localCounter++;
      }
      count2secs += localCounter;
    }
    endTime = System.currentTimeMillis();
    long amountOfWork = count2secs * measureTime / (endTime - startTime);
    long localCounter = amountOfWork;
    startTime = System.currentTimeMillis();
    while (localCounter > 0) {
      localCounter--;
    }
    endTime = System.currentTimeMillis();
    amountOfWork = amountOfWork * 2000 / (endTime - startTime);
    return amountOfWork;
  }

  private static CustomTask[] createTasks(long amountOfWork, int numberOfTasks) {
    List<CustomTask> tasks = new ArrayList<CustomTask>();
    for (int i = 0; i < numberOfTasks; i++) {
      tasks.add(new CustomTask(amountOfWork));
    }
    return tasks.toArray(new CustomTask[numberOfTasks]);
  }

  public void testPoolSpeed() {
    int cores = Runtime.getRuntime().availableProcessors();
    if (cores < 3) {
      // cannot proceed
      return;
    }

    long amountFor2secs = get2SecsOperation();
    LOG.info("Work amount: " + amountFor2secs + " ticks");

    long start = System.currentTimeMillis();
    GenerationTaskPool pool = new GenerationTaskPool(4);
    final CustomTask[] generationTasks = createTasks(amountFor2secs, 4);
    for (GenerationTask t : generationTasks) {
      pool.addTask(t);
    }

    try {
      pool.waitForCompletion();
    } catch (GenerationCanceledException e) {
      Assert.fail();
    } catch (GenerationFailureException e) {
      Assert.fail();
    }

    long end = System.currentTimeMillis();

    for (CustomTask t : generationTasks) {
      Assert.assertTrue(t.isFinished());
    }

    LOG.info("Total " + (end - start) / 1000. + " seconds to complete 4 x 2secs tasks");
    Assert.assertTrue("too slow: " + (end - start), (end - start) < 4500); // at least 2 core cpu
    pool.dispose();
  }

  public void testPoolCancelling() {
    int cores = Runtime.getRuntime().availableProcessors();
    if (cores < 3) {
      // cannot proceed
      return;
    }

    long amountFor2secs = get2SecsOperation();
    long duration = doCancelTest(amountFor2secs * 4, 0, 1000);

    LOG.info("Total " + duration / 1000. + " seconds, when cancelled after 1 sec.");
    Assert.assertTrue(duration < 1500 && duration > 970);
  }

  public void testPoolCancelling2() {
    int cores = Runtime.getRuntime().availableProcessors();
    if (cores < 3) {
      // cannot proceed
      return;
    }

    long amountFor2secs = get2SecsOperation();
    long duration = doCancelTest(amountFor2secs * 4, Math.round(amountFor2secs * 0.8), 0);

    LOG.info("Total " + duration / 1000. + " seconds (should be 2 secs), when cancelled after 1.6 secs");
    Assert.assertTrue(duration < 2300 && duration > 1700);
  }

  private long doCancelTest(long taskWork, long cancelDelayWork, long msPoolWaitDelay) {

    final long start = System.currentTimeMillis();
    GenerationTaskPool pool = new GenerationTaskPool(4);
    final CustomTask[] generationTasks = createTasks(taskWork, 4);
    pool.addTask(new CancelTask(cancelDelayWork));
    for (GenerationTask t : generationTasks) {
      pool.addTask(t);
    }

    boolean canceledExc = false;
    try {
      if (msPoolWaitDelay > 0) {
        // let the pool detect cancellation after specified delay
        Thread.sleep(msPoolWaitDelay);
      }
      pool.waitForCompletion();
    } catch (GenerationCanceledException e) {
      canceledExc = true;
    } catch (GenerationFailureException e) {
    } catch (InterruptedException ex) {
    }
    pool.dispose();
    Assert.assertTrue(canceledExc);

    long end = System.currentTimeMillis();

    for (CustomTask t : generationTasks) {
      Assert.assertFalse("task should not be finished", t.isFinished());
    }
    return end - start;
  }
}
