/*
 
   Derby - Class org.apache.derbyTesting.functionTests.tests.jdbc4.Wrapper41Test
 
   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.derbyTesting.functionTests.tests.jdbc4;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.CallableStatement;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;

import org.apache.derbyTesting.junit.BaseJDBCTestCase;

/**
 * <p>
 * Machinery shared by the JDBC 4.1 tests for ResultSets and CallableStatements.
 * </p>
 */
public  class   Wrapper41Test   extends BaseJDBCTestCase
{
    ///////////////////////////////////////////////////////////////////////
    //
    // CONSTANTS
    //
    ///////////////////////////////////////////////////////////////////////

    private static  final   String  UNSUPPORTED_COERCION = "22005";
    private static  final   String  BAD_FORMAT = "22018";
    private static  final   String  BAD_DATETIME = "22007";

    private static  final   String  VARIABLE_STRING = "XXXXX";

    public  static  final   byte[]  BINARY_VALUE = new byte[] { (byte) 0xde };

    static final long TIME_VALUE = 83342000L;
    static final long TIMESTAMP_VALUE = -229527385766L;


    ///////////////////////////////////////////////////////////////////////
    //
    // STATE
    //
    ///////////////////////////////////////////////////////////////////////

    private Class       _byteArrayClass;
    private boolean _rowOfNulls;

    ///////////////////////////////////////////////////////////////////////
    //
    // CONSTRUCTOR
    //
    ///////////////////////////////////////////////////////////////////////

    /**
     * Create test with given name.
     *
     * @param name name of the test.
     */
    public Wrapper41Test( String name ) { super( name ); }

    ///////////////////////////////////////////////////////////////////////
    //
    // BEHAVIOR
    //
    ///////////////////////////////////////////////////////////////////////

    protected void examineJDBC4_1extensions( Wrapper41 wrapper, boolean rowOfNulls ) throws Exception
    {
        println( "Vetting a " + wrapper.getWrappedObject().getClass().getName() + ". rowOfNulls = " + rowOfNulls );

        _byteArrayClass = Class.forName( "[B" );
        _rowOfNulls = rowOfNulls;

        vetWrappedNull( wrapper );
        vetWrappedInteger( wrapper, 1, "BIGINTCOL" );
        vetWrappedBlob( wrapper );
        vetWrappedBoolean( wrapper );
        vetWrappedString( wrapper, 4, "CHARCOL" );
        vetWrappedBinary( wrapper, 5, "CHARFORBITDATACOL" );
        vetWrappedClob( wrapper );
        vetWrappedDate( wrapper );
        vetWrappedFloatingPoint( wrapper, 8, "DOUBLECOL" );
        vetWrappedFloatingPoint( wrapper, 9, "FLOATCOL" );
        vetWrappedInteger( wrapper, 10, "INTCOL" );
        vetWrappedString( wrapper, 11, "LONGVARCHARCOL" );
        vetWrappedBinary( wrapper, 12, "LONGVARCHARFORBITDATACOL" );
        vetWrappedInteger( wrapper, 13, "NUMERICCOL" );
        vetWrappedFloatingPoint( wrapper, 14, "REALCOL" );
        vetWrappedInteger( wrapper, 15, "SMALLINTCOL" );
        vetWrappedTime( wrapper );
        vetWrappedTimestamp( wrapper );
        vetWrappedString( wrapper, 18, "VARCHARCOL" );
        vetWrappedBinary( wrapper, 19, "VARCHARFORBITDATACOL" );
    }
    private void    vetWrappedNull( Wrapper41 wrapper ) throws Exception
    {
            try {
                wrapper.getObject( 1, (Class<?>) null );
                fail( "Did not expect to get a result for a null class type." );
            }
            catch (SQLException e)
            {
                assertSQLState( "Null type", UNSUPPORTED_COERCION, e );
            }

            // String overloads not implemented for CallableStatements
            if ( wrapper.getWrappedObject() instanceof CallableStatement ) { return; }
            
            try {
                wrapper.getObject( "BIGINTCOL", (Class<?>) null );
                fail( "Did not expect to get a result for a null class type." );
            }
            catch (SQLException e)
            {
                assertSQLState( "Null type", UNSUPPORTED_COERCION, e );
            }
    }
    private void    vetWrappedInteger( Wrapper41 wrapper, int colID, String colName ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "1",
             new Class[] { String.class, BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class, Number.class, Object.class }
             );
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "1.0",
             new Class[] { Float.class, Double.class }
             );
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "true",
             new Class[] { Boolean.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             colID,
             colName,
             new Class[] { Date.class, Time.class, Timestamp.class, Blob.class, Clob.class, _byteArrayClass, getClass() }
             );
    }
    private void    vetWrappedBlob( Wrapper41 wrapper ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             2,
             "BLOBCOL",
             _rowOfNulls ? null : BINARY_VALUE,
             new Class[] { Blob.class, Object.class, _byteArrayClass, String.class,  }
             );
        
        vetNoWrapper
            (
             wrapper,
             2,
             "BLOBCOL",
             new Class[]
             {
                 BigDecimal.class, Boolean.class,
                 Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class,
                 Date.class, Time.class, Timestamp.class
             }
             );

        //
        // We don't try to get a Clob value because we have already gotten a LOB value.
        // Trying to open another LOB stream raises an error. Using a random class type
        // also takes us down that code path, so we don't verify against getClass() either.
        //
    }
    private void    vetWrappedBoolean( Wrapper41 wrapper ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             3,
             "BOOLEANCOL",
             _rowOfNulls ? null : "true",
             new Class[] { String.class, Boolean.class, Object.class }
             );
        vetWrapperOK
            (
             wrapper,
             3,
             "BOOLEANCOL",
             _rowOfNulls ? null : "1",
             new Class[] { BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class }
             );
        vetWrapperOK
            (
             wrapper,
             3,
             "BOOLEANCOL",
             _rowOfNulls ? null : "1.0",
             new Class[] { Float.class, Double.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             3,
             "BOOLEANCOL",
             new Class[] { Date.class, Time.class, Timestamp.class, Blob.class, Clob.class, _byteArrayClass, getClass() }
             );
    }
    private void    vetWrappedString( Wrapper41 wrapper, int colID, String colName ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "a",
             new Class[] { String.class, Object.class }
             );

        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "true",
             new Class[] { Boolean.class }
             );

        vetCoercionError
            (
             wrapper,
             colID,
             colName,
             new Class[]
             {
                 BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
             },
             BAD_FORMAT
             );

        vetCoercionError
            (
             wrapper,
             colID,
             colName,
             new Class[]
             {
                 Date.class, Time.class, Timestamp.class
             },
             BAD_DATETIME
             );

        vetNoWrapper
            (
             wrapper,
             colID,
             colName,
             new Class[]
             {
                 Blob.class, Clob.class, _byteArrayClass, getClass()
             }
             );
    }
    private void    vetWrappedBinary( Wrapper41 wrapper, int colID, String colName ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "de",
             new Class[] { String.class }
             );
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : BINARY_VALUE,
             new Class[] { _byteArrayClass, Object.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             colID,
             colName,
             new Class[]
             {
                 Boolean.class, BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
                 Date.class, Time.class, Timestamp.class, Blob.class, Clob.class, getClass()
             }
             );
    }
    private void    vetWrappedClob( Wrapper41 wrapper ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             6,
             "CLOBCOL",
             _rowOfNulls ? null : "abc",
             new Class[] { String.class, Clob.class, Object.class }
             );

        vetNoWrapper
            (
             wrapper,
             6,
             "CLOBCOL",
             new Class[]
             {
                 Boolean.class,
                 BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
                 Date.class, Time.class, Timestamp.class,
                 _byteArrayClass
             }
             );

        //
        // We don't test getting a BLOB because we are only allowed one attempt
        // to get a LOB from the column. Using a random class type
        // also takes us down that code path, so we don't verify against getClass() either.
        //
    }
    private void    vetWrappedDate( Wrapper41 wrapper ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             7,
             "DATECOL",
             _rowOfNulls ? null : "1994-02-23",
             new Class[] { String.class, Date.class, Object.class }
             );
        vetWrapperOK
            (
             wrapper,
             7,
             "DATECOL",
             _rowOfNulls ? null : "1994-02-23 00:00:00.0",
             new Class[] { Timestamp.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             7,
             "DATECOL",
             new Class[]
             {
                 Boolean.class,
                 BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
                 Time.class,
                 Blob.class, Clob.class, _byteArrayClass, getClass()
             }
             );
    }
    private void    vetWrappedFloatingPoint( Wrapper41 wrapper, int colID, String colName ) throws Exception
    {
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "1.0",
             new Class[] { String.class, Float.class, Double.class, BigDecimal.class, Number.class, Object.class }
             );
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "1",
             new Class[] { Byte.class, Short.class, Integer.class, Long.class }
             );
        vetWrapperOK
            (
             wrapper,
             colID,
             colName,
             _rowOfNulls ? null : "true",
             new Class[] { Boolean.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             colID,
             colName,
             new Class[] { Date.class, Time.class, Timestamp.class, Blob.class, Clob.class, _byteArrayClass, getClass() }
             );
    }
    private void    vetWrappedTime( Wrapper41 wrapper ) throws Exception
    {
        Time expectedTime = new Time(TIME_VALUE);

        vetWrapperOK
            (
             wrapper,
             16,
             "TIMECOL",
             _rowOfNulls ? null : expectedTime.toString(),
             new Class[] { String.class, Time.class, Object.class }
             );
        vetWrapperOK
            (
             wrapper,
             16,
             "TIMECOL",
             _rowOfNulls ? null : timeToTimestamp(expectedTime).toString(),
             new Class[] { Timestamp.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             16,
             "TIMECOL",
             new Class[]
             {
                 Boolean.class,
                 BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
                 Date.class,
                 Blob.class, Clob.class, _byteArrayClass, getClass()
             }
             );
    }
    private void    vetWrappedTimestamp( Wrapper41 wrapper ) throws Exception
    {
        String expectedTimestamp = new Timestamp(TIMESTAMP_VALUE).toString();
        vetWrapperOK
            (
             wrapper,
             17,
             "TIMESTAMPCOL",
             _rowOfNulls ? null : expectedTimestamp,
             new Class[] { String.class, Timestamp.class, Object.class }
             );

        String expectedTime = new Time(TIMESTAMP_VALUE).toString();
        vetWrapperOK
            (
             wrapper,
             17,
             "TIMESTAMPCOL",
             _rowOfNulls ? null : expectedTime,
             new Class[] { Time.class }
             );

        String expectedDate = new Date(TIMESTAMP_VALUE).toString();
        vetWrapperOK
            (
             wrapper,
             17,
             "TIMESTAMPCOL",
             _rowOfNulls ? null : expectedDate,
             new Class[] { Date.class }
             );
        
        vetNoWrapper
            (
             wrapper,
             17,
             "TIMESTAMPCOL",
             new Class[]
             {
                 Boolean.class,
                 BigDecimal.class, Byte.class, Short.class, Integer.class, Long.class,
                 Float.class, Double.class,
                 Blob.class, Clob.class, _byteArrayClass, getClass()
             }
             );
    }

    private void    vetWrapperOK
        ( Wrapper41 wrapper, int colID, String colName, Object expectedValue, Class[] supportedCoercions )
        throws Exception
    {
        int coercionCount = supportedCoercions.length;
        for ( int i = 0; i < coercionCount; i++ )
        {
            Class<?> candidate = supportedCoercions[ i ];
            vetCandidate( candidate, expectedValue, wrapper.getObject( colID, candidate ) );
            
            // you can only retrieve a LOB once
            if ( (candidate == Blob.class) || (candidate == Clob.class) ) { return; }

            // String overloads not implemented for CallableStatement
            if ( !(wrapper.getWrappedObject() instanceof CallableStatement) )
            { vetCandidate( candidate, expectedValue, wrapper.getObject( colName, candidate ) ); }
        }
    }
    private void vetCandidate( Class<?> candidate, Object expectedValue, Object actualValue )
        throws Exception
    {
        if ( actualValue != null ) { assertTrue( candidate.getName(), candidate.isAssignableFrom( actualValue.getClass( ) ) ); }

        if ( expectedValue == null )
        {
            assertNull( actualValue );
            return;
        }

        if ( VARIABLE_STRING.equals( expectedValue ) ) { return; }

        String  actualString;
        if ( actualValue instanceof Blob )
        {
            Blob    blob = (Blob) actualValue;
            vetBytes( (byte[]) expectedValue, blob.getBytes( 1L, (int) blob.length() ) );
            return;
        }
        else if ( actualValue instanceof byte[] )
        {
            vetBytes( (byte[]) expectedValue, (byte[]) actualValue );
            return;
        }
        else if ( actualValue instanceof Clob )
        {
            Clob    clob = (Clob) actualValue;
            actualString = clob.getSubString( 1L, (int) clob.length() );
        }
        else { actualString = actualValue.toString(); }
        
        assertEquals( candidate.getName(), (String) expectedValue, actualString );
    }
    private void    vetBytes( byte[] expected, byte[] actual ) throws Exception
    {
        int count = expected.length;

        assertEquals( count, actual.length );
        for ( int i = 0; i < count; i++ )
        {
            assertEquals( expected[ i ], actual[ i ] );
        }
    }
    private void    vetNoWrapper
        ( Wrapper41 wrapper, int colID, String colName, Class[] unsupportedCoercions )
        throws Exception
    {
        vetCoercionError( wrapper, colID, colName, unsupportedCoercions, UNSUPPORTED_COERCION );
    }
    private void    vetCoercionError
        ( Wrapper41 wrapper, int colID, String colName, Class[] unsupportedCoercions, String expectedSQLState )
        throws Exception
    {
        // null can be coerced to anything
        if ( _rowOfNulls ) { return; }
        
        int coercionCount = unsupportedCoercions.length;
        for ( int i = 0; i < coercionCount; i++ )
        {
            Class<?> candidate = unsupportedCoercions[ i ];

            try {
                wrapper.getObject( colID, candidate );
                fail( "Did not expect to get a " + candidate.getName() );
            }
            catch (SQLException e)
            {
                assertSQLState( candidate.getName(), expectedSQLState, e );
            }

            // you can only retrieve a LOB once
            if ( (candidate == Blob.class) || (candidate == Clob.class) ) { return; }

            // String overloads not implemented for CallableStatements
            if ( wrapper.getWrappedObject() instanceof CallableStatement ) { return; }
            
            try {
                wrapper.getObject( colName, candidate );
                fail( "Did not expect to get a " + candidate.getName() );
            }
            catch (SQLException e)
            {
                assertSQLState( candidate.getName(), expectedSQLState, e );
            }
        }
    }
    
    ///////////////////////////////////////////////////////////////////////
    //
    // MINIONS
    //
    ///////////////////////////////////////////////////////////////////////

    protected PreparedStatement    prepareStatement( Connection conn, String text )
        throws Exception
    {
        println( text );
        
        PreparedStatement   ps = conn.prepareStatement( text );

        return ps;
    }

    protected CallableStatement    prepareCall( Connection conn, String text )
        throws Exception
    {
        println( text );
        
        CallableStatement cs = conn.prepareCall( text );

        return cs;
    }

    /**
     * Convert a Time value to a Timestamp value the same way as when we call
     * getTimestamp() on a TIME column. That is, construct a Timestamp value
     * with the date component set to the current date and the time component
     * set to the specified time of day.
     *
     * @param time the Time value to convert
     * @return a Timestamp value representing the specified time on the
     * current date
     */
    private static Timestamp timeToTimestamp(Time time) {
        // Create a calendar object representing the time value
        Calendar timeCal = Calendar.getInstance();
        timeCal.setTime(time);

        // Create a calendar object for the timestamp, initialized with
        // the current time value
        Calendar tsCal = Calendar.getInstance();

        // Copy all fields, except the date fields, from the time calendar
        // to the timestamp calendar
        int[] timeFields = {
            Calendar.HOUR_OF_DAY,
            Calendar.MINUTE,
            Calendar.SECOND,
            Calendar.MILLISECOND
        };

        for (int field : timeFields) {
            tsCal.set(field, timeCal.get(field));
        }

        // Return a timestamp based on the current date and the specified time
        return new Timestamp(tsCal.getTimeInMillis());
    }

}
