/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.aop;

import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.logging.Logger;
import org.jboss.util.NestedRuntimeException;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Set interceptor to perform Set cache storage and retrieval
 *
 * @author <a href="mailto:harald@gliebe.de">Harald Gliebe</a>
 * @author Ben Wang
 */
public class CachedSetInterceptor implements Set, CachedCollectionInterceptor
{

   protected static final Logger log = Logger.getLogger(CachedSetInterceptor.class);

   protected TreeCacheAop cache;
   protected Fqn fqn;
   protected Map methodMap;

   protected static Map managedMethods =
         CollectionInterceptorUtil.getManagedMethods(Set.class);

   public CachedSetInterceptor(TreeCacheAop cache, Fqn fqn, Class clazz)
   {
      this.cache = cache;
      this.fqn = fqn;
      methodMap = CollectionInterceptorUtil.getMethodMap(clazz);
   }

   public String getName()
   {
      return "CachedSetInterceptor";
   }

   public Object invoke(org.jboss.aop.joinpoint.Invocation invocation) throws Throwable
   {
      return CollectionInterceptorUtil.invoke(invocation,
            this,
            methodMap,
            managedMethods);
   }

   protected Node getNode()
   {
      try {
         return cache.get(fqn);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   // implementation of the java.util.Set interface
   public int size()
   {
      Node node = getNode();
      if(node == null) {
         return 0;
      }

      Map children = node.getChildren();
      return children == null ? 0 : children.size();
   }

   public void clear()
   {
      for(Iterator i = iterator(); i.hasNext(); ) {
         remove(i.next());
      }
   }

   public boolean isEmpty()
   {
      return size() == 0 ? true: false;
   }

   public Object[] toArray()
   {
      Object[] objs = new Object[size()];

      int ind = 0;
      for(Iterator i = iterator(); i.hasNext(); ) {
         objs[ind++] = i.next();
      }
      return objs;
   }

   public Iterator iterator()
   {
      // TODO: check for concurrent modification
      return new Iterator()
      {
         protected int current = 0;

         public boolean hasNext()
         {
            return current < size();
         }

         public Object next()
         {
            try {
               return cache.getObject(new Fqn(fqn, new Integer(current++)));
            } catch (Exception e) {
               throw new NestedRuntimeException(e);
            }
         }

         public void remove()
         {
            // TODO Need optimization here since set does not care about index
            try {
               int size = size();
               if (current < size) {
                  // replace Object with last one
                  // Object last = cache.removeObject(((Fqn) fqn.clone()).add(new Integer(size - 1)));
                  Object last = cache.removeObject(new Fqn(fqn, new Integer(size - 1)));
                  // cache.putObject(((Fqn) fqn.clone()).add(new Integer(--current)), last);
                  cache.putObject(new Fqn(fqn, new Integer(--current)), last);
               } else {
                  // cache.removeObject(((Fqn) fqn.clone()).add(new Integer(--current)));
                  cache.removeObject(new Fqn(fqn, new Integer(--current)));
               }
            } catch (Exception e) {
               throw new NestedRuntimeException(e);
            }
         }
      };
   }

   public Object[] toArray(Object a[])
   {
      throw new AopOperationNotSupportedException("CachedSetInterceptor: set.toArray() operation not supported");
   }

   public boolean add(Object o)
   {
      if( contains(o) ) return false;

      try {
         // cache.putObject(((Fqn) fqn.clone()).add(new Integer(size())), o);
         cache.putObject(new Fqn(fqn, new Integer(size())), o);
         return true;
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public boolean contains(Object o)
   {
      for (Iterator i = iterator(); i.hasNext();) {
         Object n = i.next();
         // TODO logic correct????
         if ((o == null && n == null) || (o != null && o.equals(n))) {
            return true;
         }
      }
      return false;
   }

   public boolean remove(Object o)
   {
      for (Iterator i = iterator(); i.hasNext();) {
         Object n = i.next();
         if ((o == null && n == null) || (o != null && o.equals(n))) {
            i.remove();
            return true;
         }
      }
      return false;
   }

   public boolean addAll(Collection c)
   {
      Iterator it = c.iterator();
      while(it.hasNext()) {
         add(it.next());
      }

      return true;
   }

   public boolean containsAll(Collection c)
   {
      throw new AopOperationNotSupportedException("CachedSetInterceptor: set.containsAll() operation not supported");
   }

   public boolean removeAll(Collection c)
   {
      Iterator it = c.iterator();
      while(it.hasNext()) {
         remove(it.next());
      }

      return true;
   }

   public boolean retainAll(Collection c)
   {
      throw new AopOperationNotSupportedException("CachedSetInterceptor: set.retainAll() operation not supported");
   }


   public int hashCode()
   {
      int result = 0;
      for (Iterator i = iterator(); i.hasNext();) {
         result += i.next().hashCode();
      }
      return result;
   }

   public boolean equals(Object object)
   {
      if (object == this)
         return true;
      if (!(object instanceof Set))
         return false;
      Set list = (Set) object;
      if (size() != list.size())
         return false;
      for (Iterator i = iterator(); i.hasNext();) {
         Object value = i.next();
         if (value != null) {
            if( !contains(value) )
               return false;
         } else { // Null key is not allowed
            return false;
         }
      }
      return true;
   }

   public String toString() {
      StringBuffer buf = new StringBuffer();
      for(Iterator it = iterator(); it.hasNext();) {
         Object key = it.next();
         buf.append("[" +key).append("]");
         if(it.hasNext()) buf.append(", ");
      }

      return buf.toString();
   }

}