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

package org.jboss.cache.pojo.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.aop.Advised;
import org.jboss.aop.InstanceAdvisor;
import org.jboss.aop.advice.Interceptor;
import org.jboss.aop.proxy.ClassProxy;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.pojo.PojoCacheException;
import org.jboss.cache.pojo.collection.CollectionInterceptorUtil;
import org.jboss.cache.pojo.interceptors.dynamic.BaseInterceptor;
import org.jboss.cache.pojo.util.AopUtil;

/**
 * Handle the object graph management.
 *
 * @author Ben Wang
 *         Date: Aug 4, 2005
 * @version $Id: ObjectGraphHandler.java 4081 2007-06-28 00:56:07Z jgreene $
 */
class ObjectGraphHandler
{
   private PojoCacheImpl cache;
   private InternalHelper internal_;
   private final static Log log = LogFactory.getLog(ObjectGraphHandler.class);

   public ObjectGraphHandler(PojoCacheImpl cache, InternalHelper internal)
   {
      this.cache = cache;
      internal_ = internal;
   }

   Object get(Fqn fqn, Class clazz, PojoInstance pojoInstance) throws CacheException
   {
      // Note this is actually the aliasFqn, not the real fqn!
      Object obj;

      obj = cache.getObject(fqn);
      if (obj == null)
         throw new PojoCacheException("ObjectGraphHandler.get(): null object from internal ref node." +
                                      " Internal ref node: " + fqn);

      return obj; // No need to set the instance under fqn. It is located in refFqn anyway.
   }

   void put(Fqn fqn, Object obj, String field) throws CacheException
   {
      CachedType type = cache.getCachedType(obj.getClass());

      InstanceAdvisor advisor = null;
      Interceptor interceptor = null;

      if (obj instanceof Advised)
      {
         advisor = ((Advised) obj)._getInstanceAdvisor();
         if (advisor == null)
            throw new PojoCacheException("put(): InstanceAdvisor is null for: " + obj);
         // Step Check for cross references
         interceptor = AopUtil.findCacheInterceptor(advisor);
      }
      else
      {
         advisor = ((ClassProxy) obj)._getInstanceAdvisor();
         if (advisor == null)
            throw new PojoCacheException("put(): InstanceAdvisor is null for: " + obj);
         interceptor = CollectionInterceptorUtil.getInterceptor((ClassProxy) obj);
      }

      Fqn originalFqn = null;

      // ah, found something. So this will be multiple referenced.
      originalFqn = ((BaseInterceptor) interceptor).getFqn();

      // This will increment the ref count, reset, and add ref fqn in the current fqn node.
      setupRefCounting(fqn, originalFqn);
      // Store a PojoReference in the external fqn node
      PojoReference pojoReference = new PojoReference();
      pojoReference.setFqn(originalFqn);
      pojoReference.setPojoClass(type.getType());
      internal_.putPojoReference(fqn, pojoReference, field);
   }

   boolean isMultipleReferenced(Fqn internalFqn)
   {
      // Note this is actually the aliasFqn, not the real fqn!
      PojoInstance pojoInstance = null;
      try
      {
         pojoInstance = internal_.getPojoInstance(internalFqn);
      }
      catch (CacheException e)
      {
         throw new PojoCacheException("Exception in isMultipleReferenced", e);
      }
      // check if this is a refernce
      return InternalHelper.isMultipleReferenced(pojoInstance);

   }

   void remove(Fqn referencingFqn, Fqn internalFqn, Object pojo)
         throws CacheException
   {
      if (log.isDebugEnabled())
      {
         log.debug("remove(): removing object fqn: " + referencingFqn
                   + " Will just de-reference it.");
      }
      removeFromReference(referencingFqn, internalFqn);
   }

   /**
    * Remove the object from the the reference fqn, meaning just decrement the ref counter.
    */
   private void removeFromReference(Fqn referencingFqn, Fqn originalFqn) throws CacheException
   {
      synchronized (referencingFqn)
      {  // we lock the internal fqn here so no one else has access.
         // Decrement ref counting on the internal node
         if (decrementRefCount(referencingFqn, originalFqn) == PojoInstance.INITIAL_COUNTER_VALUE)
         {
            // No one is referring it so it is safe to remove
            // TODO we should make sure the parent nodes are also removed they are empty as well.
            cache.detach(referencingFqn);
         }
      }
   }

   /**
    * 1. increment reference counter
    * 2. put in refFqn so we can get it.
    *
    * @param fqn    The original fqn node
    * @param refFqn The new internal fqn node
    */
   private void setupRefCounting(Fqn fqn, Fqn refFqn) throws CacheException
   {
      synchronized (refFqn)
      { // we lock the ref fqn here so no one else has access.
         // increment the reference counting
         incrementRefCount(refFqn, fqn);
         // set the internal fqn in fqn so we can reference it.
         if (log.isTraceEnabled())
         {
            log.trace("setupRefCounting(): current fqn: " + fqn + " set to point to: " + refFqn);
         }
      }
   }

   private int incrementRefCount(Fqn originalFqn, Fqn referencingFqn) throws CacheException
   {
      return internal_.incrementRefCount(originalFqn, referencingFqn);
   }

   private int decrementRefCount(Fqn referencingFqn, Fqn originalFqn) throws CacheException
   {
      int count = 0;
      if ((count = internal_.decrementRefCount(originalFqn, referencingFqn)) == (PojoInstance.INITIAL_COUNTER_VALUE + 1))
      {
         internal_.removeIndirectFqn(originalFqn.toString());
      }

      return count;
   }
}
