/*
 *  Copyright Beijing 58 Information Technology Co.,Ltd.
 *
 *  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 com.bj58.oceanus.exchange.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import com.bj58.oceanus.core.context.StatementContext;
import com.bj58.oceanus.core.jdbc.StatementCallback;
import com.bj58.oceanus.core.jdbc.result.SimpleMergedResultSet;
import com.bj58.oceanus.core.lifecycle.LifeCycle;
import com.bj58.oceanus.core.shard.AnalyzeResult;
import com.bj58.oceanus.exchange.builder.StatementContextBuilder;
import com.bj58.oceanus.exchange.executors.ExecuteCallback;
import com.bj58.oceanus.exchange.executors.Executor;
import com.bj58.oceanus.exchange.router.Router;
import com.bj58.oceanus.exchange.router.RouterFactory;

/**
 * Statement包装
 * 
 * @author Service Platform Architecture Team ([email protected])
 */
public class StatementWrapper extends AbstractStatement implements Statement ,LifeCycle{

	StatementContextBuilder builder;
	ResultSet resultSet;
	int updateCount;
	StatementCallback callback;
	ConnectionWrapper connection;
	boolean close;
	protected final Set<Statement> statements = new LinkedHashSet<Statement>();
	boolean poolable;
	int fetchSize;
	int fetchDerection;
	String cursorName;
	int queryTimeout;
	boolean escapeProcessing;
	int maxRows;
	int maxFieldSize;

	public StatementWrapper(ConnectionWrapper connection,
			StatementContextBuilder builder) {
		this.builder = builder;
		this.connection = connection;
		StatementContext context = new StatementContext();
		context.setStatementWrapper(this);
		StatementContext.setContext(context);
		close = false;
	}

	@Override
	public ResultSet executeQuery(String sql) throws SQLException {
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		Router router = RouterFactory.createRouter(context);
		@SuppressWarnings("unchecked")
		Executor<ResultSet> executor = (Executor<ResultSet>) router
				.route(context);
		try {
			return executor.execute(context, new ExecuteCallback<ResultSet>() {
				@Override
				public ResultSet execute(Statement statement, String sql)
						throws SQLException {
					statements.add(statement);
					return statement.executeQuery(sql);
				}
			});
		} finally {
			StatementContext.setContext(null);
		}
	}

	protected static int getIntValue(Object result) {
		if (result != null) {
			if (result instanceof Integer[]) {
				Integer[] results = (Integer[]) (result);
				Integer res = 0;
				for (Integer item : results) {
					res += item;
				}
				return res;
			}
			if (result instanceof int[]) {
				int[] results = (int[]) (result);
				int res = 0;
				for (int item : results) {
					res += item;
				}
				return res;
			}
			return (Integer) result;
		}

		return 0;
	}

	@Override
	public int executeUpdate(String sql) throws SQLException {
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		Router router = RouterFactory.createRouter(context);
		@SuppressWarnings("unchecked")
		Executor<Integer> executor = (Executor<Integer>) router.route(context);

		try {
			Object result = executor.execute(context,
					new ExecuteCallback<Integer>() {
						@Override
						public Integer execute(Statement statement, String sql)
								throws SQLException {
							statements.add(statement);
							return statement.executeUpdate(sql);
						}
					});
			return getIntValue(result);

		} finally {
			StatementContext.setContext(null);
		}
	}

	@Override
	public void close() throws SQLException {
		close = true;
		for (Statement stmt : statements) {
			stmt.close();
		}
	}

	

	@Override
	public int getMaxFieldSize() throws SQLException {
		return maxFieldSize;
	}

	@Override
	public void setMaxFieldSize(int max) throws SQLException {
		maxFieldSize = max;

	}

	@Override
	public int getMaxRows() throws SQLException {
		return maxRows;
	}

	@Override
	public void setMaxRows(int max) throws SQLException {
		maxRows = max;
	}

	@Override
	public void setEscapeProcessing(boolean enable) throws SQLException {
		throw new UnsupportedOperationException();

	}

	@Override
	public int getQueryTimeout() throws SQLException {
		return queryTimeout;
	}

	@Override
	public void setQueryTimeout(int seconds) throws SQLException {
		queryTimeout = seconds;
	}

	@Override
	public void cancel() throws SQLException {
		for (Statement stmt : statements) {
			stmt.cancel();
		}

	}

	@Override
	public SQLWarning getWarnings() throws SQLException {
		if (statements.size() > 0) {
			return statements.iterator().next().getWarnings();
		}
		throw new UnsupportedOperationException();
	}

	@Override
	public void clearWarnings() throws SQLException {
		for (Statement stmt : statements) {
			stmt.clearWarnings();
		}

	}

	@Override
	public void setCursorName(String name) throws SQLException {
		cursorName = name;

	}

	@Override
	public boolean execute(final String sql) throws SQLException {
		this.updateCount = 0;
		this.resultSet = null;
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		AnalyzeResult analyzeResult = context.getCurrentBatch()
				.getAnalyzeResult();
		switch (analyzeResult.getStatementType()) {
		case INSERT:
		case UPDATE:
		case DELETE:
			this.updateCount = this.executeUpdate(sql);
			break;
		case SELECT:
			this.resultSet = this.executeQuery(sql);
			break;
		default:
			throw new UnsupportedOperationException();
		}

		return true;
	}

	@Override
	public ResultSet getResultSet() throws SQLException {
		return resultSet;
	}

	@Override
	public int getUpdateCount() throws SQLException {
		return updateCount;
	}

	@Override
	public boolean getMoreResults() throws SQLException {
		for (Statement stmt : statements) {
			if (stmt.getMoreResults()) {
				return true;
			}
		}
		return false;
	}

	@Override
	public void setFetchDirection(int direction) throws SQLException {
		fetchDerection = direction;
	}

	@Override
	public int getFetchDirection() throws SQLException {
		return fetchDerection;
	}

	@Override
	public void setFetchSize(int rows) throws SQLException {
		this.fetchSize = rows;

	}

	@Override
	public int getFetchSize() throws SQLException {
		return fetchSize;
	}

	@Override
	public int getResultSetConcurrency() throws SQLException {
		if (statements.size() > 0) {
			return statements.iterator().next().getResultSetConcurrency();
		}
		throw new UnsupportedOperationException();
	}

	@Override
	public int getResultSetType() throws SQLException {
		if (statements.size() > 0) {
			return statements.iterator().next().getResultSetType();
		}
		throw new UnsupportedOperationException();
	}

	@Override
	public void addBatch(String sql) throws SQLException {
		StatementContext context = StatementContext.getContext();
		context.addBatch(sql);

	}

	@Override
	public void clearBatch() throws SQLException {
		StatementContext context = new StatementContext();
		StatementContext.setContext(context);
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this); 
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public int[] executeBatch() throws SQLException {
		StatementContext context = builder.build(StatementContext.getContext()
				.getCurrentBatch().getSql(), StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this); 
		Router router = RouterFactory.createRouter(context);
		// TODO event && parameters
		Executor executor = router.route(context);
		try {
			return (int[]) executor.execute(context, new ExecuteCallback() {
				@Override
				public Object execute(Statement statement, String sql)
						throws SQLException {
					statements.add(statement);
					if (statement instanceof PreparedStatement) {
						return ((PreparedStatement) statement).executeUpdate();
					}
					return statement.executeUpdate(sql);
				}
			});
		} finally {
			StatementContext.setContext(null);
		}
	}

	@Override
	public Connection getConnection() throws SQLException {
		return connection;
	}

	@Override
	public boolean getMoreResults(int current) throws SQLException {
		for (Statement stmt : statements) {
			if (stmt.getMoreResults(current)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public ResultSet getGeneratedKeys() throws SQLException {
		List<ResultSet> results = new ArrayList<ResultSet>(statements.size());
		for (Statement stmt : statements) {
			results.add(stmt.getGeneratedKeys());
		}
		return new SimpleMergedResultSet(results);
	}

	@SuppressWarnings("unchecked")
	@Override
	public int executeUpdate(final String sql, final int autoGeneratedKeys)
			throws SQLException {
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		Router router = RouterFactory.createRouter(context);
		// TODO event && parameters
		Executor<Integer> executor = (Executor<Integer>) router.route(context);
		try {
			Object result = executor.execute(context,
					new ExecuteCallback<Integer>() {
						@Override
						public Integer execute(Statement statement, String sql)
								throws SQLException {
							statements.add(statement);
							return statement.executeUpdate(sql,
									autoGeneratedKeys);
						}
					});
			return getIntValue(result);
		} finally {
			StatementContext.setContext(null);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public int executeUpdate(final String sql, final int[] columnIndexes)
			throws SQLException {
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		Router router = RouterFactory.createRouter(context);
		// TODO event && parameters
		Executor<Integer> executor = (Executor<Integer>) router.route(context);
		try {
			Object result = executor.execute(context,
					new ExecuteCallback<Integer>() {
						@Override
						public Integer execute(Statement statement, String sql)
								throws SQLException {
							statements.add(statement);
							return statement.executeUpdate(sql, columnIndexes);
						}
					});
			return getIntValue(result);
		} finally {
			StatementContext.setContext(null);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public int executeUpdate(final String sql, final String[] columnNames)
			throws SQLException {
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		Router router = RouterFactory.createRouter(context);
		// TODO event && parameters
		Executor<Integer> executor = (Executor<Integer>) router.route(context);
		try {
			Object result = executor.execute(context,
					new ExecuteCallback<Integer>() {
						@Override
						public Integer execute(Statement statement, String sql)
								throws SQLException {
							statements.add(statement);
							return statement.executeUpdate(sql, columnNames);
						}
					});
			return getIntValue(result);
		} finally {
			StatementContext.setContext(null);
		}
	}

	@Override
	public boolean execute(final String sql, final int autoGeneratedKeys)
			throws SQLException {
		this.updateCount = 0;
		this.resultSet = null;
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		AnalyzeResult analyzeResult = context.getCurrentBatch()
				.getAnalyzeResult();
		switch (analyzeResult.getStatementType()) {
		case INSERT:
		case UPDATE:
		case DELETE:
			this.updateCount = this.executeUpdate(sql, autoGeneratedKeys);
			break;
		case SELECT:
			this.resultSet = this.executeQuery(sql);
			break;
		default:
			throw new UnsupportedOperationException();
		}

		return true;
	}

	@Override
	public boolean execute(final String sql, final int[] columnIndexes)
			throws SQLException {
		this.updateCount = 0;
		this.resultSet = null;
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		AnalyzeResult analyzeResult = context.getCurrentBatch()
				.getAnalyzeResult();
		switch (analyzeResult.getStatementType()) {
		case INSERT:
		case UPDATE:
		case DELETE:
			this.updateCount = this.executeUpdate(sql, columnIndexes);
			break;
		case SELECT:
			this.resultSet = this.executeQuery(sql);
			break;
		default:
			throw new UnsupportedOperationException();
		}

		return true;
	}

	@Override
	public boolean execute(final String sql, final String[] columnNames)
			throws SQLException {
		this.updateCount = 0;
		this.resultSet = null;
		StatementContext context = builder.build(sql,
				StatementContext.getContext());
		context.setStatementCreateCallback(callback);
		context.setStatementWrapper(this);
		AnalyzeResult analyzeResult = context.getCurrentBatch()
				.getAnalyzeResult();
		switch (analyzeResult.getStatementType()) {
		case INSERT:
		case UPDATE:
		case DELETE:
			this.updateCount = this.executeUpdate(sql, columnNames);
			break;
		case SELECT:
			this.resultSet = this.executeQuery(sql);
			break;
		default:
			throw new UnsupportedOperationException();
		}

		return true;
	}

	@Override
	public int getResultSetHoldability() throws SQLException {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean isClosed() throws SQLException {
		return close;
	}

	@Override
	public void setPoolable(boolean poolable) throws SQLException {
		this.poolable = poolable;
	}

	@Override
	public boolean isPoolable() throws SQLException {
		return poolable;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return this.getClass().isAssignableFrom(iface);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T unwrap(Class<T> iface) throws SQLException {
		try {
			return (T) this;
		} catch (Exception e) {
			throw new SQLException(e);
		}
	}

	public StatementCallback getCallback() {
		return callback;
	}

	public void setCallback(StatementCallback callback) {
		this.callback = callback;
	}

	@Override
	public void start() throws SQLException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void destroy() throws SQLException {
		// TODO Auto-generated method stub
		
	}

}