package SQLite.JDBC2z1;

import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;

public class JDBCStatement implements java.sql.Statement {

    protected JDBCConnection conn;
    protected JDBCResultSet rs;
    protected int updcnt;
    protected int maxrows = 0;
    private ArrayList<String> batch;

    public JDBCStatement(JDBCConnection conn) {
	this.conn = conn;
	this.updcnt = 0;
	this.rs = null;
	this.batch = null;	
    }

    @Override
    public void setFetchSize(int fetchSize) throws SQLException {
	if (fetchSize != 1) {
	    throw new SQLException("fetch size not 1");
	}
    }

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

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

    @Override
    public void setMaxRows(int max) throws SQLException {
	if (max < 0) {
	    throw new SQLException("max must be >= 0 (was " + max + ")");
	}
	maxrows = max;
    }

    @Override
    public void setFetchDirection(int fetchDirection) throws SQLException {
	throw new SQLException("not supported");
    }

    @Override
    public int getFetchDirection() throws SQLException {
	return ResultSet.FETCH_UNKNOWN;
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
	return ResultSet.CONCUR_READ_ONLY;
    }

    @Override
    public int getResultSetType() throws SQLException {
	return ResultSet.TYPE_SCROLL_INSENSITIVE;
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
	if (isClosed()) {
	    throw new SQLException("can't set query timeout on " +
				   "a closed statement");
	} else if (seconds < 0) {
	    throw new SQLException("can't set a query timeout of " +
				   "less than 0 seconds");
	} else if (seconds == 0) {
	    conn.timeout = 5000;
	} else {
	    conn.timeout = seconds * 1000;
	}
    }

    @Override
    public int getQueryTimeout() throws SQLException {
	return conn.timeout / 1000;
    }

    @Override
    public ResultSet getResultSet() throws SQLException {
	return rs;
    }

    ResultSet executeQuery(String sql, String args[], boolean updonly)
	throws SQLException {
	SQLite.TableResult tr = null;
	if (rs != null) {
	    rs.close();
	    rs = null;
	}
	updcnt = -1;
	if (conn == null || conn.db == null) {
	    throw new SQLException("stale connection");
	}
	int busy = 0;
	boolean starttrans = !conn.autocommit && !conn.intrans;
	while (true) {
	    try {
		if (starttrans) {
		    conn.db.exec("BEGIN TRANSACTION", null);
		    conn.intrans = true;
		}
		if (args == null) {
		    if (updonly) {
			conn.db.exec(sql, null);
		    } else {
			tr = conn.db.get_table(sql, maxrows);
		    }
		} else {
		    if (updonly) {
			conn.db.exec(sql, null, args);
		    } else {
			tr = conn.db.get_table(sql, maxrows, args);
		    }
		}
		updcnt = (int) conn.db.changes();
	    } catch (SQLite.Exception e) {
		if (conn.db.is3() &&
		    conn.db.last_error() == SQLite.Constants.SQLITE_BUSY &&
		    conn.busy3(conn.db, ++busy)) {
		    try {
			if (starttrans && conn.intrans) {
			    conn.db.exec("ROLLBACK", null);
			    conn.intrans = false;
			}
		    } catch (SQLite.Exception ee) {
		    }
		    try {
			int ms = 20 + busy * 10;
			if (ms > 1000) {
			    ms = 1000;
			}
			synchronized (this) {
			    this.wait(ms);
			}
		    } catch (java.lang.Exception eee) {
		    }
		    continue;
		}
		throw new SQLException(e);
	    }
	    break;
	}
	if (!updonly && tr == null) {
	    throw new SQLException("no result set produced");
	}
	if (!updonly && tr != null) {
	    rs = new JDBCResultSet(new TableResultX(tr), this);
	}
	return rs;
    }

    @Override
    public ResultSet executeQuery(String sql) throws SQLException {
	return executeQuery(sql, null, false);
    }

    @Override
    public boolean execute(String sql) throws SQLException {
	return executeQuery(sql) != null;
    }

    @Override
    public void cancel() throws SQLException {
	if (conn == null || conn.db == null) {
	    throw new SQLException("stale connection");
	}
	conn.db.interrupt();
    }

    @Override
    public void clearWarnings() throws SQLException {
    }

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

    @Override
    public void addBatch(String sql) throws SQLException {
	if (batch == null) {
	    batch = new ArrayList<String>(1);
	}
	batch.add(sql);
    }

    @Override
    public int[] executeBatch() throws SQLException {
	if (batch == null) {
	    return new int[0];
	}
	int[] ret = new int[batch.size()];
	for (int i = 0; i < ret.length; i++) {
	    ret[i] = EXECUTE_FAILED;
	}
	int errs = 0;
	Exception cause = null;
	for (int i = 0; i < ret.length; i++) {
	    try {
		execute(batch.get(i));
		ret[i] = updcnt;
	    } catch (SQLException e) {
		++errs;
		if (cause == null) {
		    cause = e;
		}
	    }
	}
	if (errs > 0) {
	    throw new BatchUpdateException("batch failed", ret, cause);
	}
	return ret;
    }

    @Override
    public void clearBatch() throws SQLException {
	if (batch != null) {
	    batch.clear();
	    batch = null;
	}
    }

    @Override
    public void close() throws SQLException {
	clearBatch();
	conn = null;
    }

    @Override
    public int executeUpdate(String sql) throws SQLException {
	executeQuery(sql, null, true);
	return updcnt;
    }

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

    @Override
    public boolean getMoreResults() throws SQLException {
	if (rs != null) {
	    rs.close();
	    rs = null;
	}
	return false;
    }

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

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

    @Override
    public void setCursorName(String name) throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
	throw new SQLException("not supported");
    }

    @Override
    public void setMaxFieldSize(int max) throws SQLException {
	throw new SQLException("not supported");
    }

    @Override
    public boolean getMoreResults(int x) throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int executeUpdate(String sql, int autokeys)
	throws SQLException {
	if (autokeys != Statement.NO_GENERATED_KEYS) {
	    throw new SQLFeatureNotSupportedException("generated keys not supported");
	}
	return executeUpdate(sql);
    }

    @Override
    public int executeUpdate(String sql, int colIndexes[])
	throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int executeUpdate(String sql, String colIndexes[])
	throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean execute(String sql, int autokeys)
	throws SQLException {
	if (autokeys != Statement.NO_GENERATED_KEYS) {
	    throw new SQLFeatureNotSupportedException("autogenerated keys not supported");
	}
	return execute(sql);
    }

    @Override
    public boolean execute(String sql, int colIndexes[])
	throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean execute(String sql, String colIndexes[])
	throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
	return ResultSet.HOLD_CURSORS_OVER_COMMIT;
    }

    @Override
    public boolean isClosed() throws SQLException {
	return conn == null;
    }

    @Override
    public void setPoolable(boolean yes) throws SQLException {
	if (yes) {
	    throw new SQLException("poolable statements not supported");
	}
    }

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

    @Override
    public <T> T unwrap(java.lang.Class<T> iface) throws SQLException {
	throw new SQLException("unsupported");
    }

    @Override
    public boolean isWrapperFor(java.lang.Class iface) throws SQLException {
	return false;
    }

    @Override
    public void closeOnCompletion() throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
	throw new SQLFeatureNotSupportedException();
    }

}
