/*
 *  Copyright 2004 Clinton Begin
 *
 *  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 com.ibatis.sqlmap.engine.type;

import com.ibatis.sqlmap.client.SqlMapException;

import java.math.BigDecimal;
import java.util.*;

/**
 * Not much of a suprise, this is a factory class for TypeHandler objects.
 */
public class TypeHandlerFactory {

  private final Map typeHandlerMap = new HashMap();
  private final TypeHandler unknownTypeHandler = new UnknownTypeHandler(this);
  private final HashMap typeAliases = new HashMap();

  private static final Map reversePrimitiveMap = new HashMap() {{
    put(Byte.class, byte.class);
    put(Short.class, short.class);
    put(Integer.class, int.class);
    put(Long.class, long.class);
    put(Float.class, float.class);
    put(Double.class, double.class);
    put(Boolean.class, boolean.class);
  }};


  /* Constructor */

  /**
   * Default constructor
   */
  public TypeHandlerFactory() {
    TypeHandler handler;

    handler = new BooleanTypeHandler();
    register(Boolean.class, handler);
    register(boolean.class, handler);

    handler = new ByteTypeHandler();
    register(Byte.class, handler);
    register(byte.class, handler);

    handler = new ShortTypeHandler();
    register(Short.class, handler);
    register(short.class, handler);

    handler = new IntegerTypeHandler();
    register(Integer.class, handler);
    register(int.class, handler);

    handler = new LongTypeHandler();
    register(Long.class, handler);
    register(long.class, handler);

    handler = new FloatTypeHandler();
    register(Float.class, handler);
    register(float.class, handler);

    handler = new DoubleTypeHandler();
    register(Double.class, handler);
    register(double.class, handler);

    register(String.class, new StringTypeHandler());
    register(String.class, "CLOB", new CustomTypeHandler(new ClobTypeHandlerCallback()));
    register(String.class, "LONGVARCHAR", new CustomTypeHandler(new ClobTypeHandlerCallback()));

    register(BigDecimal.class, new BigDecimalTypeHandler());

    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, "BLOB", new CustomTypeHandler(new BlobTypeHandlerCallback()));
    register(byte[].class, "LONGVARBINARY", new CustomTypeHandler(new BlobTypeHandlerCallback()));

    register(Object.class, new ObjectTypeHandler());
    register(Object.class, "OBJECT", new ObjectTypeHandler());

    register(Date.class, new DateTypeHandler());
    register(Date.class, "DATE", new DateOnlyTypeHandler());
    register(Date.class, "TIME", new TimeOnlyTypeHandler());


    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    putTypeAlias("string", String.class.getName());
    putTypeAlias("byte", Byte.class.getName());
    putTypeAlias("long", Long.class.getName());
    putTypeAlias("short", Short.class.getName());
    putTypeAlias("int", Integer.class.getName());
    putTypeAlias("integer", Integer.class.getName());
    putTypeAlias("double", Double.class.getName());
    putTypeAlias("float", Float.class.getName());
    putTypeAlias("boolean", Boolean.class.getName());
    putTypeAlias("date", Date.class.getName());
    putTypeAlias("decimal", BigDecimal.class.getName());
    putTypeAlias("object", Object.class.getName());
    putTypeAlias("map", Map.class.getName());
    putTypeAlias("hashmap", HashMap.class.getName());
    putTypeAlias("list", List.class.getName());
    putTypeAlias("arraylist", ArrayList.class.getName());
    putTypeAlias("collection", Collection.class.getName());
    putTypeAlias("iterator", Iterator.class.getName());
    putTypeAlias("cursor", java.sql.ResultSet.class.getName());

  }

  /* Public Methods */

  /**
   * Get a TypeHandler for a class
   * 
   * @param type - the class you want a TypeHandler for
   * 
   * @return - the handler
   */
  public TypeHandler getTypeHandler(Class type) {
    return getTypeHandler(type, null);
  }

  /**
   * Get a TypeHandler for a class and a JDBC type
   * 
   * @param type - the class
   * @param jdbcType - the jdbc type
   * 
   * @return - the handler
   */
  public TypeHandler getTypeHandler(Class type, String jdbcType) {
    Map jdbcHandlerMap = (Map) typeHandlerMap.get(type);
    TypeHandler handler = null;
    if (jdbcHandlerMap != null) {
      handler = (TypeHandler) jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = (TypeHandler) jdbcHandlerMap.get(null);
      }
    }
    if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
      handler = new EnumTypeHandler(type);
    }
    return handler;
  }

  /**
   * When in doubt, get the "unknown" type handler
   * 
   * @return - if I told you, it would not be unknown, would it?
   */
  public TypeHandler getUnkownTypeHandler() {
    return unknownTypeHandler;
  }


  /**
   * Tells you if a particular class has a TypeHandler
   * 
   * @param type - the class
   * 
   * @return - true if there is a TypeHandler
   */
  public boolean hasTypeHandler(Class type) {
    return type != null && (getTypeHandler(type) != null || Enum.class.isAssignableFrom(type));
  }

  /**
   * Register (add) a type handler for a class
   * 
   * @param type - the class
   * @param handler - the handler instance
   */
  public void register(Class type, TypeHandler handler) {
    register(type, null, handler);
  }

  /**
   * Register (add) a type handler for a class and JDBC type
   * 
   * @param type - the class
   * @param jdbcType - the JDBC type
   * @param handler - the handler instance
   */
  public void register(Class type, String jdbcType, TypeHandler handler) {
    Map map = (Map) typeHandlerMap.get(type);
    if (map == null) {
      map = new HashMap();
      typeHandlerMap.put(type, map);
    }
    map.put(jdbcType, handler);

    if (reversePrimitiveMap.containsKey(type)) {
      register((Class)reversePrimitiveMap.get(type),jdbcType,handler);
    }
  }

  /**
   * Lookup an aliased class and return it's REAL name
   * 
   * @param string - the alias
   * 
   * @return - the REAL name
   */
  public String resolveAlias(String string) {
    String key = null;
    if(string != null)
      key = string.toLowerCase();
    String value = null;
    if (typeAliases.containsKey(key)) {
      value = (String) typeAliases.get(key);
    } else {
      value = string;
    }

    return value;
  }

  /**
   * Adds a type alias that is case insensitive.  All of the following String, string, StRiNg will equate to the same alias.
   * @param alias - the alias
   * @param value - the real class name
   */
  public void putTypeAlias(String alias, String value) {
    String key = null;
    if(alias != null)
      key = alias.toLowerCase();
    if (typeAliases.containsKey(key) && !typeAliases.get(key).equals(value)) {
      throw new SqlMapException("Error in XmlSqlMapClientBuilder.  Alias name conflict occurred.  The alias '" + key + "' is already mapped to the value '" + typeAliases.get(alias) + "'.");
    }
    typeAliases.put(key, value);
  }

}
