/*
 * 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.*;

/**
 * List interceptor to perform list cache storage and retrieval
 *
 * @author <a href="mailto:harald@gliebe.de">Harald Gliebe</a>
 * @author Ben Wang
 */

public class CachedListInterceptor
      implements List, CachedCollectionInterceptor
{

   protected static final Logger log = Logger.getLogger(CachedListInterceptor.class);
   protected static final Map managedMethods =
         CollectionInterceptorUtil.getManagedMethods(List.class);

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

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

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

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

   // implementation of the java.util.List interface
   protected Node getNode()
   {
      try {
         return cache.get(fqn);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public Object get(int index)
   {
      checkIndex(index);
      try {
         // return cache.getObject(((Fqn) fqn.clone()).add(new Integer(index)));
         return cache.getObject(new Fqn(fqn, new Integer(index)));
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   private void checkIndex(int i) {
      if(size() == 0) return; // No need to check here.
      if( i < 0 || i >= size() ) {
         throw new IndexOutOfBoundsException("Index out of bound at CachedListInterceptor(). Index is " +i
         + " but size is " +size());
      }
   }

   private void checkArgument(Object o) {
      if(o == null)
         throw new NullPointerException("Object is null");
   }

   public int size()
   {
      Node node = getNode();
      if(node == null) {
         return 0;
      }

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

   public void clear()
   {
      // TODO Can use optimization here
      for(int i=size()-1; i >= 0; i--) {
         remove(i);
      }
   }

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

   public Object[] toArray()
   {
      Object[] objs = new Object[size()];
      for(int i=0; i < size(); i++) {
         objs[i] = get(i);
      }
      return objs;
   }

   public Object set(int index, Object element)
   {
      try {
         if(index != 0)
            checkIndex(index-1); // Since index can be size().
         Object oldValue = get(index);
         // return cache.putObject(((Fqn) fqn.clone()).add(new Integer(index)), element);
         return cache.putObject(new Fqn(fqn, new Integer(index)), element);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

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

   public void add(int index, Object element)
   {
      try {
         if(index != 0)
            checkIndex(index-1); // Since index can be size().
         for (int i = size(); i > index; i--) {
            // Object obj = cache.removeObject(((Fqn) fqn.clone()).add(new Integer(i - 1)));
            Object obj = cache.removeObject(new Fqn(fqn, new Integer(i - 1)));
            // cache.putObject(((Fqn) fqn.clone()).add(new Integer(i)), obj);
            cache.putObject(new Fqn(fqn, new Integer(i)), obj);
         }
         // cache.putObject(((Fqn) fqn.clone()).add(new Integer(index)), element);
         cache.putObject(new Fqn(fqn, new Integer(index)), element);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public int indexOf(Object o)
   {
      checkArgument(o);
      for(int i=0; i < size(); i++) {
         if(o.equals(get(i))) return i;
      }
      return -1;
   }

   public int lastIndexOf(Object o)
   {
      checkArgument(o);
      int index = -1;
      for(int i=0; i < size(); i++) {
         if(o.equals(get(i))) index = i;
      }
      return index;
   }

   public boolean add(Object o)
   {
      add(size(), o);
      return true;
   }

   public boolean contains(Object o)
   {
      if (indexOf(o) != -1) return true;
      return false;
   }

   public Object remove(int index)
   {
      try {
         checkIndex(index);
         // Object result = cache.removeObject(((Fqn) fqn.clone()).add(new Integer(index)));
         Object result = cache.removeObject(new Fqn(fqn, new Integer(index)));
         int size = size();
         for (int i = index; i < size; i++) {
            // Object obj = cache.removeObject(((Fqn) fqn.clone()).add(new Integer(i + 1)));
            Object obj = cache.removeObject(new Fqn(fqn, new Integer(i + 1)));
            // cache.putObject(((Fqn) fqn.clone()).add(new Integer(i)), obj);
            cache.putObject(new Fqn(fqn, new Integer(i)), obj);
         }
         return result;
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public boolean remove(Object o)
   {
      int i = indexOf(o);
      if(i == -1)
         return false;

      remove(i);
      return true;
   }

   public boolean addAll(int index, Collection c)
   {
      throw new AopOperationNotSupportedException("CachedListInterceptor: List.addAll() operation not supported");
   }

   public boolean addAll(Collection c)
   {
      Iterator i = c.iterator();
      while(i.hasNext()) {
         Object o = i.next();
         add(o);
      }
      return true;
   }

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

   public boolean removeAll(Collection c)
   {
      Iterator i = c.iterator();
      while(i.hasNext()) {
         Object o = i.next();
         remove(o);
      }
      return true;
   }

   public int hashCode()
   {
      int result = 0;
      for (int i =0; i < size(); i++) {
         result += get(i).hashCode();
      }
      return result;
   }

   public boolean equals(Object object)
   {
      if (object == this)
         return true;
      if (!(object instanceof List))
         return false;
      List list = (List) object;
      if (size() != list.size())
         return false;
      for (int i=0; i < list.size(); i++) {
         Object value = list.get(i);
         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();
      int size = size();
      for (int i =0; i < size; i++) {
         Object key = get(i);
         buf.append("[" +key).append("]");
         if(i <= size) buf.append(", ");
      }

      return buf.toString();
   }

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

   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 List subList(int fromIndex, int toIndex)
   {
      throw new AopOperationNotSupportedException("CachedListInterceptor: List.subList() operation not supported");
   }

   public ListIterator listIterator()
   {
      return new MyListIterator(0);
   }

   public ListIterator listIterator(int index)
   {
      return new MyListIterator(index);
   }

   protected class MyListIterator implements ListIterator {
      protected int index = 0;

      public MyListIterator(int index) {
         if(index < 0 || index >= size()) {
            throw new AopOperationNotSupportedException("CachedListInterceptor: MyListIterator construction. " +
                  " Index is out of bound : " +index);
         }
         this.index = index;
      }

      public int nextIndex()
      {
         return index;
      }

      public int previousIndex()
      {
         return index-1;
      }

      public void remove()
      {
         throw new AopOperationNotSupportedException("CachedListInterceptor: ListIterator.remove() optional operation not supported");
      }

      public boolean hasNext()
      {
         return (index == size()-1) ? false : true;
      }

      public boolean hasPrevious()
      {
         return (index == 0) ? false : true;
      }

      public Object next()
      {
         if( index == size() )
            throw new NoSuchElementException();

         index++;
         return get(index-1);
      }

      public Object previous()
      {
         if( index == 0 )
            throw new NoSuchElementException();

         index--;
         return get(index);
      }

      public void add(Object o)
      {
         throw new AopOperationNotSupportedException("CachedListInterceptor: ListIterator.add() optional operation not supported");
      }

      public void set(Object o)
      {
         throw new AopOperationNotSupportedException("CachedListInterceptor: ListIterator.set() optional operation not supported");
      }
   }
}