/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.builder.sql;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Message;
import org.apache.camel.Predicate;
import org.apache.camel.RuntimeExpressionException;
import org.apache.camel.util.ObjectHelper;

import org.josql.Query;
import org.josql.QueryExecutionException;
import org.josql.QueryParseException;

/**
 * A builder of SQL {@link org.apache.camel.Expression} and
 * {@link org.apache.camel.Predicate} implementations
 * 
 * @version 
 */
public class SqlBuilder implements Expression, Predicate {

    private Query query;
    private Map<String, Object> variables = new HashMap<String, Object>();

    public SqlBuilder(Query query) {
        this.query = query;
    }

    public <T> T evaluate(Exchange exchange, Class<T> type) {
        Object result = evaluateQuery(exchange);
        return exchange.getContext().getTypeConverter().convertTo(type, result);
    }

    public boolean matches(Exchange exchange) {
        List<?> list = evaluateQuery(exchange);
        return matches(exchange, list);
    }

    public void assertMatches(String text, Exchange exchange) throws AssertionError {
        List<?> list = evaluateQuery(exchange);
        if (!matches(exchange, list)) {
            throw new AssertionError(this + " failed on " + exchange + " as found " + list);
        }
    }

    // Builder API
    // -----------------------------------------------------------------------

    /**
     * Creates a new builder for the given SQL query string
     * 
     * @param sql the SQL query to perform
     * @return a new builder
     * @throws QueryParseException if there is an issue with the SQL
     */
    public static SqlBuilder sql(String sql) throws QueryParseException {
        Query q = new Query();
        q.parse(sql);
        return new SqlBuilder(q);
    }

    /**
     * Adds the variable value to be used by the SQL query
     */
    public SqlBuilder variable(String name, Object value) {
        getVariables().put(name, value);
        return this;
    }

    // Properties
    // -----------------------------------------------------------------------
    public Map<String, Object> getVariables() {
        return variables;
    }

    public void setVariables(Map<String, Object> properties) {
        this.variables = properties;
    }

    // Implementation methods
    // -----------------------------------------------------------------------
    protected boolean matches(Exchange exchange, List<?> list) {
        return ObjectHelper.matches(list);
    }

    protected List<?> evaluateQuery(Exchange exchange) {
        configureQuery(exchange);
        Message in = exchange.getIn();
        List<?> list = in.getBody(List.class);
        if (list == null) {
            list = Collections.singletonList(in.getBody());
        }
        try {
            return query.execute(list).getResults();
        } catch (QueryExecutionException e) {
            throw new RuntimeExpressionException(e);
        }
    }

    protected void configureQuery(Exchange exchange) {
        // lets pass in the headers as variables that the SQL can use
        addVariables(exchange.getProperties());
        addVariables(exchange.getIn().getHeaders());
        addVariables(getVariables());

        query.setVariable("exchange", exchange);
        query.setVariable("in", exchange.getIn());
        query.setVariable("out", exchange.getOut());
    }

    protected void addVariables(Map<String, Object> map) {
        Set<Map.Entry<String, Object>> propertyEntries = map.entrySet();
        for (Map.Entry<String, Object> entry : propertyEntries) {
            query.setVariable(entry.getKey(), entry.getValue());
        }
    }
}
