package railo.runtime.type;

import java.io.Serializable;
import java.sql.Types;
import java.util.Date;

import railo.runtime.op.Caster;
import railo.runtime.op.Decision;


/**
 * Helper class for the QueryColumnImpl
 */
public final class QueryColumnUtil implements Serializable {

	private static final long serialVersionUID = 4654833724194716718L;

	/**
     * reset the type of the column
     */
    protected static void resetType(QueryColumnImpl column) {
        column.type=Types.OTHER;
    }
    
	/**
	 * redefine type of value 
     * @param value
	 * @return redefined type of the value
     */
    protected static Object reDefineType(QueryColumnImpl column,Object value) {
        column.typeChecked=false;
        if(value==null || column.type==Types.OTHER)return value;
        if(value instanceof String && ((String)value).isEmpty()) return null;
        
        switch(column.type) {
        	
        // Numeric Values
			case Types.DOUBLE:	return reDefineDouble(column,value);
			case Types.BIGINT:	return reDefineDecimal(column,value);
    		case Types.NUMERIC:	return reDefineDouble(column,value);
    		case Types.INTEGER:	return reDefineInteger(column,value);
    		case Types.TINYINT:	return reDefineTinyInt(column,value);
    		case Types.FLOAT:	return reDefineFloat(column,value);
    		case Types.DECIMAL:	return reDefineDecimal(column,value);
    		case Types.REAL:	return reDefineFloat(column,value);
    		case Types.SMALLINT:return reDefineShort(column,value);
    		
    	// DateTime Values
    		case Types.TIMESTAMP:	return reDefineDateTime(column,value);
    		case Types.DATE:		return reDefineDateTime(column,value);
    		case Types.TIME:		return reDefineDateTime(column,value);
    	
    	// Char
    		case Types.CHAR: 		return reDefineString(column,value);
    		case Types.VARCHAR: 	return reDefineString(column,value);
    		case Types.LONGVARCHAR: return reDefineString(column,value);
    		case Types.CLOB:		return reDefineClob(column,value);
    	
    	// Boolean
    		case Types.BOOLEAN:		return reDefineBoolean(column,value);
    		case Types.BIT:			return reDefineBoolean(column,value);	
    	
    	// Binary
    		case Types.BINARY:			return reDefineBinary(column,value);
    		case Types.VARBINARY:		return reDefineBinary(column,value);
    		case Types.LONGVARBINARY:	return reDefineBinary(column,value);
    		case Types.BLOB:			return reDefineBlob(column,value);
    		
    	// Others
    		case Types.ARRAY:				return reDefineOther(column,value);
    		case Types.DATALINK:				return reDefineOther(column,value);
    		case Types.DISTINCT:				return reDefineOther(column,value);
    		case Types.JAVA_OBJECT:				return reDefineOther(column,value);
    		case Types.NULL:				return reDefineOther(column,value);
    		case Types.STRUCT:				return reDefineOther(column,value);
    		case Types.REF:				return reDefineOther(column,value);
    		default: return value; 
        }
        
    }

    private static Object reDefineBoolean(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToBoolean(value))
    		return value;
    	
        resetType(column);
        return value;
    }

    private static Object reDefineDouble(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToNumeric(value))
    		return value;
    	
        resetType(column);
        return value;
    }

    private static Object reDefineFloat(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToNumeric(value))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineInteger(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToNumeric(value))
    		return value;
    	resetType(column);
        return value;
    }

    private static Object reDefineShort(QueryColumnImpl column, Object value) {
    	
    	double dbl = Caster.toDoubleValue(value,Double.NaN);
        if(Decision.isValid(dbl)) {  
            short sht=(short)dbl;
            if(sht==dbl)return value;
            
            column.type=Types.DOUBLE;
            return value;
        }
        resetType(column);
        return value;
    }

    private static Object reDefineTinyInt(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToNumeric(value))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineDecimal(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToNumeric(value))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineDateTime(QueryColumnImpl column, Object value) {
    	if(Decision.isDateSimple(value,true))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineString(QueryColumnImpl column, Object value) {
    	if(Decision.isCastableToString(value))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineClob(QueryColumnImpl column, Object value) {
    	if(Decision.isCastableToString(value))
    		return value;
        resetType(column);
        return value;
    }

    private static Object reDefineBinary(QueryColumnImpl column, Object value) {
    	if(Decision.isCastableToBinary(value,false))
    		return value;
    	
    	resetType(column);
        return value;
    	
    	
    }

    private static Object reDefineBlob(QueryColumnImpl column,Object value) {
    	if(Decision.isCastableToBinary(value,false))
    		return value;
    	resetType(column);
        return value;
        
    
    }
    
    private static Object reDefineOther(QueryColumnImpl column,Object value) {
        resetType(column);
        return value;
    }
    

    /**
     * reorganize type of a column
     * @param reorganize 
     */
    protected static void reOrganizeType(QueryColumnImpl column) {
    	if((column.type==Types.OTHER) && !column.typeChecked) {
        	column.typeChecked=true;
        	if(column.size()>0) {
                checkOther(column,column.data[0]);
        		
                // get Type
                for(int i=1;i<column.size();i++) {
                    switch(column.type) {
                        case Types.NULL:checkOther(column,column.data[i]);break;
                        case Types.TIMESTAMP:checkDate(column,column.data[i]);break;
                        //case Types.DATE:checkDate(column.data[i]);break;
                        case Types.BOOLEAN:checkBoolean(column,column.data[i]);break;
                        case Types.DOUBLE:checkDouble(column,column.data[i]);break;
                        case Types.VARCHAR:checkBasic(column,column.data[i]);break;
                        default:break;
                    }
                }
            }
        }
    }
    
    
    

    private static void checkOther(QueryColumnImpl column, Object value) {
        // NULL
        if(value==null) {
            column.type=Types.NULL;
            return;
        }
        // DateTime
        if(Decision.isDateSimple(value,false)) {
            column.type=Types.TIMESTAMP;
            return;
        }
        // Boolean
        if(Decision.isBoolean(value)) {
            column.type=Types.BOOLEAN;
            return;
        }
        // Double
        if(Decision.isNumeric(value)) {
            column.type=Types.DOUBLE;
            return;
        }
        // String
        String str = Caster.toString(value,null);
        if(str!=null) {
            column.type=Types.VARCHAR;
            return;
        }
    }
    
    private static void checkDate(QueryColumnImpl column,Object value) {
        // NULL
        if(value==null) return;
        // DateTime
        if(Decision.isDateSimple(value,false)) {
            column.type=Types.TIMESTAMP;
            return;
        }
        // String
        String str = Caster.toString(value,null);
        if(str!=null) {
            column.type=Types.VARCHAR;
            return;
        }
        // Other
        column.type=Types.OTHER;
        return;
    }

    private static void checkBoolean(QueryColumnImpl column, Object value) {
        // NULL
        if(value==null) return;
        // Boolean
        if(Decision.isBoolean(value)) {
            column.type=Types.BOOLEAN;
            return;
        }
        // Double
        if(Decision.isNumeric(value)) {
            column.type=Types.DOUBLE;
            return;
        }
        // String
        String str = Caster.toString(value,null);
        if(str!=null) {
            column.type=Types.VARCHAR;
            return;
        }
        // Other
        column.type=Types.OTHER;
        return;
    }
    private static void checkDouble(QueryColumnImpl column,Object value) {
        // NULL
        if(value==null) return;
        // Double
        if(Decision.isNumeric(value)) {
            column.type=Types.DOUBLE;
            return;
        }
        // String
        String str = Caster.toString(value,null);
        if(str!=null) {
            column.type=Types.VARCHAR;
            return;
        }
        // Other
        column.type=Types.OTHER;
        return;
    }
    
    private static void checkBasic(QueryColumnImpl column,Object value) {
        // NULL
        if(value==null) return;
        // Date
        if(value instanceof Date || value instanceof Number) return;
        // String
        String str = Caster.toString(value,null);
        if(str!=null) {
            return;
        }
        // OTHER
        column.type=Types.OTHER;
        return;
    }
}