/*
 *
 * Derby - Class SURBaseTest
 *
 * 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.jdbcapi;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.JDBC;

import junit.framework.*;
import java.sql.*;

/**
 * Base class for testing Scrollable Updatable ResultSets. 
 * The setUp() provides a Connection to the database.
 * 
 * Tests of this class needs to be decorated by a DBSetup
 * and SURDataModelSetup.
 * 
 */
abstract public class SURBaseTest extends BaseJDBCTestCase {
    
    /** Creates a new instance of SURBaseTest */
    public SURBaseTest(String name) {
        super(name);
        recordCount = SURDataModelSetup.recordCount;  
    }

    /** Creates a new instance of SURBaseTest*/
    public SURBaseTest(String name, int records) {
        super(name);
        recordCount = records;  
    }
    
    /**
     * Override a connection's  default state to ensure it
     * is always in autocommit false and repeatable
     * read as a starting point.
     */
    protected void initializeConnection(Connection conn) throws SQLException {
        conn.setAutoCommit(false);
        conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);   
    }
    
    /**
     * Verify the data of a tuple in the ResultSet, based on the data 
     * that were inserted.
     */
    protected void verifyTuple(ResultSet rs) throws SQLException {
        int id = rs.getInt(1);
        int a = rs.getInt(2);
        int b = rs.getInt(3);
        int sum = a + id + 17;
        println("Reading tuple:(" + id + "," + a  + "," + b + ",'" + 
                rs.getString(4) + "', '"+rs.getString(5)+"'");
        assertEquals("Expecting b==id+a+17", sum, b);
    }
    
    /**
     * Update the current tuple in the ResultSet using updateXXX() and 
     * updateRow()
     */
    protected void updateTuple(ResultSet rs) throws SQLException {
        int id = rs.getInt(1);
        int a = rs.getInt(2);
        int b = rs.getInt(3);        
        int newA = a*2 +id + 37;
        int newB = newA + id + 17;
        println("Updating record (" + id + "," + newA + "," + newB + ")");
        rs.updateInt(2, newA);
        rs.updateInt(3, newB); 
        rs.updateRow();
    }
    
    /**
     * Update the current tuple in the ResultSet using positioned update
     */
    protected void updateTuplePositioned(ResultSet rs) throws SQLException {
        int id = rs.getInt(1);
        int a = rs.getInt(2);
        int b = rs.getInt(3);        
        int newA = a*2 +id + 37;
        int newB = newA + id + 17;
        PreparedStatement ps = 
            prepareStatement("update T1 set a=?,b=? where current of " +
                             rs.getCursorName());
        ps.setInt(1, newA);
        ps.setInt(2, newB);
        assertEquals("Expected one tuple to be updated", 1, ps.executeUpdate());
        ps.close();
    }
    
    /**
     * Scroll forward to the end of the ResultSet, and verify tuples while 
     * scrolling. 
     */
    protected void scrollForward(ResultSet rs) throws SQLException
    {        
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isBeforeFirst();
        int nRecords = 0; 
        while (rs.next()) {
            nRecords++;
            verifyTuple(rs);
        }
        if (!ignoreCount) {
            assertEquals("Record Count", recordCount, nRecords);
        }
    }
    
    /**
     * Scroll backward to the beginning of the ResultSet, and verify tuples 
     * while scrolling.
     */
    protected void scrollBackward(ResultSet rs) throws SQLException
    {
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isAfterLast();
        
        int nRecords = 0; 
        while (rs.previous()) {
            nRecords++;
            verifyTuple(rs);
        }
        if (!ignoreCount) {
        	assertEquals("Record Count", recordCount, nRecords);
        }
    }
    
    /**
     * Scroll forward and update the tuples using updateXXX() and updateRow()
     */
    protected void scrollForwardAndUpdate(ResultSet rs) throws SQLException
    {
        int nRecords = 0; 
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isBeforeFirst();
        
        while (rs.next()) {
            nRecords++;
            verifyTuple(rs);
            updateTuple(rs);
        }
        if (!ignoreCount) {
            assertEquals("Record Count", recordCount, nRecords);
        }
        assertNotNull("rs.getCursorName()", rs.getCursorName());
    }
    
    /**
     * Scroll forward and do positioned updates.
     */
    protected void scrollForwardAndUpdatePositioned(ResultSet rs) 
        throws SQLException
    {
        int nRecords = 0; 
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isBeforeFirst();
        while (rs.next()) {
            nRecords++;
            verifyTuple(rs);
            updateTuplePositioned(rs);
        }
        if (!ignoreCount) {
        	assertEquals("Record Count", recordCount, nRecords);
        }
        assertNotNull("rs.getCursorName()", rs.getCursorName());
    }
    
    /**
     * Scroll backward and update the records using updateXXX() and updateRow()
     */
    protected void scrollBackwardAndUpdate(ResultSet rs) throws SQLException
    {
        int nRecords = 0; 
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isAfterLast();
        while (rs.previous()) {
            nRecords++;
            verifyTuple(rs);
            updateTuple(rs);
        }
        if (!ignoreCount) {
        	assertEquals("Record Count", recordCount, nRecords);
        }
        assertNotNull("rs.getCursorName()", rs.getCursorName());
    }
    
    /**
     * Scroll backward and update the records using positioned updates.
     */
    protected void scrollBackwardAndUpdatePositioned(ResultSet rs) 
        throws SQLException
    {
        int nRecords = 0; 
        boolean ignoreCount = rs.getType()==ResultSet.TYPE_FORWARD_ONLY 
            || !rs.isAfterLast();
        while (rs.previous()) {
            nRecords++;
            verifyTuple(rs);
            updateTuplePositioned(rs);
        }
        if (!ignoreCount) {
        	assertEquals("Record Count", recordCount, nRecords);
        }
        assertNotNull("rs.getCursorName()", rs.getCursorName());
    }
    
    /**
     * Assert that update of ResultSet fails with a SQLException
     * due to read-only ResultSet.
     */
    protected void assertFailOnUpdate(ResultSet rs) 
        throws SQLException
    {
        boolean failedCorrect = false;
        try {
            updateTuple(rs);
        } catch (SQLException e) {
            failedCorrect = true;
            assertEquals("Unexpected SQL state", 
                         RESULTSET_NOT_UPDATABLE_SQL_STATE, 
                         e.getSQLState());
            
        }
        assertTrue("Expected cursor to fail on update, since it is read only", 
                   failedCorrect);
    }
    
    /**
     * Assert that a warning was received
     */
    protected void assertWarning(SQLWarning warn, String sqlState) 
        throws SQLException
    {
        if (warn!=null || usingEmbedded()) {
            assertEquals("Unexpected SQL state", 
                         sqlState,
                         warn.getSQLState());
        } else {
            println("Expected warning with SQLState = '" + sqlState +
                    "', however warning not propagated to client driver");
        }
    }
    
    final int recordCount;
    
       
    /**
     * Error codes and SQL state
     */
    final static String FOR_UPDATE_NOT_PERMITTED_SQL_STATE = "42Y90";
    final static String CURSOR_NOT_UPDATABLE_SQL_STATE = "42X23";
    final static String RESULTSET_NOT_UPDATABLE_SQL_STATE = "XJ083";
    final static String LOCK_TIMEOUT_SQL_STATE = "40XL1";
    final static String LOCK_TIMEOUT_EXPRESSION_SQL_STATE = "38000";
    final static String INVALID_CURSOR_STATE_NO_CURRENT_ROW = "24000";
    final static String CURSOR_OPERATION_CONFLICT = "01001";
    final static String QUERY_NOT_QUALIFIED_FOR_UPDATABLE_RESULTSET = "01J06";
    final static String CURSOR_NOT_POSITIONED_ON_INSERT_ROW = "XJ086";
}
