/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.EvictionPolicyConfig;
import org.jboss.cache.config.EvictionRegionConfig;
import org.jboss.cache.eviction.EvictedEventNode;
import org.jboss.cache.eviction.EvictionPolicy;
import org.jboss.cache.eviction.NodeEventType;
import org.jboss.cache.util.Util;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Default implementation of a {@link Region}
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class RegionImpl implements Region
{
   private static final Log log = LogFactory.getLog(RegionImpl.class);

   private final RegionManager regionManager;
   private Fqn fqn;
   private Status status;
   private ClassLoader classLoader;
   private BlockingQueue<EvictedEventNode> nodeEventQueue = null;
   private int capacityWarnThreshold = 0;
   private EvictionRegionConfig configuration = new EvictionRegionConfig();
   private EvictionPolicy policy;

   /**
    * Constructs a marshalling region from an fqn and region manager.
    */
   public RegionImpl(Fqn fqn, RegionManager regionManager)
   {
      this.fqn = fqn;
      this.regionManager = regionManager;
      status = !regionManager.isDefaultInactive() ? Status.ACTIVE : Status.INACTIVE;
   }

   /**
    * Constructs an eviction region from a policy and configuration, defined by an fqn and region manager.
    */
   public RegionImpl(EvictionPolicy policy, EvictionRegionConfig config, Fqn fqn, RegionManager regionManager)
   {
      this(fqn, regionManager);
      this.configuration = config;
      this.policy = policy;
      createQueue();
   }

   public Configuration getCacheConfiguration()
   {
      if (regionManager != null && regionManager.getCache() != null)
         return regionManager.getCache().getConfiguration();
      else
         return null;
   }

   public void registerContextClassLoader(ClassLoader classLoader)
   {
      this.classLoader = classLoader;
   }

   public void unregisterContextClassLoader()
   {
      this.classLoader = null;
   }

   public void activate()
   {
      regionManager.activate(fqn);
      status = Status.ACTIVE;
   }

   public void activateIfEmpty()
   {
      regionManager.activateIfEmpty(fqn);
      status = Status.ACTIVE;
   }

   public void deactivate()
   {
      regionManager.deactivate(fqn);
      status = Status.INACTIVE;
   }

   public boolean isActive()
   {
      return status == Status.ACTIVE;
   }

   public ClassLoader getClassLoader()
   {
      return classLoader;
   }

   public Fqn getFqn()
   {
      return fqn;
   }

   public void setStatus(Status status)
   {
      this.status = status;
   }

   public Status getStatus()
   {
      return status;
   }

   public void setActive(boolean b)
   {
      status = b ? Status.ACTIVE : Status.INACTIVE;
   }

   // -------- eviction stuff -----

   public void markNodeCurrentlyInUse(Fqn fqn, long timeout)
   {
      EvictedEventNode markUse = new EvictedEventNode(fqn, NodeEventType.MARK_IN_USE_EVENT);
      markUse.setInUseTimeout(timeout);
      putNodeEvent(markUse);
   }

   public void unmarkNodeCurrentlyInUse(Fqn fqn)
   {
      EvictedEventNode markNoUse = new EvictedEventNode(fqn, NodeEventType.UNMARK_USE_EVENT);
      putNodeEvent(markNoUse);
   }

   @Override
   public String toString()
   {
      return "RegionImpl{" +
            "fqn=" + fqn +
            "; classloader=" + classLoader +
            "; status=" + status +
            "; eviction=" + (getEvictionPolicy() != null) +
            "; timerThreadRegistered=" + (getEvictionPolicy() != null && regionManager.getEvictionTimerTask().isRegionRegisteredForProcessing(this)) +
            '}';
   }

   public int compareTo(Region other)
   {
      return getFqn().compareTo(other.getFqn());
   }

   @Override
   public boolean equals(Object o)
   {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      RegionImpl region = (RegionImpl) o;

      if (fqn != null ? !fqn.equals(region.fqn) : region.fqn != null) return false;

      return true;
   }

   @Override
   public int hashCode()
   {
      return (fqn != null ? fqn.hashCode() : 0);
   }

   public void putNodeEvent(EvictedEventNode event)
   {
      try
      {
         if (nodeEventQueue == null) createQueue();// in case the queue does not exist yet.
         if (nodeEventQueue.size() > capacityWarnThreshold)
         {
            log.warn("putNodeEvent(): eviction node event queue size is at 98% threshold value of capacity: " + configuration.getEventQueueSize() +
                  " Region: " + fqn +
                  " You will need to reduce the wakeUpIntervalSeconds parameter.");
         }
         nodeEventQueue.put(event);
      }
      catch (InterruptedException e)
      {
         log.debug("give up put", e);
      }
   }

   public EvictedEventNode takeLastEventNode()
   {
      try
      {
         return nodeEventQueue.poll(0, TimeUnit.SECONDS);
      }
      catch (InterruptedException e)
      {
         log.debug("trace", e);
      }
      return null;
   }

   public int nodeEventQueueSize()
   {
      return nodeEventQueue.size();
   }

   public void resetEvictionQueues()
   {
      nodeEventQueue.clear();
   }

   private synchronized void createQueue()
   {
      if (nodeEventQueue == null)
      {
         if (configuration == null)
         {
            throw new IllegalArgumentException("null eviction configuration");
         }
         int size = configuration.getEventQueueSize();
         capacityWarnThreshold = (98 * size) / 100 - 100;
         if (capacityWarnThreshold <= 0)
         {
            throw new RuntimeException("Capacity warn threshold used in eviction is smaller than 1.");
         }
         nodeEventQueue = new LinkedBlockingQueue<EvictedEventNode>(size);
      }
   }

   public EvictionRegionConfig getEvictionRegionConfig()
   {
      return this.configuration;
   }

   public EvictionPolicyConfig getEvictionPolicyConfig()
   {
      return configuration == null ? null : configuration.getEvictionPolicyConfig();
   }

   public EvictionPolicy getEvictionPolicy()
   {
      return policy;
   }

   public void setEvictionPolicy(EvictionPolicyConfig evictionPolicyConfig)
   {
      configuration.setEvictionPolicyConfig(evictionPolicyConfig);
      policy = createPolicy(evictionPolicyConfig.getEvictionPolicyClass());
      regionManager.getEvictionTimerTask().addRegionToProcess(this);
      if (nodeEventQueue == null) createQueue();
   }

   private EvictionPolicy createPolicy(String className)
   {
      if (className == null)
      {
         throw new IllegalArgumentException("null className");
      }
      try
      {
         if (log.isTraceEnabled()) log.trace("Instantiating " + className);
         EvictionPolicy ep = (EvictionPolicy) Util.loadClass(className).newInstance();
         ep.setCache(regionManager.getCache());
         return ep;
      }
      catch (Exception e)
      {
         log.fatal("Unable to instantiate eviction policy class " + className, e);
         return null;
      }
   }

   public RegionImpl clone(Fqn newRoot)
   {
      RegionImpl clone = null;
      try
      {
         clone = (RegionImpl) super.clone();
         clone.policy = policy;
         clone.configuration = configuration;
         clone.status = status;
         clone.fqn = Fqn.fromRelativeFqn(newRoot, fqn);
         // we also need to copy all of the eviction event nodes to the clone's queue
         clone.createQueue();
         for (EvictedEventNode een : this.nodeEventQueue)
         {
            EvictedEventNode cloneEEN = een.clone(newRoot);
            clone.putNodeEvent(cloneEEN);
         }
      }
      catch (CloneNotSupportedException e)
      {
         // problems cloning?  Should never get here.
      }
      return clone;
   }
}
