/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.services.scheduler.test;

import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.container.BaseContainerLifecyclePlugin;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.RootContainer;
import org.exoplatform.container.component.ComponentRequestLifecycle;
import org.exoplatform.services.scheduler.JobInfo;
import org.exoplatform.services.scheduler.JobSchedulerService;
import org.exoplatform.services.scheduler.PeriodInfo;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.TriggerListener;

import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by The eXo Platform SAS Author : Hoa Pham hoapham@exoplatform.com Oct
 * 5, 2005
 */
public class TestSchedulerService extends SchedulerServiceTestBase
{

   public void setUp() throws Exception
   {
      service_ = PortalContainer.getInstance().getComponentInstanceOfType(JobSchedulerService.class);
   }

   public void tearDown() throws Exception
   {
     ExoContainer topContainer = ExoContainerContext.getTopContainer();
     if(topContainer != null) {
        topContainer.stop();
     }
     PortalContainer.setInstance(null);
     RootContainer.setInstance(null);
   }

   public void testQueueTask() throws Exception
   {
      service_.addGlobalTriggerListener(new GlobalTriggerListener());
      AtomicInteger counter = new AtomicInteger(3);
      synchronized (counter)
      {
         service_.queueTask(new ATask(counter));
         service_.queueTask(new ATask(counter));
         service_.queueTask(new ATask(counter));
         counter.wait(30000);
      }
      assertEquals("expect a task is run 3 times", 3, 3 - counter.get());
      boolean b = service_.removeGlobaTriggerListener("GlobalTriggerListener");
      assertTrue("expect Global Trigger Listener is removed", b);
   }

   public void testListener() throws Exception
   {
      // ---------getAllGlobalJobListener
      List<JobListener> jobListenerCol = service_.getAllGlobalJobListener();
      assertEquals("expect 1 GlobalJobListener is found", 1, jobListenerCol.size());
      /* -----2 joblistener------------- */
      JobListener jcontext = service_.getGlobalJobListener("JobContextConfigListener");
      assertTrue("exepect found 'JobContextConfigListenner'", jcontext != null
         && jcontext.getName().equals("JobContextConfigListener"));
      hasObjectInCollection(jcontext, jobListenerCol, new JobListenerComparator());
      // --------------remove JobContextConfigListenner of SchedulerSerice-------
      boolean b = service_.removeGlobalJobListener(jcontext.getName());
      jcontext = service_.getGlobalJobListener("JobContextConfigListener");
      assertTrue("expect JobContextConfigListenner is removed", b && jcontext == null);
      jobListenerCol = service_.getAllGlobalJobListener();
      assertEquals("expect 0 job listenner is found", 0, jobListenerCol.size());

      // -----Test global trigger listener
      List<TriggerListener> triggerListenerCol = service_.getAllGlobalTriggerListener();
      assertEquals("expect no global trigger listener is found", 0, triggerListenerCol.size());
      /* ----------add TriggerListenner--- */
      service_.addGlobalTriggerListener(new GlobalTriggerListener());
      // ------getAllTriggerListener---
      TriggerListener globalTriggerListener = service_.getGlobalTriggerListener("GlobalTriggerListener");
      assertTrue("expect 'GlobalTriggerListener' is found", globalTriggerListener != null
         && globalTriggerListener.getName().equals("GlobalTriggerListener"));
      triggerListenerCol = service_.getAllGlobalTriggerListener();
      assertEquals("expect 1 trigger listenner is found", 1, triggerListenerCol.size());
      hasObjectInCollection(globalTriggerListener, triggerListenerCol, new TriggerListenerComparator());
      // ----------------remove GlobalTriggerListener
      b = service_.removeGlobaTriggerListener(globalTriggerListener.getName());
      assertTrue("expect GlobalTriggerListener is removed", b);
      triggerListenerCol = service_.getAllGlobalTriggerListener();
      assertEquals("expect no trigger listenner is found", 0, triggerListenerCol.size());

      // --------------Test non global Job Listener
      jobListenerCol = service_.getAllJobListener();
      assertEquals("expect no non global job listener is found", 0, jobListenerCol.size());
      // ----add 2 Non Global Job Listenner----
      service_.addGlobalJobListener(new GlobalJobListener());
      service_.addJobListener(new FirstJobListener());
      service_.addJobListener(new SecondJobListener());
      JobListener globalListener = service_.getGlobalJobListener("GlobalJobListener");
      JobListener joblistener1st = service_.getJobListener("FirstJobListener");
      JobListener joblistener2nd = service_.getJobListener("SecondJobListener");
      assertTrue("expect 'GlobalJobListener' is found", globalListener != null
               && globalListener.getName().equals("GlobalJobListener"));
      assertTrue("expect 'FirstJobListener' is found", joblistener1st != null
         && joblistener1st.getName().equals("FirstJobListener"));
      assertTrue("expect 'SecondJobListenner' is found", joblistener2nd != null
         && joblistener2nd.getName().equals("SecondJobListener"));
      jobListenerCol = service_.getAllJobListener();
      assertEquals("expect 1 global job listener is found", 1, service_.getAllGlobalJobListener().size());
      assertEquals("expect 2 non global job listener is found", 2, jobListenerCol.size());
      hasObjectInCollection(joblistener1st, jobListenerCol, new JobListenerComparator());
      hasObjectInCollection(joblistener2nd, jobListenerCol, new JobListenerComparator());
      
      GlobalJobListener.countCalled_ = 0;
      FirstJobListener.countCalled_ = 0;
      SecondJobListener.countCalled_ = 0;
      assertEquals(0, GlobalJobListener.countCalled_);
      assertEquals(0, FirstJobListener.countCalled_);
      assertEquals(0, SecondJobListener.countCalled_);
      
      service_.addJob(new JobInfo("GlobalJobListener", null/* default group */, AJob.class), new Date());
      service_.addJob(new JobInfo("FirstJobListener", null/* default group */, AJob.class), new Date());
      service_.addJob(new JobInfo("SecondJobListener", null/* default group */, AJob.class), new Date());
      Thread.sleep(500);
      
      //Scheduler can be faster and execute more than 3 tasks
      assertTrue(GlobalJobListener.countCalled_ >= 3);
      assertEquals(1, FirstJobListener.countCalled_);
      assertEquals(1, SecondJobListener.countCalled_);
      // ---remove FirstJobListenner---
      b = service_.removeJobListener(joblistener1st.getName());
      assertTrue("expect FirstJobListenner is removed", b);
      joblistener1st = service_.getJobListener("FirstJobListenner");
      assertTrue("expect FirstJobListenner is not found", joblistener1st == null);
      jobListenerCol = service_.getAllJobListener();
      assertEquals("now,expect 1 non global job is found", 1, jobListenerCol.size());
      // ---remove SecondJobListenner---
      b = service_.removeJobListener(joblistener2nd.getName());
      joblistener2nd = service_.getJobListener("SecondJobListener");
      assertTrue("expect SecondJobListenner is removed", b && joblistener2nd == null);
      jobListenerCol = service_.getAllJobListener();
      assertEquals("now,expect no non global job is found", 0, jobListenerCol.size());

      b = service_.removeGlobalJobListener(globalListener.getName());
      globalListener = service_.getGlobalJobListener("GlobalJobListener");
      assertTrue("expect GlobalJobListener is removed", b && globalListener == null);
      assertEquals("now we expect no global job listener", 0,  service_.getAllGlobalJobListener().size());
      
      Thread.sleep(2500);

      // -----Test non global Trigger Listenner ----
      triggerListenerCol = service_.getAllTriggerListener();
      assertEquals("expect no non global trigger listener is found", 0, triggerListenerCol.size());
      // ----------add 2 non global trigger listener---
      service_.addGlobalTriggerListener(new GlobalTriggerListener());
      service_.addTriggerListener(new FirstTriggerListener());
      service_.addTriggerListener(new SecondTriggerListener());
      TriggerListener triggerListener1st = service_.getTriggerListener("FirstTriggerListener");
      TriggerListener triggerListener2nd = service_.getTriggerListener("SecondTriggerListener");
      assertTrue("expect 'FirstTriggerListener' is found", triggerListener1st != null
         && triggerListener1st.getName().equals("FirstTriggerListener"));
      assertTrue("expect 'SecondTriggerListener' is found", triggerListener2nd != null
         && triggerListener2nd.getName().equals("SecondTriggerListener"));
      triggerListenerCol = service_.getAllTriggerListener();
      globalTriggerListener = service_.getGlobalTriggerListener("GlobalTriggerListener");
      assertTrue("expect 'GlobalTriggerListener' is found", globalTriggerListener != null
         && globalTriggerListener.getName().equals("GlobalTriggerListener"));
      assertEquals("expect 1 trigger listenner is found", 1, service_.getAllGlobalTriggerListener().size());     
      assertEquals("expect 2 non global trigger listener is found", 2, triggerListenerCol.size());
      hasObjectInCollection(triggerListener1st, triggerListenerCol, new TriggerListenerComparator());
      hasObjectInCollection(triggerListener2nd, triggerListenerCol, new TriggerListenerComparator());
      
      GlobalTriggerListener.countTriggerComplete_ = 0;
      FirstTriggerListener.countTriggerComplete_ = 0;
      SecondTriggerListener.countTriggerComplete_ = 0;
      assertEquals(0, GlobalTriggerListener.countTriggerComplete_);
      assertEquals(0, FirstTriggerListener.countTriggerComplete_);
      assertEquals(0, SecondTriggerListener.countTriggerComplete_);
      
      service_.addJob(new JobInfo("GlobalTriggerListener", null/* default group */, AJob.class), new Date());
      service_.addJob(new JobInfo("FirstTriggerListener", null/* default group */, AJob.class), new Date());
      service_.addJob(new JobInfo("SecondTriggerListener", null/* default group */, AJob.class), new Date());
      Thread.sleep(100);
      
      //Scheduler can be faster and execute more than 3 tasks
      assertTrue(GlobalTriggerListener.countTriggerComplete_ >= 3);
      assertEquals(1, FirstTriggerListener.countTriggerComplete_);
      assertEquals(1, SecondTriggerListener.countTriggerComplete_);
      
      // ----remove non global trigger listener----
      b = service_.removeTriggerListener(triggerListener1st.getName());
      triggerListener1st = service_.getTriggerListener("FirstTriggerListener");
      assertTrue("expect 'FirstTriggerListener' is removed", b && triggerListener1st == null);
      triggerListenerCol = service_.getAllTriggerListener();
      assertEquals("now, expect 1 non global trigger is found", 1, triggerListenerCol.size());
      b = service_.removeTriggerListener(triggerListener2nd.getName());
      // -----remove Second Trigger Listener----
      triggerListener2nd = service_.getTriggerListener("SecondTriggerListener");
      assertTrue("expect 'SecondTriggerListener' is removed", b && triggerListener2nd == null);
      triggerListenerCol = service_.getAllTriggerListener();
      assertEquals("now, expect no non global trigger is found", 0, triggerListenerCol.size());
      b = service_.removeGlobaTriggerListener(globalTriggerListener.getName());
      assertTrue("expect GlobalTriggerListener is removed", b);
      triggerListenerCol = service_.getAllGlobalTriggerListener();
      assertEquals("expect no trigger listenner is found", 0, triggerListenerCol.size());
   }
   
   public void testJobWithNonStartedServices() throws Exception
   {
      MyComponent component = (MyComponent)PortalContainer.getInstance().getComponentInstanceOfType(MyComponent.class);
      assertEquals(Boolean.TRUE, component.getResult());
   }
   
   public void testMultiplePortalContainers() throws Exception
   {
      ExoContainer oldContainer = ExoContainerContext.getCurrentContainerIfPresent();
      MyComponent component;
      PortalContainer container = null;
      MyComponent component2;
      PortalContainer container2 = null;
      String oldProfileList = System.getProperty(PropertyManager.RUNTIME_PROFILES);
      try
      {
         try
         {
            PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, "MultiplePortalContainers");
            component = (MyComponent)(container = RootContainer.getInstance().getPortalContainer("portal-container")).getComponentInstanceOfType(MyComponent.class);
         }
         finally
         {
            ExoContainerContext.setCurrentContainer(oldContainer);
         }
         try
         {
            PropertyManager.setProperty(PropertyManager.RUNTIME_PROFILES, "MultiplePortalContainers,portal-container2");
            component2 = (MyComponent)(container2 = RootContainer.getInstance().getPortalContainer("portal-container2")).getComponentInstanceOfType(MyComponent.class);
            Thread.sleep(2000);
         }
         finally
         {
            ExoContainerContext.setCurrentContainer(oldContainer);
         }
      }
      finally
      {
         if (oldProfileList == null)
         {
            System.clearProperty(PropertyManager.RUNTIME_PROFILES);
         }
         else
         {
            System.setProperty(PropertyManager.RUNTIME_PROFILES, oldProfileList);
         }
         PropertyManager.refresh();
      }
      assertEquals("myJob1", component.name);
      assertEquals(container, component.container);
      assertTrue(component.endRequest > 0);
      assertFalse(component.endRequestFailed);
      assertEquals("myJob2", component2.name);
      assertEquals(container2, component2.container);
      assertTrue(component2.endRequest > 0);
      assertFalse(component2.endRequestFailed);
   }
   
   public void testSeviceWithGlobalListener() throws Exception
   {
      assertTrue("JobScheduler is not deployed correctly", service_ != null);
      Thread.sleep(1000);
      Date startTime = new Date(System.currentTimeMillis() + 1000);
      Date endTime = null;
      JobInfo jinfo;

      // Test addJob(JobInfo, Date startTime) and GlobalTriggerListener()
      resetTestEnvironment();
      jinfo = new JobInfo("TestAddJob", null/* default group */, AJob.class, "false");
      AJob.expectExecuteTime_ = startTime;
      service_.addGlobalTriggerListener(new GlobalTriggerListener());
      service_.addJob(jinfo, startTime);
      Thread.sleep(1100);
      assertEquals("task has been run once", 1, AJob.counter_);
      // assertEquals("GlobalTriggerListener detect one task", 2,
      // GlobalTriggerListener.countTriggerFired_) ;
      assertTrue("The task is triggered  at the correct time  within 500ms margin", AJob.errorCounter_ == 0);

      /*
       * Test addJobPeriod(JobInfo jinfo,JobPeriodInfor pinfor) Job will fire
       * after 3 second, repeat forever, 2 second pause between repeat fire
       */
      resetTestEnvironment();
      jinfo = new JobInfo("TestJobPeriod", null/* default group */, AJob.class);
      startTime = new Date(System.currentTimeMillis() + 1000);
      PeriodInfo jpinfo = new PeriodInfo(startTime, null /* endTime */, 2/* repeatCount */, 1000/* period */);
      service_.addPeriodJob(jinfo, jpinfo);
      Thread.sleep(2100);
      assertEquals("task has been run exactly two times", 2, AJob.repeatCounter_);

      /*
       * Test addJobPeriod(JobInfo jinfo,JobPeriodInfor pinfor) Job will fire
       * after 1 second, stop after 2 second, 1 second pause between repeat fire
       */
      resetTestEnvironment();
      long currentTime = System.currentTimeMillis();
      jinfo = new JobInfo("TestJobPeriod", null/* default group */, AJob.class);
      startTime = new Date(currentTime + 1000);
      endTime = new Date(currentTime + 2100);
      jpinfo = new PeriodInfo(startTime, endTime, 0/* repeatCount */, 1000/* period */);
      service_.addPeriodJob(jinfo, jpinfo);
      Thread.sleep(2500);
      assertEquals("task has been run exactly two times", 2, AJob.repeatCounter_);

      /*
       * test test addPeriodJob(JobInfo jinfo,JobperiodInfo pinfo) Job will run
       * imediately, and exactly two times, 1 second pause between repeat run
       */
      resetTestEnvironment();
      jinfo = new JobInfo("ImediatelyPeriodJob", null/* default group */, AJob.class);
      jpinfo = new PeriodInfo(null/* startTime */, null/* endTime */, 2/* repeatCount */, 500/* period */);
      service_.addPeriodJob(jinfo, jpinfo);
      Thread.sleep(1100);
      assertEquals("task has been run exactly two times", 2, AJob.repeatCounter_);
      service_.removeJob(jinfo);
      /*
       * test addCronJob(JobInfor jinfo, String exp); Job will fire at 10: am
       * every day "0 15 10 ? * *"
       */
      resetTestEnvironment();
      String exp = "0 25 11 ? * *";
      jinfo = new JobInfo("TestCronJob", "cronGroup"/* default group */, AJob.class);
      service_.addCronJob(jinfo, exp);
      assertEquals("task has been run exactly one times at " + exp, 0, AJob.repeatCounter_);
      service_.removeJob(jinfo);

      /*
       * test test addPeriodJob(JobInfo jinfo,JobperiodInfo pinfo) Job will run
       * imediately, and forever, 1 second pause between repeat run
       */
      resetTestEnvironment();
      jinfo = new JobInfo("ForeverPeriodJob", null/* default group */, AJob.class);
      jpinfo = new PeriodInfo(null/* startTime */, null/* endTime */, 0/* repeatCount */, 500/* period */);
      service_.addPeriodJob(jinfo, jpinfo);
      Thread.sleep(1100);
      assertEquals("task has been run forever: ", 3, AJob.repeatCounter_);
      boolean b = service_.removeJob(jinfo);
      assertTrue("expect Job is removed", b);
      Thread.sleep(1000);
      assertEquals("task has been run forever: ", 3, AJob.repeatCounter_);
      b = service_.removeGlobaTriggerListener("GlobalTriggerListener");
      assertTrue("expect Global Trigger Listener is removed", b);
   }
   
   public void testGetAvailableJobs() throws Exception
   {
      resetTestEnvironment();
      Thread.sleep(100);
      int initSize = service_.getAllJobs().size();
      // some information about job execution
      Date firedTime = new Date(System.currentTimeMillis() + 1000000);
      service_.addJob(new JobInfo("queuejobTest", null/* default group */, AJob.class), firedTime);
      Thread.sleep(100);
      assertEquals("Expect one job in the queue", initSize + 1, service_.getAllJobs().size());
   }
   
   public static class MyContainerLifecyclePlugin extends BaseContainerLifecyclePlugin
   {

      @Override
      public void startContainer(ExoContainer container) throws Exception
      {
         MyComponent component = (MyComponent)container.getComponentInstanceOfType(MyComponent.class);
         component.doneSignal.await(2, TimeUnit.SECONDS);
         component.started = true;
      }
   }
   
   public static class MyComponent implements ComponentRequestLifecycle
   {
      public ExoContainer container;
      public String name;
      public boolean started;
      public final CountDownLatch doneSignal = new CountDownLatch(1);
      public Boolean result;
      public int endRequest;
      public boolean endRequestFailed;
      private ExoContainer startRequestContainer;

      public void doSomething()
      {
         if (started)
         {
            result = Boolean.TRUE;
         }
         else
         {
            result = Boolean.FALSE;
         }
         doneSignal.countDown();
      }
      
      public Boolean getResult() throws InterruptedException
      {
         doneSignal.await(2, TimeUnit.SECONDS);
         return result;
      }

      @Override
      public void startRequest(ExoContainer container)
      {
         this.startRequestContainer = container;
      }

      @Override
      public void endRequest(ExoContainer container)
      {
         if (container.equals(this.container) && this.container.equals(startRequestContainer))
            endRequest++;
         else if (this.container != null)
            endRequestFailed = true;
      }
   }
   
   public static class MyJobWithNonStartedServices implements Job
   {

      public void execute(JobExecutionContext context) throws JobExecutionException
      {
         MyComponent component = (MyComponent)PortalContainer.getInstance().getComponentInstanceOfType(MyComponent.class);
         component.doSomething();
      }
   }
   public static class MyJobMultiplePortalContainers implements Job
   {

      public void execute(JobExecutionContext context) throws JobExecutionException
      {
         MyComponent component = (MyComponent)PortalContainer.getInstance().getComponentInstanceOfType(MyComponent.class);
         component.name = context.getJobDetail().getKey().getName();
         component.container = PortalContainer.getInstance();
      }
   }
}
