/*
 * Copyright (C) 2011.
 * All rights reserved.
 */
package ro.isdc.wro.util;

import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Alex Objelean
 */
public class TestSchedulerHelper {
  private static final Logger LOG = LoggerFactory.getLogger(TestSchedulerHelper.class);
  @Mock
  private Runnable mockRunnable;
  private SchedulerHelper helper;

  @Before
  public void setUp() {
    initMocks(this);
  }

  @Test(expected=NullPointerException.class)
  public void cannotAcceptNullArgument() {
    SchedulerHelper.create(null);
  }


  @Test(expected=NullPointerException.class)
  public void cannotAcceptNullRunnable() {
    useNullRunnableWithPeriod(1);
  }

  @Test
  public void canAcceptNullRunnableWhenPeriodIsZero() {
    useNullRunnableWithPeriod(0);
  }

  @Test
  public void runnableNotStartedWhenPeriodIsZero() {
    createAndRunHelperForTest(createSleepingRunnable(1000), 0, TimeUnit.SECONDS);
  }

  @Test
  public void runLongRunningThread() throws Exception {
    createAndRunHelperForTest(createSleepingRunnable(100), 100, TimeUnit.MILLISECONDS);
    Thread.sleep(400);
  }

  @Test
  public void scheduleWithDifferentPeriods() throws Exception {
    helper = SchedulerHelper.create(new DestroyableLazyInitializer<Runnable>() {
      @Override
      protected Runnable initialize() {
        return createSleepingRunnable(10);
      }
    });
    helper.scheduleWithPeriod(10);
    Assert.assertEquals(10, helper.getPeriod());
    Thread.sleep(20);

    helper.scheduleWithPeriod(20);
    Assert.assertEquals(20, helper.getPeriod());
    Thread.sleep(40);
  }

  @Test
  public void scheduleWithSamePeriods() throws Exception {
    helper = SchedulerHelper.create(new DestroyableLazyInitializer<Runnable>() {
      @Override
      protected Runnable initialize() {
        return createSleepingRunnable(10);
      }
    });
    helper.scheduleWithPeriod(10);
    Assert.assertEquals(10, helper.getPeriod());
    Thread.sleep(20);

    helper.scheduleWithPeriod(10);
    Assert.assertEquals(10, helper.getPeriod());
    Thread.sleep(30);
  }

  @Test
  public void schedulerHelperIsSynchronized() throws Exception {
    helper = SchedulerHelper.create(new DestroyableLazyInitializer<Runnable>() {
      @Override
      protected Runnable initialize() {
        return new Runnable() {
          public void run() {
            try {
              LOG.debug("\tRunning thread ...");
              Thread.sleep(40);
            } catch (final Exception e) {
              LOG.error("runnable interrupted", e);
            }
          }
        };
      }
    });
    final ThreadLocal<Long> period = new InheritableThreadLocal<Long>() {
      @Override
      protected Long initialValue() {
        return 0l;
      }
    };

    final ExecutorService service = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 10; i++) {
      period.set(period.get() + 30);
      //Thread.sleep(300);
      service.execute(new Runnable() {
        public void run() {
          helper.scheduleWithPeriod(period.get(), TimeUnit.MILLISECONDS);
        }
      });
    }
    Thread.sleep(400);
    helper.destroy();
    service.shutdown();
  }

  /**
   * creates a runnable which sleeps for a given period of time.
   *
   * @param period
   *          number of milliseconds to sleep.
   */
  private Runnable createSleepingRunnable(final long period) {
    return new Runnable() {
      public void run() {
        try {
          Thread.sleep(period);
        } catch (final Exception e) {
          LOG.error("thread interrupted", e);
        }
      }
    };
  }

  private void useNullRunnableWithPeriod(final long period) {
    createAndRunHelperForTest(null, period, TimeUnit.SECONDS);
  }


  private void createAndRunHelperForTest(final Runnable runnable, final long period, final TimeUnit timeUnit) {
    helper = SchedulerHelper.create(new DestroyableLazyInitializer<Runnable>() {
      @Override
      protected Runnable initialize() {
        return runnable;
      }
    });
    helper.scheduleWithPeriod(period, timeUnit);
  }

  @Test
  public void shouldNotInvokeRunnableImmediatelyAfterScheduleIsInvoked() throws Exception {
    helper = SchedulerHelper.create(new DestroyableLazyInitializer<Runnable>() {
      @Override
      protected Runnable initialize() {
        return mockRunnable;
      }
    });
    helper.scheduleWithPeriod(7200);
    Thread.sleep(10);
    verify(mockRunnable, Mockito.never()).run();
  }

  @After
  public void tearDown() {
    if (helper != null) {
      helper.destroy();
    }
  }
}
