package org.jboss.seam.async;

import java.lang.annotation.Annotation;
import java.util.Date;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.async.Duration;
import org.jboss.seam.annotations.async.Expiration;
import org.jboss.seam.annotations.async.FinalExpiration;
import org.jboss.seam.annotations.async.IntervalCron;
import org.jboss.seam.annotations.async.IntervalDuration;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.transaction.Transaction;

/**
 * Abstract Dispatcher implementation
 * 
 * @author Gavin King
 *
 */
public abstract class AbstractDispatcher<T, S> implements Dispatcher<T, S>
{
   
   public class DispatcherParameters
   {
      private Date expiration;
      private Date finalExpiration;
      private Long duration;
      private Long intervalDuration;
      private String intervalCron;
      
      public String getIntervalCron()
      {
         return intervalCron;
      }
      public Long getDuration()
      {
         return duration;
      }
      public Date getExpiration()
      {
         return expiration;
      }
      public Date getFinalExpiration()
      {
         return finalExpiration;
      }
      public Long getIntervalDuration()
      {
         return intervalDuration;
      }
      public void setIntervalCron(String cron)
      {
         this.intervalCron = cron;
      }
      public void setDuration(Long duration)
      {
         this.duration = duration;
      }
      public void setExpiration(Date expiration)
      {
         this.expiration = expiration;
      }
      public void setFinalExpiration(Date finalExpiration)
      {
         this.finalExpiration = finalExpiration;
      }
      public void setIntervalDuration(Long intervalDuration)
      {
         this.intervalDuration = intervalDuration;
      }
      
      
   }
   
   public static final String EXECUTING_ASYNCHRONOUS_CALL = "org.jboss.seam.core.executingAsynchronousCall";
      
   public static Dispatcher instance()
   {
      if ( !Contexts.isApplicationContextActive() )
      {
         throw new IllegalStateException("no application context active");
      }
      return (Dispatcher) Component.getInstance("org.jboss.seam.async.dispatcher");         
   }
   
   public void scheduleTransactionSuccessEvent(String type, Object... parameters)
   {
      Transaction.instance().registerSynchronization( new TransactionSuccessEvent(type, parameters) );
   }

   public void scheduleTransactionCompletionEvent(String type, Object... parameters)
   {
      Transaction.instance().registerSynchronization( new TransactionCompletionEvent(type, parameters) );
   }

   protected Schedule createSchedule(InvocationContext invocation)
   {
      DispatcherParameters dispatcherParameters = extractAndValidateParameters(invocation);
      if (dispatcherParameters.getIntervalCron() == null)
      {
         return new TimerSchedule(dispatcherParameters.getDuration(), dispatcherParameters.getExpiration(), dispatcherParameters.getIntervalDuration(), dispatcherParameters.getFinalExpiration());
      }
      else
      {
         return new CronSchedule(dispatcherParameters.getDuration(), dispatcherParameters.getExpiration(), dispatcherParameters.getIntervalCron(), dispatcherParameters.getFinalExpiration());
      }
   }
   
   protected TimerSchedule createTimerSchedule(InvocationContext invocation)
   {
      DispatcherParameters dispatcherParameters = extractAndValidateParameters(invocation);
      return createTimerSchedule(dispatcherParameters);
   }
   
   private TimerSchedule createTimerSchedule(DispatcherParameters dispatcherParameters)
   {
      return new TimerSchedule(dispatcherParameters.getDuration(), dispatcherParameters.getExpiration(), dispatcherParameters.getIntervalDuration(), dispatcherParameters.getFinalExpiration());
   }
   
   protected DispatcherParameters extractAndValidateParameters(InvocationContext invocation)
   {
      DispatcherParameters dispatcherParameters = new DispatcherParameters();
      for ( int i=0; i < invocation.getMethod().getParameterAnnotations().length; i++ )
      {
         Annotation[] annotations = invocation.getMethod().getParameterAnnotations()[i];
         for (Annotation annotation: annotations)
         {
            if ( annotation.annotationType().equals(Duration.class) )
            {
               if (invocation.getParameters()[i] instanceof Long)
               {
                  dispatcherParameters.setDuration((Long) invocation.getParameters()[i]);
               }
               else if (invocation.getParameters()[i] != null)
               {
                  throw new IllegalArgumentException("@Duration on " + invocation.getTarget().getClass() + ":" + invocation.getMethod().getName() + " must be a Long");
               }
            }
            else if ( annotation.annotationType().equals(Expiration.class) )
            {
               if (invocation.getParameters()[i] instanceof Date)
               {
                  dispatcherParameters.setExpiration((Date) invocation.getParameters()[i]);
               }
               else if (invocation.getParameters()[i] != null)
               {
                  throw new IllegalArgumentException("@Expiration on " + invocation.getTarget().getClass() + ":" + invocation.getMethod().getName() + " must be a java.util.Date");
               }
            }
            else if ( annotation.annotationType().equals(FinalExpiration.class) )
            {
               if (!( this instanceof QuartzDispatcher ))
               {
                  throw new IllegalArgumentException("Can only use @FinalExpiration with the QuartzDispatcher");
               }
               else if (invocation.getParameters()[i] instanceof Date)
               {
                  dispatcherParameters.setFinalExpiration((Date) invocation.getParameters()[i]);
               }
               else if (invocation.getParameters()[i] != null)
               {
                  throw new IllegalArgumentException("@FinalExpiration on " + invocation.getTarget().getClass() + ":" + invocation.getMethod().getName() + " must be a java.util.Date");
               }
            }
            else if ( annotation.annotationType().equals(IntervalCron.class) )
            {
               if (!( this instanceof QuartzDispatcher ))
               {
                  throw new IllegalArgumentException("Can only use @IntervalCron with the QuartzDispatcher");
               }
               else if (invocation.getParameters()[i] instanceof String)
               {
                  dispatcherParameters.setIntervalCron((String) invocation.getParameters()[i]);
               }
               else if (invocation.getParameters()[i] != null)
               {
                  throw new IllegalArgumentException("@IntervalCron on " + invocation.getTarget().getClass() + ":" + invocation.getMethod().getName() + " must be a String");
               }
            }
            else if ( annotation.annotationType().equals(IntervalDuration.class) )
            {
               if (invocation.getParameters()[i] instanceof Long)
               {
                  dispatcherParameters.setIntervalDuration((Long) invocation.getParameters()[i]);
               }
               else if (invocation.getParameters()[i] != null)
               {
                  throw new IllegalArgumentException("@IntervalDuration on " + invocation.getTarget().getClass() + ":" + invocation.getMethod().getName() + " must be a Long");
               }
            }
         }
      }

      if ( dispatcherParameters.getIntervalCron() != null && dispatcherParameters.getIntervalDuration() != null )
      {
         throw new IllegalArgumentException("Can only use one of @IntervalCron and @IntervalDuration");
      }
      
      return dispatcherParameters;
   }
   
}
