package org.jboss.cache.cluster;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.RPCManager;
import org.jboss.cache.commands.ReplicableCommand;
import org.jboss.cache.commands.remote.ReplicateCommand;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.factories.CommandsFactory;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.factories.annotations.Start;
import org.jboss.cache.factories.annotations.Stop;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Periodically (or when certain size is exceeded) takes elements and replicates them.
 *
 * @author <a href="mailto:bela@jboss.org">Bela Ban</a> May 24, 2003
 * @version $Revision: 5197 $
 */
public class ReplicationQueue
{

   private static final Log log = LogFactory.getLog(ReplicationQueue.class);

   /**
    * We flush every 5 seconds. Inactive if -1 or 0
    */
   private long interval = 5000;

   /**
    * Max elements before we flush
    */
   private long max_elements = 500;

   /**
    * Holds the replication jobs: LinkedList<MethodCall>
    */
   final List<ReplicableCommand> elements = new LinkedList<ReplicableCommand>();

   /**
    * For periodical replication
    */
   private Timer timer = null;

   /**
    * The timer task, only calls flush() when executed by Timer
    */
   private ReplicationQueue.MyTask task = null;
   private RPCManager rpcManager;
   private Configuration configuration;
   private boolean enabled;
   private CommandsFactory commandsFactory;


   public boolean isEnabled()
   {
      return enabled;
   }

   @Inject
   private void injectDependencies(RPCManager rpcManager, Configuration configuration, CommandsFactory commandsFactory)
   {
      this.rpcManager = rpcManager;
      this.configuration = configuration;
      this.commandsFactory = commandsFactory;

      // this is checked again in Start
      enabled = configuration.isUseReplQueue() && (configuration.getBuddyReplicationConfig() == null || !configuration.getBuddyReplicationConfig().isEnabled());
   }

   /**
    * Starts the asynchronous flush queue.
    */
   @Start
   public synchronized void start()
   {
      this.interval = configuration.getReplQueueInterval();
      this.max_elements = configuration.getReplQueueMaxElements();
      // check again
      enabled = configuration.isUseReplQueue() && (configuration.getBuddyReplicationConfig() == null || !configuration.getBuddyReplicationConfig().isEnabled());
      if (enabled && interval > 0)
      {
         if (task == null)
            task = new ReplicationQueue.MyTask();
         if (timer == null)
         {
            timer = new Timer(true);
            timer.schedule(task,
                  500, // delay before initial flush
                  interval); // interval between flushes
         }
      }
   }

   /**
    * Stops the asynchronous flush queue.
    */
   @Stop
   public synchronized void stop()
   {
      if (task != null)
      {
         task.cancel();
         task = null;
      }
      if (timer != null)
      {
         timer.cancel();
         timer = null;
      }
   }


   /**
    * Adds a new method call.
    */
   public void add(ReplicateCommand job)
   {
      if (job == null)
         throw new NullPointerException("job is null");
      synchronized (elements)
      {
         elements.add(job);
         if (elements.size() >= max_elements)
            flush();
      }
   }

   /**
    * Flushes existing method calls.
    */
   public void flush()
   {
      List<ReplicableCommand> toReplicate;
      synchronized (elements)
      {
         if (log.isTraceEnabled())
            log.trace("flush(): flushing repl queue (num elements=" + elements.size() + ")");
         toReplicate = new ArrayList<ReplicableCommand>(elements);
         elements.clear();
      }

      if (toReplicate.size() > 0)
      {
         try
         {

            ReplicateCommand replicateCommand = commandsFactory.buildReplicateCommand(toReplicate);
            // send to all live nodes in the cluster
            rpcManager.callRemoteMethods(null, replicateCommand, false, configuration.getSyncReplTimeout(), false);
         }
         catch (Throwable t)
         {
            log.error("failed replicating " + toReplicate.size() + " elements in replication queue", t);
         }
      }
   }

   class MyTask extends TimerTask
   {
      @Override
      public void run()
      {
         flush();
      }
   }
}