/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.metatype.plugins.values;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.Map.Entry;

import org.jboss.metatype.api.types.MapCompositeMetaType;
import org.jboss.metatype.api.values.CompositeValue;
import org.jboss.metatype.api.values.MapCompositeValueSupport;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.MetaValueFactory;
import org.jboss.util.UnreachableStatementException;

/**
 * CompositeValueInvocationHandler.
 *
 * @author <a href="mailto:ales.justin@jboss.com">Ales Justin</a>
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @author Scott.Stark@jboss.org
 * @version $Revision: 84841 $
 */
public class CompositeValueInvocationHandler implements InvocationHandler, Serializable
{
   private static final long serialVersionUID = 1;

   /** The MetaValueFactory */
   private static MetaValueFactory factory = MetaValueFactory.getInstance();

   /** A cache of methods to keys */
   private static final Map<Method, String> compositeDataKeyCache = Collections.synchronizedMap(new WeakHashMap<Method, String>());

   /** The composite value */
   private CompositeValue compositeValue;

   /**
    * Create a new CompositeValueInvocationHandler.
    *
    * @param compositeValue the composite value
    */
   public CompositeValueInvocationHandler(CompositeValue compositeValue)
   {
      if (compositeValue == null)
         throw new IllegalArgumentException("Null compositeValue");
      this.compositeValue = compositeValue;
   }

   /**
    * Get the compositeData.
    *
    * @return the compositeData.
    */
   public CompositeValue getCompositeValue()
   {
      return compositeValue;
   }

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
   {
      Class<?> declaringClass = method.getDeclaringClass();
      if (Object.class.equals(declaringClass))
         return handleObjectInvocation(method.getName(), args);

      Type returnType = method.getGenericReturnType();
      // size
      if(method.getName().equals("size"))
         return compositeValue.values().size();

      MetaValue value = compositeValue.get(getCompositeDataKey(method));
      return factory.unwrap(value, returnType);
   }

   private Object handleObjectInvocation(String name, Object[] args) throws Throwable
   {
      if ("equals".equals(name))
      {
         Object object = args[0];
         if (object == null || object instanceof Proxy == false)
            return false;
         InvocationHandler handler = Proxy.getInvocationHandler(object);
         if (handler == this)
            return true;
         if (handler == null || handler instanceof CompositeValueInvocationHandler == false)
            return false;

         CompositeValueInvocationHandler other = (CompositeValueInvocationHandler) handler;
         return getCompositeValue().equals(other.getCompositeValue());
      }
      else if ("hashCode".equals(name))
         return getCompositeValue().hashCode();
      else if ("toString".equals(name))
         return getCompositeValue().toString();
      throw new UnreachableStatementException();
   }

   /**
    * Get the key for a composite data getter method
    *
    * @param method the method
    * @return the key
    */
   public static String getCompositeDataKey(Method method)
   {
      String key = compositeDataKeyCache.get(method);
      if (key != null)
         return key;

      StringBuilder fieldName = null;

      Class<?> returnType = method.getReturnType();
      Class<?>[] paramTypes = method.getParameterTypes();
      if (Void.TYPE.equals(returnType) == false && paramTypes.length == 0)
      {
         String name = method.getName();
         if (name.startsWith("is") && name.length() > 2)
         {
            if (Boolean.TYPE.equals(returnType))
            {
               fieldName = new StringBuilder();
               fieldName.append(Character.toLowerCase(name.charAt(2)));
               if (name.length() > 3)
                  fieldName.append(name.substring(3));
            }
         }
         else if (name.startsWith("get") && name.length() > 3)
         {
            fieldName = new StringBuilder();
            fieldName.append(Character.toLowerCase(name.charAt(3)));
            if (name.length() > 4)
               fieldName.append(name.substring(4));
         }
      }

      if (fieldName == null)
         return null;

      String result = fieldName.toString();
      compositeDataKeyCache.put(method, result);
      return result;
   }

   /*
   static class MapCVProxy implements Map<String, Object>
   {
      private CompositeValue delegate;

      MapCVProxy(CompositeValue delegate)
      {
         this.delegate = delegate;
      }

      public void clear()
      {
      }

      public boolean containsKey(Object key)
      {
         return delegate.containsKey(key.toString());
      }

      public boolean containsValue(Object value)
      {
         if ((value instanceof MetaValue) == false)
         {
            value = factory.create(value);
         }
         MetaValue mv = MetaValue.class.cast(value);
         return delegate.containsValue(mv);
      }

      public Set<Entry<String, Object>> entrySet()
      {
         HashSet<Entry<String, MetaValue>> entrySet = new HashSet<Entry<String, MetaValue>>();
         for(String key : delegate.getMetaType().itemSet())
         {
            MetaValue value = delegate.get(key);
            Map.Entry<String, Object> entry = new MapCVProxyEntry(key, value);
            entrySet.add(entry);
         }
         return entrySet;
      }

      public MetaValue get(Object key)
      {
         return delegate.get(key.toString());
      }

      public boolean isEmpty()
      {
         return delegate.values().size() == 0;
      }

      public Set<String> keySet()
      {
         return delegate.getMetaType().itemSet();
      }

      public MetaValue put(String key, MetaValue value)
      {
         MetaValue old = null;
         if (delegate instanceof MapCompositeValueSupport)
         {
            MapCompositeValueSupport mcvs = (MapCompositeValueSupport) delegate;
            old = mcvs.get(key);
            if ((value instanceof MetaValue) == false)
               
            mcvs.put(key, value);
         }
         return old;
      }

      public void putAll(Map<? extends String, ? extends Object> map)
      {
         if (delegate instanceof MapCompositeValueSupport)
         {
            MapCompositeValueSupport mcvs = (MapCompositeValueSupport) delegate;
            for(String key : map.keySet())
            {
               Object mv = map.get(key);
               if ((mv instanceof MetaValue) == false)
                  mv = factory.create(mv);
               mcvs.put(key, (MetaValue) mv);
            }
         }
      }

      public Object remove(Object key)
      {
         if (delegate instanceof MapCompositeValueSupport)
         {
            MapCompositeValueSupport mcvs = (MapCompositeValueSupport) delegate;
            return mcvs.remove(key.toString());
         }
         return null;
      }

      public int size()
      {
         return delegate.values().size();
      }

      public Collection<Object> values()
      {
         return delegate.values();
      }
      
   }
   static class MapCVProxyEntry implements Entry<String, Object>
   {
      private String key;
      private Object value;
      MapCVProxyEntry(String key, Object value)
      {
         this.key = key;
         this.value = value;
      }
      public String getKey()
      {
         return key;
      }
      public Object getValue()
      {
         return value;
      }

      public Object setValue(Object value)
      {
         Object old = value;
         this.value = value;
         return old;
      }
   }
   */
}
