/*
 * 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.beanutils2.sql;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Map;

/**
 * <p>
 * Mock object that implements enough of {@code java.sql.ResultSet} to exercise the {@link ResultSetDynaClass} functionality.
 * </p>
 *
 */

public class TestResultSet implements InvocationHandler {

    /**
     * Factory method for creating {@link ResultSet} proxies.
     *
     * @return A result set proxy
     */
    public static ResultSet createProxy() {
        return TestResultSet.createProxy(new TestResultSet());
    }

    /**
     * Factory method for creating {@link ResultSet} proxies.
     *
     * @param invocationHandler Invocation Handler
     * @return A result set proxy
     */
    public static ResultSet createProxy(final InvocationHandler invocationHandler) {
        final ClassLoader classLoader = ResultSet.class.getClassLoader();
        final Class<?>[] interfaces = new Class[] { ResultSet.class };
        return (ResultSet) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }

    /**
     * Current row number (0 means "before the first one").
     */
    protected int row = 0;

    /**
     * The constant (per run) value used to initialize date/time/timestamp.
     */
    protected long timestampMillis = System.currentTimeMillis();

    /**
     * Meta data for the result set.
     */
    protected ResultSetMetaData resultSetMetaData;

    /**
     * Create a proxy ResultSet.
     */
    public TestResultSet() {
        this(TestResultSetMetaData.createProxy());
    }

    /**
     * Create a proxy ResultSet with the specified meta data.
     *
     * @param resultSetMetaData The result set meta data
     */
    public TestResultSet(final ResultSetMetaData resultSetMetaData) {
        this.resultSetMetaData = resultSetMetaData;
    }

    public boolean absolute(final int row) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void afterLast() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void beforeFirst() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void cancelRowUpdates() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void clearWarnings() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void close() throws SQLException {
        // No action required
    }

    private String columnName(final Object arg) throws SQLException {
        if (arg instanceof Integer) {
            return resultSetMetaData.getColumnName(((Integer) arg).intValue());
        }
        return (String) arg;
    }

    public void deleteRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int findColumn(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean first() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Array getArray(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Array getArray(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public InputStream getAsciiStream(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public InputStream getAsciiStream(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public BigDecimal getBigDecimal(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    /** @deprecated */
    @Deprecated
    public BigDecimal getBigDecimal(final int columnIndex, final int scale) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public BigDecimal getBigDecimal(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    /** @deprecated */
    @Deprecated
    public BigDecimal getBigDecimal(final String columnName, final int scale) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public InputStream getBinaryStream(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public InputStream getBinaryStream(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Blob getBlob(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Blob getBlob(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean getBoolean(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean getBoolean(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public byte getByte(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public byte getByte(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public byte[] getBytes(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public byte[] getBytes(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Reader getCharacterStream(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Reader getCharacterStream(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Clob getClob(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Clob getClob(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getConcurrency() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public String getCursorName() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Date getDate(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Date getDate(final int columnIndex, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Date getDate(final String columnName) throws SQLException {
        return new Date(timestampMillis);
    }

    public Date getDate(final String columnName, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public double getDouble(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public double getDouble(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getFetchDirection() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getFetchSize() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public float getFloat(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public float getFloat(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getInt(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getInt(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public long getLong(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public long getLong(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public ResultSetMetaData getMetaData() throws SQLException {
        return resultSetMetaData;
    }

    public Object getObject(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Object getObject(final int columnIndex, final Map<?, ?> map) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Object getObject(final String columnName) throws SQLException {
        if (row > 5) {
            throw new SQLException("No current row");
        }
        if ("bigDecimalProperty".equals(columnName)) {
            return new BigDecimal(123.45);
        }
        if ("booleanProperty".equals(columnName)) {
            if (row % 2 == 0) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        }
        if ("byteProperty".equals(columnName)) {
            return Byte.valueOf((byte) row);
        }
        if ("dateProperty".equals(columnName)) {
            return new Date(timestampMillis);
        }
        if ("doubleProperty".equals(columnName)) {
            return Double.valueOf(321.0);
        }
        if ("floatProperty".equals(columnName)) {
            return Float.valueOf((float) 123.0);
        }
        if ("intProperty".equals(columnName)) {
            return Integer.valueOf(100 + row);
        }
        if ("longProperty".equals(columnName)) {
            return Long.valueOf(200 + row);
        }
        if ("nullProperty".equals(columnName)) {
            return null;
        }
        if ("shortProperty".equals(columnName)) {
            return Short.valueOf((short) (300 + row));
        }
        if ("stringProperty".equals(columnName)) {
            return "This is a string";
        }
        if ("timeProperty".equals(columnName)) {
            return new Time(timestampMillis);
        }
        if ("timestampProperty".equals(columnName)) {
            return new Timestamp(timestampMillis);
        }
        throw new SQLException("Unknown column name " + columnName);
    }

    public Object getObject(final String columnName, final Map<?, ?> map) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Ref getRef(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Ref getRef(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public short getShort(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public short getShort(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Statement getStatement() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public String getString(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public String getString(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Time getTime(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Time getTime(final int columnIndex, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Time getTime(final String columnName) throws SQLException {
        return new Time(timestampMillis);
    }

    public Time getTime(final String columnName, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Timestamp getTimestamp(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Timestamp getTimestamp(final int columnIndex, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public Timestamp getTimestamp(final String columnName) throws SQLException {
        return new Timestamp(timestampMillis);
    }

    public Timestamp getTimestamp(final String columnName, final Calendar cal) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public int getType() throws SQLException {
        throw new UnsupportedOperationException();
    }

    /** @deprecated */
    @Deprecated
    public InputStream getUnicodeStream(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    /** @deprecated */
    @Deprecated
    public InputStream getUnicodeStream(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public URL getURL(final int columnIndex) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public URL getURL(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public SQLWarning getWarnings() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void insertRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    /**
     * Handles method invocation on the ResultSet proxy.
     *
     * @param proxy  The proxy ResultSet object
     * @param method the method being invoked
     * @param args   The method arguments
     * @return The result of invoking the method.
     * @throws Throwable if an error occurs.
     */
    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final String methodName = method.getName();
        if ("close".equals(methodName)) {
            return null;
        }
        if ("getMetaData".equals(methodName)) {
            return getMetaData();
        }
        if ("getObject".equals(methodName)) {
            return getObject(columnName(args[0]));
        }
        if ("getDate".equals(methodName)) {
            return getDate(columnName(args[0]));
        }
        if ("getTime".equals(methodName)) {
            return getTime(columnName(args[0]));
        }
        if ("getTimestamp".equals(methodName)) {
            return getTimestamp(columnName(args[0]));
        }
        if ("next".equals(methodName)) {
            return next() ? Boolean.TRUE : Boolean.FALSE;
        }
        if ("updateObject".equals(methodName)) {
            updateObject((String) args[0], args[1]);
            return null;
        }

        throw new UnsupportedOperationException(methodName + " not implemented");
    }

    public boolean isAfterLast() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean isBeforeFirst() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean isFirst() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean isLast() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean last() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void moveToCurrentRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void moveToInsertRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean next() throws SQLException {
        if (row++ < 5) {
            return true;
        }
        return false;
    }

    public boolean previous() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void refreshRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean relative(final int rows) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean rowDeleted() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean rowInserted() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean rowUpdated() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setFetchDirection(final int direction) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void setFetchSize(final int size) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateArray(final int columnPosition, final Array x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateArray(final String columnName, final Array x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateAsciiStream(final int columnPosition, final InputStream x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateAsciiStream(final String columnName, final InputStream x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBigDecimal(final int columnPosition, final BigDecimal x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBigDecimal(final String columnName, final BigDecimal x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBinaryStream(final int columnPosition, final InputStream x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBinaryStream(final String columnName, final InputStream x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBlob(final int columnPosition, final Blob x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBlob(final String columnName, final Blob x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBoolean(final int columnPosition, final boolean x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBoolean(final String columnName, final boolean x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateByte(final int columnPosition, final byte x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateByte(final String columnName, final byte x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBytes(final int columnPosition, final byte[] x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateBytes(final String columnName, final byte[] x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateCharacterStream(final int columnPosition, final Reader x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateCharacterStream(final String columnName, final Reader x, final int len) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateClob(final int columnPosition, final Clob x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateClob(final String columnName, final Clob x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateDate(final int columnPosition, final Date x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateDate(final String columnName, final Date x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateDouble(final int columnPosition, final double x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateDouble(final String columnName, final double x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateFloat(final int columnPosition, final float x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateFloat(final String columnName, final float x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateInt(final int columnPosition, final int x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateInt(final String columnName, final int x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateLong(final int columnPosition, final long x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateLong(final String columnName, final long x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateNull(final int columnPosition) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateNull(final String columnName) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateObject(final int columnPosition, final Object x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateObject(final int columnPosition, final Object x, final int scale) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateObject(final String columnName, final Object x) throws SQLException {
        if (row > 5) {
            throw new SQLException("No current row");
        }
        // FIXME - updateObject()
    }

    public void updateObject(final String columnName, final Object x, final int scale) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateRef(final int columnPosition, final Ref x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateRef(final String columnName, final Ref x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateRow() throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateShort(final int columnPosition, final short x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateShort(final String columnName, final short x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateString(final int columnPosition, final String x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateString(final String columnName, final String x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateTime(final int columnPosition, final Time x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateTime(final String columnName, final Time x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateTimestamp(final int columnPosition, final Timestamp x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public void updateTimestamp(final String columnName, final Timestamp x) throws SQLException {
        throw new UnsupportedOperationException();
    }

    public boolean wasNull() throws SQLException {
        throw new UnsupportedOperationException();
    }

}
