/**************************************************************************************
 * Copyright (C) 2008 EsperTech, Inc. All rights reserved.                            *
 * http://esper.codehaus.org                                                          *
 * http://www.espertech.com                                                           *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the GPL license       *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package com.espertech.esper.epl.expression;

import com.espertech.esper.client.EventBean;
import com.espertech.esper.util.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;

/**
 * Represents the CAST(expression, type) function is an expression tree.
 */
public class ExprCastNode extends ExprNodeBase implements ExprEvaluator
{
    private final String classIdentifier;
    private Class targetType;
    private transient CasterParserComputer casterParserComputer;
    private transient ExprEvaluator evaluator;
    private static final long serialVersionUID = 7448449031028156455L;

    /**
     * Ctor.
     * @param classIdentifier the the name of the type to cast to
     */
    public ExprCastNode(String classIdentifier)
    {
        this.classIdentifier = classIdentifier;
    }

    public ExprEvaluator getExprEvaluator()
    {
        return this;
    }

    /**
     * Returns the name of the type of cast to.
     * @return type name
     */
    public String getClassIdentifier()
    {
        return classIdentifier;
    }

    public Map<String, Object> getEventType() {
        return null;
    }

    public void validate(ExprValidationContext validationContext) throws ExprValidationException
    {
        if (this.getChildNodes().size() != 1)
        {
            throw new ExprValidationException("Cast function node must have exactly 1 child node");
        }

        evaluator = this.getChildNodes().get(0).getExprEvaluator();
        Class fromType = evaluator.getType();

        // identify target type
        // try the primitive names including "string"
        SimpleTypeCaster caster;
        targetType = JavaClassHelper.getPrimitiveClassForName(classIdentifier.trim());
        boolean numeric;
        if (targetType != null)
        {
            targetType = JavaClassHelper.getBoxedType(targetType);
            caster = SimpleTypeCasterFactory.getCaster(fromType, targetType);
            numeric = caster.isNumericCast();
        }
        else if (classIdentifier.trim().toLowerCase().equals("BigInteger".toLowerCase()))
        {
            targetType = BigInteger.class;
            caster = SimpleTypeCasterFactory.getCaster(fromType, targetType);
            numeric = true;
        }
        else if (classIdentifier.trim().toLowerCase().equals("BigDecimal".toLowerCase()))
        {
            targetType = BigDecimal.class;
            caster = SimpleTypeCasterFactory.getCaster(fromType, targetType);
            numeric = true;
        }
        else
        {
            try
            {
                targetType = JavaClassHelper.getClassForName(classIdentifier.trim());
            }
            catch (ClassNotFoundException e)
            {
                throw new ExprValidationException("Class as listed in cast function by name '" + classIdentifier + "' cannot be loaded", e);
            }
            numeric = JavaClassHelper.isNumeric(targetType);
            if (numeric)
            {
                caster = SimpleTypeCasterFactory.getCaster(fromType, targetType);
            }
            else
            {
                caster = new SimpleTypeCasterAnyType(targetType);
            }
        }

        // to-string
        if (targetType == String.class)
        {
            casterParserComputer = new StringXFormComputer();
        }
        // parse
        else if (fromType == String.class)
        {
            SimpleTypeParser parser = SimpleTypeParserFactory.getParser(JavaClassHelper.getBoxedType(targetType));
            casterParserComputer = new StringParserComputer(parser);
        }
        // numeric cast with check
        else if (numeric)
        {
            casterParserComputer = new NumberCasterComputer(caster);
        }
        // non-numeric cast
        else
        {
            casterParserComputer = new NonnumericCasterComputer(caster);
        }
    }

    public boolean isConstantResult()
    {
        return false;
    }

    public Class getType()
    {
        return targetType;
    }

    public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext)
    {
        Object result = evaluator.evaluate(eventsPerStream, isNewData, exprEvaluatorContext);
        if (result == null)
        {
            return null;
        }
        return casterParserComputer.compute(result);
    }

    public String toExpressionString()
    {
        StringBuilder buffer = new StringBuilder();
        buffer.append("cast(");
        buffer.append(this.getChildNodes().get(0).toExpressionString());
        buffer.append(", ");
        buffer.append(classIdentifier);
        buffer.append(')');
        return buffer.toString();
    }

    public boolean equalsNode(ExprNode node)
    {
        if (!(node instanceof ExprCastNode))
        {
            return false;
        }
        ExprCastNode other = (ExprCastNode) node;
        if (other.classIdentifier.equals(this.classIdentifier))
        {
            return true;
        }

        return false;
    }

    /**
     * Casting and parsing computer.
     */
    public interface CasterParserComputer
    {
        /**
         * Compute an result performing casting and parsing.
         * @param input to process
         * @return cast or parse result
         */
        public Object compute(Object input);
    }

    /**
     * Casting and parsing computer.
     */
    public static class StringXFormComputer implements CasterParserComputer
    {
        public Object compute(Object input)
        {
            return input.toString();
        }
    }

    /**
     * Casting and parsing computer.
     */
    public static class NumberCasterComputer implements CasterParserComputer
    {
        private final SimpleTypeCaster numericTypeCaster;

        /**
         * Ctor.
         * @param numericTypeCaster caster
         */
        public NumberCasterComputer(SimpleTypeCaster numericTypeCaster)
        {
            this.numericTypeCaster = numericTypeCaster;
        }

        public Object compute(Object input)
        {
            if (input instanceof Number)
            {
                return numericTypeCaster.cast(input);
            }
            return null;
        }
    }

    /**
     * Casting and parsing computer.
     */
    public static class StringParserComputer implements CasterParserComputer
    {
        private final SimpleTypeParser parser;

        /**
         * Ctor.
         * @param parser parser
         */
        public StringParserComputer(SimpleTypeParser parser)
        {
            this.parser = parser;
        }

        public Object compute(Object input)
        {
            return parser.parse(input.toString());
        }
    }

    /**
     * Casting and parsing computer.
     */
    public static class NonnumericCasterComputer implements CasterParserComputer
    {
        private final SimpleTypeCaster caster;

        /**
         * Ctor.
         * @param numericTypeCaster caster
         */
        public NonnumericCasterComputer(SimpleTypeCaster numericTypeCaster)
        {
            this.caster = numericTypeCaster;
        }

        public Object compute(Object input)
        {
            return caster.cast(input);
        }
    }

}
