/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq.broker.scheduler;

import java.io.File;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.IOHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class JobSchedulerTest {

   private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerTest.class);

   private JobSchedulerStore store;
   private JobScheduler scheduler;

   @Test
   public void testAddLongStringByteSequence() throws Exception {
      final int COUNT = 10;
      final CountDownLatch latch = new CountDownLatch(COUNT);
      scheduler.addListener(new JobListener() {
         @Override
         public void scheduledJob(String id, ByteSequence job) {
            latch.countDown();
         }

      });
      for (int i = 0; i < COUNT; i++) {
         String test = new String("test" + i);
         scheduler.schedule("id" + i, new ByteSequence(test.getBytes()), 1000);
      }
      latch.await(5, TimeUnit.SECONDS);
      assertEquals(0, latch.getCount());
   }

   @Test
   public void testAddCronAndByteSequence() throws Exception {

      final CountDownLatch latch = new CountDownLatch(1);
      scheduler.addListener(new JobListener() {
         @Override
         public void scheduledJob(String id, ByteSequence job) {
            latch.countDown();
         }
      });

      Calendar current = Calendar.getInstance();
      current.add(Calendar.MINUTE, 1);
      int minutes = current.get(Calendar.MINUTE);
      int hour = current.get(Calendar.HOUR_OF_DAY);
      int day = current.get(Calendar.DAY_OF_WEEK) - 1;

      String cronTab = String.format("%d %d * * %d", minutes, hour, day);

      String str = new String("test1");
      scheduler.schedule("id:1", new ByteSequence(str.getBytes()), cronTab, 0, 0, 0);

      // need a little slack so go over 60 seconds
      assertTrue(latch.await(70, TimeUnit.SECONDS));
      assertEquals(0, latch.getCount());
   }

   @Test
   public void testAddLongLongIntStringByteSequence() throws Exception {
      final int COUNT = 10;
      final CountDownLatch latch = new CountDownLatch(COUNT);
      scheduler.addListener(new JobListener() {
         @Override
         public void scheduledJob(String id, ByteSequence job) {
            latch.countDown();
         }
      });
      long time = 2000;
      for (int i = 0; i < COUNT; i++) {
         String test = new String("test" + i);
         scheduler.schedule("id" + i, new ByteSequence(test.getBytes()), "", time, 10, -1);
      }
      assertTrue(latch.getCount() == COUNT);
      latch.await(3000, TimeUnit.SECONDS);
      assertTrue(latch.getCount() == 0);
   }

   @Test
   public void testAddStopThenDeliver() throws Exception {
      final int COUNT = 10;
      final CountDownLatch latch = new CountDownLatch(COUNT);
      long time = 2000;
      for (int i = 0; i < COUNT; i++) {
         String test = new String("test" + i);
         scheduler.schedule("id" + i, new ByteSequence(test.getBytes()), "", time, 1000, -1);
      }
      File directory = store.getDirectory();
      tearDown();
      startStore(directory);
      scheduler.addListener(new JobListener() {
         @Override
         public void scheduledJob(String id, ByteSequence job) {
            latch.countDown();
         }
      });
      assertTrue(latch.getCount() == COUNT);
      latch.await(3000, TimeUnit.SECONDS);
      assertTrue(latch.getCount() == 0);
   }

   @Test
   public void testRemoveLong() throws Exception {
      final int COUNT = 10;

      long time = 60000;
      for (int i = 0; i < COUNT; i++) {
         String str = new String("test" + i);
         scheduler.schedule("id" + i, new ByteSequence(str.getBytes()), "", time, 1000, -1);
      }

      int size = scheduler.getAllJobs().size();
      assertEquals(size, COUNT);

      long removeTime = scheduler.getNextScheduleTime();
      scheduler.remove(removeTime);

      // If all jobs are not started within the same second we need to call remove again
      if (size != 0) {
         removeTime = scheduler.getNextScheduleTime();
         scheduler.remove(removeTime);
      }

      size = scheduler.getAllJobs().size();
      assertEquals(0, size);
   }

   @Test
   public void testRemoveString() throws Exception {
      final int COUNT = 10;
      final String test = "TESTREMOVE";
      long time = 20000;

      for (int i = 0; i < COUNT; i++) {
         String str = new String("test" + i);
         scheduler.schedule("id" + i, new ByteSequence(str.getBytes()), "", time, 1000, -1);
         if (i == COUNT / 2) {
            scheduler.schedule(test, new ByteSequence(test.getBytes()), "", time, 1000, -1);
         }
      }

      int size = scheduler.getAllJobs().size();
      assertEquals(size, COUNT + 1);
      scheduler.remove(test);
      size = scheduler.getAllJobs().size();
      assertEquals(size, COUNT);
   }

   @Test
   public void testGetExecutionCount() throws Exception {
      final String jobId = "Job-1";
      long time = 10000;
      final CountDownLatch done = new CountDownLatch(10);

      String str = new String("test");
      scheduler.schedule(jobId, new ByteSequence(str.getBytes()), "", time, 1000, 10);

      int size = scheduler.getAllJobs().size();
      assertEquals(size, 1);

      scheduler.addListener(new JobListener() {
         @Override
         public void scheduledJob(String id, ByteSequence job) {
            LOG.info("Job executed: {}", 11 - done.getCount());
            done.countDown();
         }
      });

      List<Job> jobs = scheduler.getNextScheduleJobs();
      assertEquals(1, jobs.size());
      Job job = jobs.get(0);
      assertEquals(jobId, job.getJobId());
      assertEquals(0, job.getExecutionCount());
      assertTrue("Should have fired ten times.", done.await(60, TimeUnit.SECONDS));
      // The job is not updated on the last firing as it is removed from the store following
      // it's last execution so the count will always be one less than the max firings.
      assertTrue(job.getExecutionCount() >= 9);
   }

   @Test
   public void testgetAllJobs() throws Exception {
      final int COUNT = 10;
      final String ID = "id:";
      long time = 20000;

      for (int i = 0; i < COUNT; i++) {
         String str = new String("test" + i);
         scheduler.schedule(ID + i, new ByteSequence(str.getBytes()), "", time, 10 + i, -1);
      }

      List<Job> list = scheduler.getAllJobs();

      assertEquals(list.size(), COUNT);
      int count = 0;
      for (Job job : list) {
         assertEquals(job.getJobId(), ID + count);
         count++;
      }
   }

   @Test
   public void testgetAllJobsInRange() throws Exception {
      final int COUNT = 10;
      final String ID = "id:";
      long start = 10000;

      for (int i = 0; i < COUNT; i++) {
         String str = new String("test" + i);
         scheduler.schedule(ID + i, new ByteSequence(str.getBytes()), "", start + (i * 1000), 10000 + i, 0);
      }

      start = System.currentTimeMillis();
      long finish = start + 12000 + (COUNT * 1000);
      List<Job> list = scheduler.getAllJobs(start, finish);

      assertEquals(COUNT, list.size());
      int count = 0;
      for (Job job : list) {
         assertEquals(job.getJobId(), ID + count);
         count++;
      }
   }

   @Test
   public void testRemoveAllJobsInRange() throws Exception {
      final int COUNT = 10;
      final String ID = "id:";
      long start = 10000;

      for (int i = 0; i < COUNT; i++) {
         String str = new String("test" + i);
         scheduler.schedule(ID + i, new ByteSequence(str.getBytes()), "", start + (i * 1000), 10000 + i, 0);
      }
      start = System.currentTimeMillis();
      long finish = start + 12000 + (COUNT * 1000);
      scheduler.removeAllJobs(start, finish);

      assertTrue(scheduler.getAllJobs().isEmpty());
   }

   @Before
   public void setUp() throws Exception {
      File directory = new File("target/test/ScheduledJobsDB");
      IOHelper.mkdirs(directory);
      IOHelper.deleteChildren(directory);
      startStore(directory);
   }

   protected JobSchedulerStore createJobSchedulerStore() throws Exception {
      return new JobSchedulerStoreImpl();
   }

   protected void startStore(File directory) throws Exception {
      store = createJobSchedulerStore();
      store.setDirectory(directory);
      store.start();
      scheduler = store.getJobScheduler("test");
      scheduler.startDispatching();
   }

   @After
   public void tearDown() throws Exception {
      scheduler.stopDispatching();
      store.stop();
   }
}
