package org.apache.struts.action;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.FormPropertyConfig;

public class DynaActionForm extends ActionForm implements DynaBean {
  protected DynaActionFormClass dynaClass = null;
  
  protected HashMap dynaValues = new HashMap();
  
  public void initialize(ActionMapping mapping) {
    String name = mapping.getName();
    if (name == null)
      return; 
    FormBeanConfig config = mapping.getModuleConfig().findFormBeanConfig(name);
    if (config == null)
      return; 
    FormPropertyConfig[] props = config.findFormPropertyConfigs();
    for (int i = 0; i < props.length; i++)
      set(props[i].getName(), props[i].initial()); 
  }
  
  public void reset(ActionMapping mapping, ServletRequest request) {
    try {
      reset(mapping, (HttpServletRequest)request);
    } catch (ClassCastException e) {}
  }
  
  public void reset(ActionMapping mapping, HttpServletRequest request) {}
  
  public boolean contains(String name, String key) {
    Object value = this.dynaValues.get(name);
    if (value == null)
      throw new NullPointerException("No mapped value for '" + name + "(" + key + ")'"); 
    if (value instanceof Map)
      return ((Map)value).containsKey(key); 
    throw new IllegalArgumentException("Non-mapped property for '" + name + "(" + key + ")'");
  }
  
  public Object get(String name) {
    Object value = this.dynaValues.get(name);
    if (value != null)
      return value; 
    Class type = getDynaProperty(name).getType();
    if (type == null)
      throw new NullPointerException("The type for property " + name + " is invalid"); 
    if (!type.isPrimitive())
      return value; 
    if (type == boolean.class)
      return Boolean.FALSE; 
    if (type == byte.class)
      return new Byte((byte)0); 
    if (type == char.class)
      return new Character(false); 
    if (type == double.class)
      return new Double(0.0D); 
    if (type == float.class)
      return new Float(0.0F); 
    if (type == int.class)
      return new Integer(0); 
    if (type == long.class)
      return new Long(0L); 
    if (type == short.class)
      return new Short((short)0); 
    return null;
  }
  
  public Object get(String name, int index) {
    Object value = this.dynaValues.get(name);
    if (value == null)
      throw new NullPointerException("No indexed value for '" + name + "[" + index + "]'"); 
    if (value.getClass().isArray())
      return Array.get(value, index); 
    if (value instanceof List)
      return ((List)value).get(index); 
    throw new IllegalArgumentException("Non-indexed property for '" + name + "[" + index + "]'");
  }
  
  public Object get(String name, String key) {
    Object value = this.dynaValues.get(name);
    if (value == null)
      throw new NullPointerException("No mapped value for '" + name + "(" + key + ")'"); 
    if (value instanceof Map)
      return ((Map)value).get(key); 
    throw new IllegalArgumentException("Non-mapped property for '" + name + "(" + key + ")'");
  }
  
  public DynaClass getDynaClass() {
    return this.dynaClass;
  }
  
  public Map getMap() {
    return this.dynaValues;
  }
  
  public void remove(String name, String key) {
    Object value = this.dynaValues.get(name);
    if (value == null)
      throw new NullPointerException("No mapped value for '" + name + "(" + key + ")'"); 
    if (value instanceof Map) {
      ((Map)value).remove(key);
    } else {
      throw new IllegalArgumentException("Non-mapped property for '" + name + "(" + key + ")'");
    } 
  }
  
  public void set(String name, Object value) {
    DynaProperty descriptor = getDynaProperty(name);
    if (descriptor.getType() == null)
      throw new NullPointerException("The type for property " + name + " is invalid"); 
    if (value == null) {
      if (descriptor.getType().isPrimitive())
        throw new NullPointerException("Primitive value for '" + name + "'"); 
    } else if (!isDynaAssignable(descriptor.getType(), value.getClass())) {
      throw new ConversionException("Cannot assign value of type '" + value.getClass().getName() + "' to property '" + name + "' of type '" + descriptor.getType().getName() + "'");
    } 
    this.dynaValues.put(name, value);
  }
  
  public void set(String name, int index, Object value) {
    Object prop = this.dynaValues.get(name);
    if (prop == null)
      throw new NullPointerException("No indexed value for '" + name + "[" + index + "]'"); 
    if (prop.getClass().isArray()) {
      Array.set(prop, index, value);
    } else if (prop instanceof List) {
      try {
        ((List)prop).set(index, value);
      } catch (ClassCastException e) {
        throw new ConversionException(e.getMessage());
      } 
    } else {
      throw new IllegalArgumentException("Non-indexed property for '" + name + "[" + index + "]'");
    } 
  }
  
  public void set(String name, String key, Object value) {
    Object prop = this.dynaValues.get(name);
    if (prop == null)
      throw new NullPointerException("No mapped value for '" + name + "(" + key + ")'"); 
    if (prop instanceof Map) {
      ((Map)prop).put(key, value);
    } else {
      throw new IllegalArgumentException("Non-mapped property for '" + name + "(" + key + ")'");
    } 
  }
  
  public String toString() {
    StringBuffer sb = new StringBuffer("DynaActionForm[dynaClass=");
    sb.append(getDynaClass().getName());
    DynaProperty[] props = getDynaClass().getDynaProperties();
    if (props == null)
      props = new DynaProperty[0]; 
    for (int i = 0; i < props.length; i++) {
      sb.append(',');
      sb.append(props[i].getName());
      sb.append('=');
      Object value = get(props[i].getName());
      if (value == null) {
        sb.append("<NULL>");
      } else if (value.getClass().isArray()) {
        int n = Array.getLength(value);
        sb.append("{");
        for (int j = 0; j < n; j++) {
          if (j > 0)
            sb.append(','); 
          sb.append(Array.get(value, j));
        } 
        sb.append("}");
      } else if (value instanceof List) {
        int n = ((List)value).size();
        sb.append("{");
        for (int j = 0; j < n; j++) {
          if (j > 0)
            sb.append(','); 
          sb.append(((List)value).get(j));
        } 
        sb.append("}");
      } else if (value instanceof Map) {
        int n = 0;
        Iterator keys = ((Map)value).keySet().iterator();
        sb.append("{");
        while (keys.hasNext()) {
          if (n > 0)
            sb.append(','); 
          n++;
          String key = keys.next();
          sb.append(key);
          sb.append('=');
          sb.append(((Map)value).get(key));
        } 
        sb.append("}");
      } else {
        sb.append(value);
      } 
    } 
    sb.append("]");
    return sb.toString();
  }
  
  void setDynaActionFormClass(DynaActionFormClass dynaClass) {
    this.dynaClass = dynaClass;
  }
  
  protected DynaProperty getDynaProperty(String name) {
    DynaProperty descriptor = getDynaClass().getDynaProperty(name);
    if (descriptor == null)
      throw new IllegalArgumentException("Invalid property name '" + name + "'"); 
    return descriptor;
  }
  
  protected boolean isDynaAssignable(Class dest, Class source) {
    if (dest.isAssignableFrom(source) || (dest == boolean.class && source == Boolean.class) || (dest == byte.class && source == Byte.class) || (dest == char.class && source == Character.class) || (dest == double.class && source == Double.class) || (dest == float.class && source == Float.class) || (dest == int.class && source == Integer.class) || (dest == long.class && source == Long.class) || (dest == short.class && source == Short.class))
      return true; 
    return false;
  }
}
