package com.xtremelabs.robolectric.shadows;

import android.database.sqlite.SQLiteCursor;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import org.h2.jdbc.JdbcClob;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

/**
 * Simulates an Android Cursor object, by wrapping a JDBC ResultSet.
 */
@Implements(SQLiteCursor.class)
public class ShadowSQLiteCursor extends ShadowAbstractCursor {

    private ResultSet resultSet;
    
    
    /**
     * Stores the column names so they are retrievable after the resultSet has closed
     */
    private void cacheColumnNames(ResultSet rs) {
    	try {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();    
            columnNameArray = new String[columnCount];
            for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
                String cName = metaData.getColumnName(columnIndex).toLowerCase();
                this.columnNames.put(cName, columnIndex-1);
                this.columnNameArray[columnIndex-1]=cName;
            }
        } catch (SQLException e) {
            throw new RuntimeException("SQL exception in cacheColumnNames", e);
        }
    }
    
   

  
    private Integer getColIndex(String columnName) {
        if (columnName == null) {
            return -1;
        }
        
        Integer i  = this.columnNames.get(columnName.toLowerCase());
        if (i==null) return -1;
        return i;
    }
    
    @Implementation
    public int getColumnIndex(String columnName) {
    	return getColIndex(columnName);
    }

    @Implementation
    public int getColumnIndexOrThrow(String columnName) {
    	Integer columnIndex = getColIndex(columnName);
        if (columnIndex == -1) {
            throw new IllegalArgumentException("Column index does not exist");
        }
        return columnIndex;
    }

    @Implementation
    @Override
    public final boolean moveToFirst() {
        return super.moveToFirst();
    }

    @Implementation
    @Override
    public boolean moveToNext() {
        return super.moveToNext();
    }
    
    @Implementation
    @Override
    public boolean moveToPrevious() {
        return super.moveToPrevious();
    }
    
    @Implementation
    @Override
    public boolean moveToPosition(int pos) {
    	return super.moveToPosition(pos);
    }

    @Implementation
    public byte[] getBlob(int columnIndex) {
    	checkPosition();
        return (byte[]) this.currentRow.get(getColumnNames()[columnIndex]);
    }

    @Implementation
    public String getString(int columnIndex) {
        checkPosition();
        Object value = this.currentRow.get(getColumnNames()[columnIndex]);
        if (value instanceof JdbcClob) {
            try {
                return ((JdbcClob) value).getSubString(1, (int)((JdbcClob) value).length());
            } catch (SQLException x) {
                throw new RuntimeException(x);
            }
        } else {
            return (String)value;
        }
    }
	
	@Implementation
	public short getShort(int columnIndex) {
		checkPosition();
		Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    	if (o==null) return 0;
        return new Short(o.toString());
	}
	
    @Implementation
    public int getInt(int columnIndex) {
    	checkPosition();
    	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    	if (o==null) return 0;
        return new Integer(o.toString());
    }

    @Implementation
    public long getLong(int columnIndex) {
    	checkPosition();
    	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    	if (o==null) return 0;
        return new Long(o.toString());
    }

    @Implementation
    public float getFloat(int columnIndex) {
    	checkPosition();
    	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    	if (o==null) return 0;
        return new Float(o.toString());
        
    }

    @Implementation
    public double getDouble(int columnIndex) {
    	checkPosition();
    	Object o =this.currentRow.get(getColumnNames()[columnIndex]);
    	if (o==null) return 0;
    	return new Double(o.toString());
    }
    
    private void checkPosition() {
        if (-1 == currentRowNumber || getCount() == currentRowNumber) {      
            throw new IndexOutOfBoundsException(currentRowNumber + " " + getCount());
        }
    }

    @Implementation
    public void close() {
        if (resultSet == null) {
            return;
        }

        try {
            resultSet.close();
            resultSet = null;
            rows = null;
            currentRow = null;
        } catch (SQLException e) {
            throw new RuntimeException("SQL exception in close", e);
        }
    }

    @Implementation
    public boolean isClosed() {
        return (resultSet == null);
    }

    @Implementation
    public boolean isNull(int columnIndex) {
        Object o = this.currentRow.get(getColumnNames()[columnIndex]);
        return o == null;
    }

    /**
     * Allows test cases access to the underlying JDBC ResultSet, for use in
     * assertions.
     *
     * @return the result set
     */
    public ResultSet getResultSet() {
        return resultSet;
    }
    
    /**
     * Allows test cases access to the underlying JDBC ResultSetMetaData, for use in
     * assertions. Available even if cl
     *
     * @return the result set
     */
    public ResultSet getResultSetMetaData() {
        return resultSet;
    }    
    
    /**
     * loads a row's values
     * @param rs
     * @return
     * @throws SQLException
     */
    private Map<String,Object> fillRowValues(ResultSet rs) throws SQLException {
    	Map<String,Object> row = new HashMap<String,Object>();
    	for (String s : getColumnNames()) {
			  row.put(s, rs.getObject(s));
    	}
    	return row;
    }
    private void fillRows(String sql, Connection connection) throws SQLException {
    	//ResultSets in SQLite\Android are only TYPE_FORWARD_ONLY. Android caches results in the WindowedCursor to allow moveToPrevious() to function.
    	//Robolectric will have to cache the results too. In the rows map.
    	Statement statement = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        ResultSet rs = statement.executeQuery(sql);
        int count = 0;
        if (rs.next()) {  
        	     do {
        	    	Map<String,Object> row = fillRowValues(rs);
         	    	rows.put(count, row);
        	    	count++;
        	     } while (rs.next());  
        	 } else {  
        		 rs.close(); 
        	 } 
        
        rowCount = count;
        
    }
    
    public void setResultSet(ResultSet result, String sql) {
        this.resultSet = result;
        rowCount = 0;

        //Cache all rows.  Caching rows should be thought of as a simple replacement for ShadowCursorWindow
        if (resultSet != null) {
        	cacheColumnNames(resultSet);
        	try {
        		fillRows(sql, result.getStatement().getConnection());
			} catch (SQLException e) {
			    throw new RuntimeException("SQL exception in setResultSet", e);
			}
        }
    }
}

@@ -201,38 +201,31 @@
 ts;%0A
+%0A
 import 
-org.h2.jdbc.Jdbc
+java.sql.
 Clob;%0A
-%0A
 impo
@@ -2918,20 +2918,16 @@
 tanceof 
-Jdbc
 Clob) %7B%0A
@@ -2969,20 +2969,16 @@
 eturn ((
-Jdbc
 Clob) va
@@ -3009,12 +3009,8 @@
 t)((
-Jdbc
 Clob

fix CLOB changes for SQLite compatibility. Use the Clob interface instead of the H2 specific class JdbcClob.