/*
 * 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.commons.dbcp2;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;

/**
 * A dummy {@link Statement}, for testing purposes.
 */
public class TesterStatement extends AbandonedTrace implements Statement {

    protected final Connection _connection;
    protected boolean _open = true;
    protected final long _rowsUpdated = 1;
    protected final boolean _executeResponse = true;
    protected int _maxFieldSize = 1024;
    protected long _maxRows = 1024;
    protected boolean _escapeProcessing;
    protected int _queryTimeout = 1000;
    protected String _cursorName;
    protected int _fetchDirection = 1;
    protected int _fetchSize = 1;
    protected int _resultSetConcurrency = 1;
    protected int _resultSetType = 1;
    private int _resultSetHoldability = 1;
    protected ResultSet _resultSet;
    protected boolean _sqlExceptionOnClose;

    public TesterStatement(final Connection conn) {
        _connection = conn;
    }

    public TesterStatement(final Connection conn, final int resultSetType, final int resultSetConcurrency) {
        _connection = conn;
        _resultSetType = resultSetType;
        _resultSetConcurrency = resultSetConcurrency;
    }

    public TesterStatement(final Connection conn, final int resultSetType, final int resultSetConcurrency,
            final int resultSetHoldability) {
        _connection = conn;
        _resultSetType = resultSetType;
        _resultSetConcurrency = resultSetConcurrency;
        _resultSetHoldability = resultSetHoldability;
    }

    @Override
    public void addBatch(final String sql) throws SQLException {
        checkOpen();
    }

    @Override
    public void cancel() throws SQLException {
        checkOpen();
    }

    protected void checkOpen() throws SQLException {
        if (!_open) {
            throw new SQLException("Connection is closed.");
        }
    }

    @Override
    public void clearBatch() throws SQLException {
        checkOpen();
    }

    @Override
    public void clearWarnings() throws SQLException {
        checkOpen();
    }

    @Override
    public void close() throws SQLException {
        if (_sqlExceptionOnClose) {
            throw new SQLException("TestSQLExceptionOnClose");
        }

        // calling close twice has no effect
        if (!_open) {
            return;
        }

        _open = false;
        if (_resultSet != null) {
            _resultSet.close();
            _resultSet = null;
        }
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public boolean execute(final String sql) throws SQLException {
        checkOpen();
        if ("invalid".equals(sql)) {
            throw new SQLException("invalid query");
        }
        return _executeResponse;
    }

    @Override
    public boolean execute(final String sql, final int autoGeneratedKeys)
        throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public boolean execute(final String sql, final int[] columnIndexes)
        throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public boolean execute(final String sql, final String[] columnNames)
        throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public int[] executeBatch() throws SQLException {
        checkOpen();
        return new int[0];
    }

    @Override
    public long[] executeLargeBatch() throws SQLException {
        checkOpen();
        return new long[0];
    }

    @Override
    public long executeLargeUpdate(final String sql) throws SQLException {
        checkOpen();
        return _rowsUpdated;
    }

    @Override
    public long executeLargeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public long executeLargeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public long executeLargeUpdate(final String sql, final String[] columnNames) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public ResultSet executeQuery(final String sql) throws SQLException {
        checkOpen();
        if ("null".equals(sql)) {
            return null;
        }
        if ("invalid".equals(sql)) {
            throw new SQLException("invalid query");
        }
        if ("broken".equals(sql)) {
            throw new SQLException("broken connection");
        }
        if ("select username".equals(sql)) {
            final String userName = ((TesterConnection) _connection).getUserName();
            final Object[][] data = { { userName } };
            return new TesterResultSet(this, data);
        }
        // Simulate timeout if queryTimout is set to less than 5 seconds
        if (_queryTimeout > 0 && _queryTimeout < 5) {
            throw new SQLException("query timeout");
        }
        return new TesterResultSet(this);
    }

    @Override
    public int executeUpdate(final String sql) throws SQLException {
        checkOpen();
        return (int) _rowsUpdated;
    }

    @Override
    public int executeUpdate(final String sql, final int autoGeneratedKeys) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public int executeUpdate(final String sql, final String[] columnNames) throws SQLException {
        throw new SQLException("Not implemented.");
    }

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

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

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

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return new TesterResultSet(this);
    }

    @Override
    public long getLargeMaxRows() throws SQLException {
        checkOpen();
        return _maxRows;
    }

    @Override
    public long getLargeUpdateCount() throws SQLException {
        checkOpen();
        return _rowsUpdated;
    }

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

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

    @Override
    public boolean getMoreResults() throws SQLException {
        checkOpen();
        return false;
    }

    @Override
    public boolean getMoreResults(final int current) throws SQLException {
        throw new SQLException("Not implemented.");
    }

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

    @Override
    public ResultSet getResultSet() throws SQLException {
        checkOpen();
        if (_resultSet == null) {
            _resultSet = new TesterResultSet(this);
        }
        return _resultSet;
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        checkOpen();
        return _resultSetConcurrency;
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        checkOpen();
        return _resultSetHoldability;
    }

    @Override
    public int getResultSetType() throws SQLException {
        checkOpen();
        return _resultSetType;
    }

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


    @Override
    public SQLWarning getWarnings() throws SQLException {
        checkOpen();
        return null;
    }

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

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public boolean isPoolable() throws SQLException {
        throw new SQLException("Not implemented.");
    }

    public boolean isSqlExceptionOnClose() {
        return _sqlExceptionOnClose;
    }

    @Override
    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public void setCursorName(final String name) throws SQLException {
        checkOpen();
        _cursorName = name;
    }

    @Override
    public void setEscapeProcessing(final boolean enable) throws SQLException {
        checkOpen();
        _escapeProcessing = enable;
    }

    @Override
    public void setFetchDirection(final int direction) throws SQLException {
        checkOpen();
        _fetchDirection = direction;
    }

    @Override
    public void setFetchSize(final int rows) throws SQLException {
        checkOpen();
        _fetchSize = rows;
    }

    @Override
    public void setLargeMaxRows(final long max) throws SQLException {
        checkOpen();
        _maxRows = max;
    }

    @Override
    public void setMaxFieldSize(final int max) throws SQLException {
        checkOpen();
        _maxFieldSize = max;
    }

    @Override
    public void setMaxRows(final int max) throws SQLException {
        checkOpen();
        _maxRows = max;
    }

    @Override
    public void setPoolable(final boolean poolable) throws SQLException {
        throw new SQLException("Not implemented.");
    }

    @Override
    public void setQueryTimeout(final int seconds) throws SQLException {
        checkOpen();
        _queryTimeout = seconds;
    }

    public void setSqlExceptionOnClose(final boolean _sqlExceptionOnClose) {
        this._sqlExceptionOnClose = _sqlExceptionOnClose;
    }

    @Override
    public <T> T unwrap(final Class<T> iface) throws SQLException {
        throw new SQLException("Not implemented.");
    }
}
