package org.apache.slide.projector.processor.sql;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.apache.commons.contract.Context;
import org.apache.commons.contract.Processor;
import org.apache.commons.contract.Result;
import org.apache.commons.contract.constraints.ArrayConstraints;
import org.apache.commons.contract.constraints.NumberConstraints;
import org.apache.commons.contract.constraints.StringConstraints;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.commons.contract.descriptor.ResultDescriptor;
import org.apache.commons.contract.descriptor.ResultEntryDescriptor;
import org.apache.commons.contract.descriptor.StateDescriptor;
import org.apache.commons.contract.i18n.ParameterMessage;
import org.apache.commons.i18n.LocalizedError;
import org.apache.commons.i18n.LocalizedMessage;
import org.apache.slide.projector.constraints.AnyConstraints;
import org.apache.slide.projector.processor.ProcessException;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.Text;

/**
 * @version $Revision: 1.6 $
 */

public class SQLProcessor implements Processor {
    private final static String STATEMENT = "statement";
    private final static String VALUES = "values";
    private final static String RESULT = "result";
    private final static String ROW_COUNT = "row-count";

    private final static ParameterDescriptor[] parameterDescriptors = new ParameterDescriptor[] {
        new ParameterDescriptor("statement", new ParameterMessage("update/statement"), new StringConstraints()),
        new ParameterDescriptor("values", new ParameterMessage("update/values"), new ArrayConstraints(new AnyConstraints()), new Object[0])
    };

    private final static ResultDescriptor[] resultDescriptors = new ResultDescriptor[] {
            new ResultDescriptor(StateDescriptor.OK_DESCRIPTOR,
            new ResultEntryDescriptor[] {
                new ResultEntryDescriptor(ROW_COUNT, new LocalizedMessage("sql/row-count"), NumberConstraints.UNCONSTRAINED),
                new ResultEntryDescriptor(RESULT, new LocalizedMessage("sql/result"), ArrayConstraints.UNCONSTRAINED)
            })
    };

    public Result process(Map parameter, Context context) throws Exception {
        String statemenet = parameter.get(STATEMENT).toString();
        Object []values = (Object [])parameter.get(VALUES);
        javax.naming.Context ctx = new InitialContext();

        if ( ctx == null ) throw new ProcessException(new LocalizedError("noInitialContextAvailable"));

        DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/TestDB");
        Result result = new Result(StateDescriptor.OK);
        if (ds != null) {
            ResultSet resultSet = null;
            PreparedStatement preparedStatement = null;
            Connection conn = null;
            try {
                conn = ds.getConnection();
                if(conn != null)  {
                    preparedStatement = conn.prepareStatement(statemenet);
                    for ( int i = 0; i < values.length; i++ ) {
                        // FIXME: We need a mapping for every sql type that should be supported
                        if ( values[i] instanceof Text ) {
                            preparedStatement.setString(i+1, values[i].toString());
                        } else if ( values[i] instanceof Number ) {
                            preparedStatement.setInt(i+1, ((Number)values[i]).intValue());
                        } else if ( values[i] instanceof Streamable ) {
                            preparedStatement.setBinaryStream(i+1, ((Streamable)values[i]).getInputStream(), ((Streamable)values[i]).getContentLength());
                        }
                    }
                    if ( preparedStatement.execute() ) {
                        resultSet = preparedStatement.getResultSet();
                        List resultSetResources = new ArrayList();
                        ResultSetMetaData metaData = resultSet.getMetaData();
                        while ( resultSet.next() ) {
                            Map rowMap = new HashMap();
                            int columnCount = metaData.getColumnCount();
                            for ( int i = 0; i < columnCount; i++ ) {
                                String key = metaData.getColumnLabel(i+1);
                                Object object = resultSet.getObject(i+1);
                                if ( object instanceof String ) {
                                    rowMap.put(key, new String((String)object));
                                } else if ( object instanceof Integer ) {
                                    rowMap.put(key, (Integer)object);
                                }
                            }
                            resultSetResources.add(rowMap);
                        }
                        result.addResultEntry(RESULT, resultSetResources);
                    } else {
                        result.addResultEntry(ROW_COUNT, new Integer(preparedStatement.getUpdateCount()));
                    }
                    conn.close();
                }
            } catch (SQLException e) {
                throw new ProcessException(new LocalizedError("sqlException"), e);
            } finally {
                // Always make sure result sets and statements are closed,
                // and the connection is returned to the pool
                if (resultSet != null) {
                    try { resultSet.close(); } catch (SQLException e) { ; }
                    resultSet = null;
                }
                if (preparedStatement != null) {
                    try { preparedStatement.close(); } catch (SQLException e) { ; }
                    preparedStatement = null;
                }
                if (conn != null) {
                    try { conn.close(); } catch (SQLException e) { ; }
                    conn = null;
                }
            }
        }
        return result;
    }

    public ParameterDescriptor[] getParameterDescriptors() {
        return parameterDescriptors;
    }

    public ResultDescriptor[] getResultDescriptors() {
        return resultDescriptors;
    }
}
