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

package org.jboss.cache.pojo.impl;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.aop.Advised;
import org.jboss.aop.ClassInstanceAdvisor;
import org.jboss.aop.InstanceAdvisor;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.pojo.interceptors.dynamic.CacheFieldInterceptor;
import org.jboss.cache.pojo.memory.FieldPersistentReference;
import org.jboss.cache.pojo.util.AopUtil;
import org.jboss.cache.pojo.util.Instantiator;

/**
 * Handling the advised pojo operations. No consideration of object graph here.
 *
 * @author Ben Wang
 *         Date: Aug 4, 2005
 * @version $Id: AdvisedPojoHandler.java 4495 2007-09-21 00:20:00Z jason.greene@jboss.com $
 */
class AdvisedPojoHandler
{
   private final Log log = LogFactory.getLog(AdvisedPojoHandler.class);
   private Cache<Object, Object> cache_;
   private PojoCacheImpl pCache_;
   private PojoUtil util_;

   public AdvisedPojoHandler(PojoCacheImpl pCache, InternalHelper internal,
                             PojoUtil util)
   {
      pCache_ = pCache;
      cache_ = pCache_.getCache();
      util_ = util;
   }

   public Object get(Fqn<?> fqn, Class<?> clazz, PojoInstance pojoInstance)
         throws CacheException
   {
      CachedType type = pCache_.getCachedType(clazz);
      Object obj =Instantiator.newInstance(clazz);

      // TODO Need to populate the object from the cache as well.
      // Insert interceptor at runtime
      InstanceAdvisor advisor = ((Advised) obj)._getInstanceAdvisor();
      CacheFieldInterceptor interceptor = new CacheFieldInterceptor(pCache_, fqn, type);
      interceptor.setAopInstance(pojoInstance);
      util_.attachInterceptor(obj, advisor, interceptor);
      return obj;
   }

   void put(Fqn<?> fqn, Fqn<?> referencingFqn, Object obj) throws CacheException
   {
      CachedType type = pCache_.getCachedType(obj.getClass());
      // We have a clean slate then.
      InstanceAdvisor advisor = ((Advised) obj)._getInstanceAdvisor();
      // TODO workaround for deserialiased objects
      if (advisor == null)
      {
         advisor = new ClassInstanceAdvisor(obj);
         ((Advised) obj)._setInstanceAdvisor(advisor);
      }

      // Let's do batch update via Map instead
      Map map = new HashMap();
      // Always initialize the ref count so we can mark this as an AopNode.
      PojoInstance pojoInstance = InternalHelper.initializeAopInstance(referencingFqn);
      map.put(PojoInstance.KEY, pojoInstance);
      pojoInstance.setPojoClass(type.getType());
      // we will do it recursively.
      // Map of sub-objects that are non-primitive
      Map subPojoMap = new HashMap();

      for (Iterator i = type.getFields().iterator(); i.hasNext();)
      {
         Field field = (Field) (((FieldPersistentReference) i.next())).get();
         Object value = null;
         try
         {
            value = field.get(obj);
         }
         catch (IllegalAccessException e)
         {
            throw new CacheException("field access failed", e);
         }

         // we simply treat field that has @Serializable as a primitive type.
         if (CachedType.isSimpleAttribute(field))
         {
            // switched using batch update
            map.put(field.getName(), value);
         }
         else
         {
            subPojoMap.put(field, value);
         }
      }

      // Use option to skip locking since we have parent lock already.
//      cache_.getInvocationContext().getOptionOverrides().setSuppressLocking(true);

      cache_.getRoot().addChild(fqn).putAll(map);

      // Insert interceptor after PojoInstance has been written to the cache
      // This prevents JBCACHE-1078 with pessimistic locking, optimistic is still a problem
      CacheFieldInterceptor interceptor = new CacheFieldInterceptor(pCache_, fqn, type);
      interceptor.setAopInstance(pojoInstance);
      util_.attachInterceptor(obj, advisor, interceptor);

//      cache_.getInvocationContext().getOptionOverrides().setSuppressLocking(false);
      // This is in-memory operation only
      InternalHelper.setPojo(pojoInstance, obj);

      for (Object o : subPojoMap.keySet())
      {
         Field field = (Field) o;
         Object value = subPojoMap.get(field);
         if (value == null) continue; // really no need to map the POJO.
         pCache_.attach(fqn, value, field.getName());
         // If it is Collection classes, we replace it with dynamic proxy.
         // But we will have to ignore it if value is null
         if (value instanceof Map || value instanceof List || value instanceof Set)
         {
            Object newValue = pCache_.getObject(fqn, field.getName());
            util_.inMemorySubstitution(obj, field, newValue);
         }
      }

      // Need to make sure this is behind put such that obj.toString is done correctly.
      if (log.isDebugEnabled())
      {
         log.debug("internalPut(): inserting with fqn: " + fqn);
      }
   }

   Object remove(Fqn<?> fqn, Object result, Class<?> clazz) throws CacheException
   {
      CachedType type = pCache_.getCachedType(clazz);
      InstanceAdvisor advisor = ((Advised) result)._getInstanceAdvisor();
      for (Iterator i = type.getFields().iterator(); i.hasNext();)
      {
         Field field = (Field) (((FieldPersistentReference) i.next())).get();
         Object value = null;

         if (! CachedType.isSimpleAttribute(field))
         {
            value = pCache_.detach(fqn, field.getName());

            // Check for Collection. If it is, we need to reset the original reference.
            if ((value instanceof Map || value instanceof List || value instanceof Set))
            {
               // If this Collection class, we are returning the original value already
               util_.inMemorySubstitution(result, field, value);
            }
         }
         else
         {
            // Update last known field state
            value = cache_.get(fqn,  field.getName());
            util_.inMemorySubstitution(result, field, value);
         }
      }

      // batch remove
      cache_.getRoot().getChild(fqn).clearData();
      // Determine if we want to keep the interceptor for later use.
      CacheFieldInterceptor interceptor = (CacheFieldInterceptor) AopUtil.findCacheInterceptor(advisor);
      // Remember to remove the interceptor from in-memory object but make sure it belongs to me first.
      if (interceptor != null)
      {
         if (log.isDebugEnabled())
         {
            log.debug("regularRemoveObject(): removed cache interceptor fqn: " + fqn + " interceptor: " + interceptor);
         }
         util_.detachInterceptor(advisor, interceptor);
      }

      return null; // Not really null though.
   }
}
