/*
 * Copyright 2010 JBoss, a divison Red Hat, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.errai.common.client.types;

import org.jboss.errai.common.client.types.handlers.collections.*;
import org.jboss.errai.common.client.types.handlers.numbers.*;

import java.util.*;

public class TypeHandlerFactory {
  private static Map<Class, Map<Class, TypeHandler>> handlers =
      new HashMap<Class, Map<Class, TypeHandler>>();

  private static Map<Class, Class> inheritanceMap = new HashMap<Class, Class>();

  static {
    /**
     * Declare all the default coercion handlers.
     */
    final Map<Class, TypeHandler> collectionHandlers = new HashMap<Class, TypeHandler>();
    collectionHandlers.put(Object[].class, new CollectionToObjArray());
    collectionHandlers.put(String[].class, new CollectionToStringArray());
    collectionHandlers.put(Integer[].class, new CollectionToIntArray());
    collectionHandlers.put(Long[].class, new CollectionToLongArray());
    collectionHandlers.put(Boolean[].class, new CollectionToBooleanArray());
    collectionHandlers.put(Double[].class, new CollectionToDoubleArray());

    collectionHandlers.put(Set.class, new CollectionToSet());
    collectionHandlers.put(List.class, new CollectionToList());
    collectionHandlers.put(Queue.class, new CollectionToQueue());

    handlers.put(Collection.class, collectionHandlers);

    final Map<Class, TypeHandler> numberHandlers = new HashMap<Class, TypeHandler>();
    numberHandlers.put(Integer.class, new NumberToInt());
    numberHandlers.put(Long.class, new NumberToLong());
    numberHandlers.put(Short.class, new NumberToShort());
    numberHandlers.put(Float.class, new NumberToFloat());
    numberHandlers.put(Double.class, new NumberToFloat());
    numberHandlers.put(Byte.class, new NumberToByte());
    numberHandlers.put(java.util.Date.class, new NumberToDate());
    numberHandlers.put(java.sql.Date.class, new NumberToSQLDate());

    handlers.put(Number.class, numberHandlers);

    /**
     * Build an inheretence Map.
     */
    inheritanceMap.put(Integer.class, Number.class);
    inheritanceMap.put(Long.class, Number.class);
    inheritanceMap.put(Short.class, Number.class);
    inheritanceMap.put(Float.class, Number.class);
    inheritanceMap.put(Double.class, Number.class);

    inheritanceMap.put(ArrayList.class, List.class);
    inheritanceMap.put(LinkedList.class, List.class);
    inheritanceMap.put(AbstractList.class, List.class);
    inheritanceMap.put(Stack.class, List.class);

    inheritanceMap.put(HashSet.class, Set.class);
    inheritanceMap.put(AbstractSet.class, Set.class);

    inheritanceMap.put(Set.class, Collection.class);
    inheritanceMap.put(List.class, Collection.class);

    addHandler(String.class, Character.class, new TypeHandler<String, Character>() {
      public Character getConverted(final String in) {
        return in.charAt(0);
      }
    });
  }

  public static Map<Class, TypeHandler> getHandler(final Class from) {
    if (!handlers.containsKey(from) && inheritanceMap.containsKey(from)) {
      return getHandler(inheritanceMap.get(from));
    }
    else {
      return handlers.get(from);
    }
  }

  public static <T> T convert(final Object value, final Class<T> to) {
    if (value == null) return null;
    return convert(value.getClass(), to, value);
  }

  public static <T> T convert(final Class from, final Class<T> to, final Object value) {
    if (value.getClass() == to) return (T) value;
    final Map<Class, TypeHandler> toHandlers = getHandler(from);
    if (toHandlers == null) {
      if (value instanceof String) {
        /**
         * We assume that this may be an enum type.  It may not be, but we try to decode it
         * as such.
         */
        try {
          T val = (T) Enum.valueOf((Class<? extends Enum>) to, (String) value);

          /**
           * If we successfully decoded an enum, then we cache this handler for future
           * use.
           */
          addHandler(from, to, new TypeHandler<String, T>() {
            public T getConverted(final String in) {
              return (T) Enum.valueOf((Class<? extends Enum>) to, in);
            }
          });

          return val;
        }
        catch (Exception e) {
          /**
           * Definitely not an enum, so do nothing.
           */
          return (T) value;
        }
      }

      return (T) value;
    }

    if (toHandlers.containsKey(to)) {
      return (T) toHandlers.get(to).getConverted(value);
    }
    else {
      return (T) value;
    }
  }

  public static void addHandler(final Class from, final Class to, final TypeHandler handler) {
    if (!handlers.containsKey(from)) {
      handlers.put(from, new HashMap<Class, TypeHandler>());
    }
    handlers.get(from).put(to, handler);
  }
}
