package fitnesse.slim.converters;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.ParameterizedType;
import java.util.*;

import fitnesse.slim.Converter;

public class ConverterRegistry {

  private static final Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>();
  private static Converter<Object> defaultConverter = new DefaultConverter();

  static {
    addStandardConverters();
  }

  protected static void addStandardConverters() {
    addConverter(void.class, new VoidConverter());

    addConverter(String.class, new StringConverter());
    addConverter(Date.class, new DateConverter());

    addConverter(Double.class, new DoubleConverter());
    addConverter(double.class, new PrimitiveDoubleConverter());

    addConverter(Integer.class, new IntConverter());
    addConverter(int.class, new PrimitiveIntConverter());

    addConverter(Character.class, new CharConverter());
    addConverter(char.class, new PrimitiveCharConverter());

    addConverter(Boolean.class, new BooleanConverter());
    addConverter(boolean.class, new PrimitiveBooleanConverter());

    try {
      addConverter(Map.class, new MapConverter());
    } catch (NoClassDefFoundError e) {
      System.err.println("Slim  Map converter not loaded: could not find class " + e.getMessage());
    }
  }

  public static <T> Converter<T> getConverterForClass(Class<? extends T> clazz) {
    Converter<T> converter = getConverterForClass(clazz, null);
    return converter;
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  public static <T> Converter<T> getConverterForClass(Class<? extends T> clazz, ParameterizedType typedClazz) {

    //use converter set in registry
    if (converters.containsKey(clazz)) {
      return (Converter<T>) converters.get(clazz);
    }
    // use converter for superclass set in registry
    Class<?> superclass = clazz.getSuperclass();
    while (superclass != null && !Object.class.equals(superclass)) {
      if (converters.containsKey(superclass)) {
        return (Converter<T>) converters.get(superclass);
      }
      superclass = superclass.getSuperclass();
    }
    // use converter for implemented interface set in registry
    Converter<T> converterForInterface = getConverterForInterface(clazz);
    if (converterForInterface != null) {
      return converterForInterface;
    }

    //use property editor
    PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
    if (pe != null && !"EnumEditor".equals(pe.getClass().getSimpleName())) {
      // com.sun.beans.EnumEditor and sun.beans.EnumEditor seem to be used in different usages.
      return new PropertyEditorConverter<T>(pe);
    }

    //for enum, use generic enum converter
    if (clazz.isEnum()) {
      return new GenericEnumConverter(clazz);
    }

    //for array, use generic array converter
    if (clazz.isArray()) {
      Class<?> componentType = clazz.getComponentType();
      Converter<?> converterForClass = getConverterForClassOrDefaultConverter(componentType);
      return new GenericArrayConverter(componentType, converterForClass);
    }

    //for collection, use generic collection converter
    if (Collection.class.isAssignableFrom(clazz)) {
      Class<?> componentType = typedClazz != null ? (Class<?>) typedClazz.getActualTypeArguments()[0] : String.class;
      Converter<?> converterForClass = getConverterForClassOrDefaultConverter(componentType);
      return new GenericCollectionConverter(clazz, converterForClass);
    }

    // last resort, see if there is a converter for Object
    return (Converter<T>) converters.get(Object.class);
  }

  protected static <T> Converter<T> getConverterForInterface(Class<?> clazz) {
    List<Class<?>> superInterfaces = new ArrayList<Class<?>>();
    Converter<T> converterForInterface = null;
    Class<?>[] interfaces = clazz.getInterfaces();
    for (Class<?> interf : interfaces) {
      Class<?>[] s = interf.getInterfaces();
      superInterfaces.addAll(Arrays.asList(s));
      if (converters.containsKey(interf)) {
        converterForInterface = (Converter<T>) converters.get(interf);
        break;
      }
    }
    if (converterForInterface == null) {
      for (Class<?> supInterf : superInterfaces) {
        converterForInterface = getConverterForInterface(supInterf);
        if (converterForInterface != null) {
          break;
        }
      }
    }
    if (converterForInterface == null) {
      Class<?> superclass = clazz.getSuperclass();
      while (superclass != null && !Object.class.equals(superclass)) {
        converterForInterface = getConverterForInterface(superclass);
        if (converterForInterface != null) {
          break;
        }
        superclass = superclass.getSuperclass();
      }

    }
    return converterForInterface;
  }

  public static <T> void addConverter(Class<? extends T> clazz, Converter<T> converter) {
    converters.put(clazz, converter);
  }

  public static Map<Class<?>, Converter<?>> getConverters() {
    return Collections.unmodifiableMap(converters);
  }

  /*
   * PRIVATE
   */
  public static Converter<?> getConverterForClassOrDefaultConverter(Class<?> clazz) {
    Converter<?> converter = getConverterForClass(clazz);
    if (converter == null) {
      converter = defaultConverter;
    }
    return converter;
  }
}

@@ -4397,38 +4397,35 @@
       %7D%0A      %7D%0A
+%0A
   
-  %7D%0A
     if (converte
@@ -4445,24 +4445,26 @@
  == null) %7B%0A
+  
       Class%3C
@@ -4504,24 +4504,26 @@
 ss();%0A      
+ 
+ 
 while (super
@@ -4575,32 +4575,34 @@
 ass)) %7B%0A        
+  
 converterForInte
@@ -4647,32 +4647,34 @@
 class);%0A        
+ 
+ 
 if (converterFor
@@ -4686,32 +4686,34 @@
 face != null) %7B%0A
+  
           break;
@@ -4717,26 +4717,30 @@
 ak;%0A        
-%7D%0A
+  %7D%0A  
         supe
@@ -4778,26 +4778,36 @@
 ss();%0A      
+  %7D%0A%0A      
 %7D
-%0A
 %0A    %7D%0A    r

Check for superclass' interfaces is only ever needed if no converter was found for original interface, so can be nested inside the previous if