/*
 * Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
 * Version 1.0, and under the Eclipse Public License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.test.bench;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;

/**
 * This class implements the functionality of one thread of BenchC.
 */
public class BenchCThread {

    private static final int OP_NEW_ORDER = 0, OP_PAYMENT = 1,
            OP_ORDER_STATUS = 2, OP_DELIVERY = 3,
            OP_STOCK_LEVEL = 4;
    private static final BigDecimal ONE = new BigDecimal("1");

    private Database db;
    private int warehouseId;
    private int terminalId;
    private HashMap prepared = new HashMap();
    private BenchCRandom random;
    private BenchC bench;

    BenchCThread(Database db, BenchC bench, BenchCRandom random, int terminal)
            throws SQLException {
        this.db = db;
        this.bench = bench;
        this.terminalId = terminal;
        db.setAutoCommit(false);
        this.random = random;
        warehouseId = random.getInt(1, bench.warehouses);
    }

    /**
     * Process the list of operations (a 'deck') in random order.
     */
    void process() throws SQLException {
        int[] deck = new int[] { OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER,
                OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER,
                OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, OP_PAYMENT,
                OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT,
                OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT,
                OP_ORDER_STATUS, OP_DELIVERY, OP_STOCK_LEVEL };
        int len = deck.length;
        for (int i = 0; i < len; i++) {
            int temp = deck[i];
            int j = random.getInt(0, len);
            deck[i] = deck[j];
            deck[j] = temp;
        }
        for (int i = 0; i < len; i++) {
            int op = deck[i];
            switch (op) {
            case OP_NEW_ORDER:
                processNewOrder();
                break;
            case OP_PAYMENT:
                processPayment();
                break;
            case OP_ORDER_STATUS:
                processOrderStatus();
                break;
            case OP_DELIVERY:
                processDelivery();
                break;
            case OP_STOCK_LEVEL:
                processStockLevel();
                break;
            default:
                throw new Error("op=" + op);
            }
        }
    }

    private void processNewOrder() throws SQLException {
        int dId = random.getInt(1, bench.districtsPerWarehouse);
        int cId = random.getNonUniform(1023, 1, bench.customersPerDistrict);
        int olCnt = random.getInt(5, 15);
        boolean rollback = random.getInt(1, 100) == 1;
        int[] supplyId = new int[olCnt];
        int[] itemId = new int[olCnt];
        int[] quantity = new int[olCnt];
        int allLocal = 1;
        for (int i = 0; i < olCnt; i++) {
            int w;
            if (bench.warehouses > 1 && random.getInt(1, 100) == 1) {
                do {
                    w = random.getInt(1, bench.warehouses);
                } while (w != warehouseId);
                allLocal = 0;
            } else {
                w = warehouseId;
            }
            supplyId[i] = w;
            int item;
            if (rollback && i == olCnt - 1) {
                // unused order number
                item = -1;
            } else {
                item = random.getNonUniform(8191, 1, bench.items);
            }
            itemId[i] = item;
            quantity[i] = random.getInt(1, 10);
        }
        char[] bg = new char[olCnt];
        int[] stock = new int[olCnt];
        BigDecimal[] amt = new BigDecimal[olCnt];
        Timestamp datetime = new Timestamp(System.currentTimeMillis());
        PreparedStatement prep;
        ResultSet rs;

        prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=D_NEXT_O_ID+1 "
                + "WHERE D_ID=? AND D_W_ID=?");
        prep.setInt(1, dId);
        prep.setInt(2, warehouseId);
        db.update(prep, "updateDistrict");
        prep = prepare("SELECT D_NEXT_O_ID, D_TAX FROM DISTRICT "
                + "WHERE D_ID=? AND D_W_ID=?");
        prep.setInt(1, dId);
        prep.setInt(2, warehouseId);
        rs = db.query(prep);
        rs.next();
        int oId = rs.getInt(1) - 1;
        BigDecimal tax = rs.getBigDecimal(2);
        rs.close();
        // TODO optimizer: such cases can be optimized! A=1 AND B=A means
        // also B=1!
        //        prep = prepare("SELECT C_DISCOUNT, C_LAST, C_CREDIT, W_TAX "
        //                + "FROM CUSTOMER, WAREHOUSE "
        //                + "WHERE C_ID=? AND W_ID=? AND C_W_ID=W_ID AND C_D_ID=?");
        prep = prepare("SELECT C_DISCOUNT, C_LAST, C_CREDIT, W_TAX "
                + "FROM CUSTOMER, WAREHOUSE "
                + "WHERE C_ID=? AND C_W_ID=? AND C_W_ID=W_ID AND C_D_ID=?");
        prep.setInt(1, cId);
        prep.setInt(2, warehouseId);
        prep.setInt(3, dId);
        rs = db.query(prep);
        rs.next();
        BigDecimal discount = rs.getBigDecimal(1);
        // c_last
        rs.getString(2);
        // c_credit
        rs.getString(3);
        BigDecimal wTax = rs.getBigDecimal(4);
        rs.close();
        BigDecimal total = new BigDecimal("0");
        for (int number = 1; number <= olCnt; number++) {
            int olId = itemId[number - 1];
            int olSupplyId = supplyId[number - 1];
            int olQuantity = quantity[number - 1];
            prep = prepare("SELECT I_PRICE, I_NAME, I_DATA "
                    + "FROM ITEM WHERE I_ID=?");
            prep.setInt(1, olId);
            rs = db.query(prep);
            if (!rs.next()) {
                if (rollback) {
                    // item not found - correct behavior
                    db.rollback();
                    return;
                }
                throw new SQLException("item not found: " + olId + " "
                        + olSupplyId);
            }
            BigDecimal price = rs.getBigDecimal(1);
            // i_name
            rs.getString(2);
            String data = rs.getString(3);
            rs.close();
            prep = prepare("SELECT S_QUANTITY, S_DATA, "
                    + "S_DIST_01, S_DIST_02, S_DIST_03, S_DIST_04, S_DIST_05, "
                    + "S_DIST_06, S_DIST_07, S_DIST_08, S_DIST_09, S_DIST_10 "
                    + "FROM STOCK WHERE S_I_ID=? AND S_W_ID=?");
            prep.setInt(1, olId);
            prep.setInt(2, olSupplyId);
            rs = db.query(prep);
            if (!rs.next()) {
                if (rollback) {
                    // item not found - correct behavior
                    db.rollback();
                    return;
                }
                throw new SQLException("item not found: " + olId + " "
                        + olSupplyId);
            }
            int sQuantity = rs.getInt(1);
            String sData = rs.getString(2);
            String[] dist = new String[10];
            for (int i = 0; i < 10; i++) {
                dist[i] = rs.getString(3 + i);
            }
            rs.close();
            String distInfo = dist[dId - 1];
            stock[number - 1] = sQuantity;
            if ((data.indexOf("original") != -1)
                    && (sData.indexOf("original") != -1)) {
                bg[number - 1] = 'B';
            } else {
                bg[number - 1] = 'G';
            }
            if (sQuantity > olQuantity) {
                sQuantity = sQuantity - olQuantity;
            } else {
                sQuantity = sQuantity - olQuantity + 91;
            }
            prep = prepare("UPDATE STOCK SET S_QUANTITY=? "
                    + "WHERE S_W_ID=? AND S_I_ID=?");
            prep.setInt(1, sQuantity);
            prep.setInt(2, olSupplyId);
            prep.setInt(3, olId);
            db.update(prep, "updateStock");
            BigDecimal olAmount = new BigDecimal(olQuantity).multiply(
                    price).multiply(ONE.add(wTax).add(tax)).multiply(
                    ONE.subtract(discount));
            olAmount = olAmount.setScale(2, BigDecimal.ROUND_HALF_UP);
            amt[number - 1] = olAmount;
            total = total.add(olAmount);
            prep = prepare("INSERT INTO ORDER_LINE (OL_O_ID, OL_D_ID, OL_W_ID, OL_NUMBER, "
                    + "OL_I_ID, OL_SUPPLY_W_ID, "
                    + "OL_QUANTITY, OL_AMOUNT, OL_DIST_INFO) "
                    + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
            prep.setInt(1, oId);
            prep.setInt(2, dId);
            prep.setInt(3, warehouseId);
            prep.setInt(4, number);
            prep.setInt(5, olId);
            prep.setInt(6, olSupplyId);
            prep.setInt(7, olQuantity);
            prep.setBigDecimal(8, olAmount);
            prep.setString(9, distInfo);
            db.update(prep, "insertOrderLine");
        }
        prep = prepare("INSERT INTO ORDERS (O_ID, O_D_ID, O_W_ID, O_C_ID, "
                + "O_ENTRY_D, O_OL_CNT, O_ALL_LOCAL) "
                + "VALUES (?, ?, ?, ?, ?, ?, ?)");
        prep.setInt(1, oId);
        prep.setInt(2, dId);
        prep.setInt(3, warehouseId);
        prep.setInt(4, cId);
        prep.setTimestamp(5, datetime);
        prep.setInt(6, olCnt);
        prep.setInt(7, allLocal);
        db.update(prep, "insertOrders");
        prep = prepare("INSERT INTO NEW_ORDER (NO_O_ID, NO_D_ID, NO_W_ID) "
                + "VALUES (?, ?, ?)");
        prep.setInt(1, oId);
        prep.setInt(2, dId);
        prep.setInt(3, warehouseId);
        db.update(prep, "insertNewOrder");
        db.commit();
    }

    private void processPayment() throws SQLException {
        int dId = random.getInt(1, bench.districtsPerWarehouse);
        int wId, cdId;
        if (bench.warehouses > 1 && random.getInt(1, 100) <= 15) {
            do {
                wId = random.getInt(1, bench.warehouses);
            } while (wId != warehouseId);
            cdId = random.getInt(1, bench.districtsPerWarehouse);
        } else {
            wId = warehouseId;
            cdId = dId;
        }
        boolean byName;
        String last;
        int cId = 1;
        if (random.getInt(1, 100) <= 60) {
            byName = true;
            last = random.getLastname(random.getNonUniform(255, 0, 999));
        } else {
            byName = false;
            last = "";
            cId = random.getNonUniform(1023, 1, bench.customersPerDistrict);
        }
        BigDecimal amount = random.getBigDecimal(random.getInt(100, 500000),
                2);
        Timestamp datetime = new Timestamp(System.currentTimeMillis());
        PreparedStatement prep;
        ResultSet rs;

        prep = prepare("UPDATE DISTRICT SET D_YTD = D_YTD+? "
                + "WHERE D_ID=? AND D_W_ID=?");
        prep.setBigDecimal(1, amount);
        prep.setInt(2, dId);
        prep.setInt(3, warehouseId);
        db.update(prep, "updateDistrict");
        prep = prepare("UPDATE WAREHOUSE SET W_YTD=W_YTD+? WHERE W_ID=?");
        prep.setBigDecimal(1, amount);
        prep.setInt(2, warehouseId);
        db.update(prep, "updateWarehouse");
        prep = prepare("SELECT W_STREET_1, W_STREET_2, W_CITY, W_STATE, W_ZIP, W_NAME "
                + "FROM WAREHOUSE WHERE W_ID=?");
        prep.setInt(1, warehouseId);
        rs = db.query(prep);
        rs.next();
        // w_street_1
        rs.getString(1);
        // w_street_2
        rs.getString(2);
        // w_city
        rs.getString(3);
        // w_state
        rs.getString(4);
        // w_zip
        rs.getString(5);
        String wName = rs.getString(6);
        rs.close();
        prep = prepare("SELECT D_STREET_1, D_STREET_2, D_CITY, D_STATE, D_ZIP, D_NAME "
                + "FROM DISTRICT WHERE D_ID=? AND D_W_ID=?");
        prep.setInt(1, dId);
        prep.setInt(2, warehouseId);
        rs = db.query(prep);
        rs.next();
        // d_street_1
        rs.getString(1);
        // d_street_2
        rs.getString(2);
        // d_city
        rs.getString(3);
        // d_state
        rs.getString(4);
        // d_zip
        rs.getString(5);
        String dName = rs.getString(6);
        rs.close();
        BigDecimal balance;
        String credit;
        if (byName) {
            prep = prepare("SELECT COUNT(C_ID) FROM CUSTOMER "
                    + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=?");
            prep.setString(1, last);
            prep.setInt(2, cdId);
            prep.setInt(3, wId);
            rs = db.query(prep);
            rs.next();
            int namecnt = rs.getInt(1);
            rs.close();
            if (namecnt == 0) {
                // TODO TPC-C: check if this can happen
                db.rollback();
                return;
            }
            prep = prepare("SELECT C_FIRST, C_MIDDLE, C_ID, "
                    + "C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, "
                    + "C_PHONE, C_CREDIT, C_CREDIT_LIM, "
                    + "C_DISCOUNT, C_BALANCE, C_SINCE FROM CUSTOMER "
                    + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=? "
                    + "ORDER BY C_FIRST");
            prep.setString(1, last);
            prep.setInt(2, cdId);
            prep.setInt(3, wId);
            rs = db.query(prep);
            // locate midpoint customer
            if (namecnt % 2 != 0) {
                namecnt++;
            }
            for (int n = 0; n < namecnt / 2; n++) {
                rs.next();
            }
            // c_first
            rs.getString(1);
            // c_middle
            rs.getString(2);
            cId = rs.getInt(3);
            // c_street_1
            rs.getString(4);
            // c_street_2
            rs.getString(5);
            // c_city
            rs.getString(6);
            // c_state
            rs.getString(7);
            // c_zip
            rs.getString(8);
            // c_phone
            rs.getString(9);
            credit = rs.getString(10);
            // c_credit_lim
            rs.getString(11);
            // c_discount
            rs.getBigDecimal(12);
            balance = rs.getBigDecimal(13);
            // c_since
            rs.getTimestamp(14);
            rs.close();
        } else {
            prep = prepare("SELECT C_FIRST, C_MIDDLE, C_LAST, "
                    + "C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, "
                    + "C_PHONE, C_CREDIT, C_CREDIT_LIM, "
                    + "C_DISCOUNT, C_BALANCE, C_SINCE FROM CUSTOMER "
                    + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
            prep.setInt(1, cId);
            prep.setInt(2, cdId);
            prep.setInt(3, wId);
            rs = db.query(prep);
            rs.next();
            // c_first
            rs.getString(1);
            // c_middle
            rs.getString(2);
            // c_last
            rs.getString(3);
            // c_street_1
            rs.getString(4);
            // c_street_2
            rs.getString(5);
            // c_city
            rs.getString(6);
            // c_state
            rs.getString(7);
            // c_zip
            rs.getString(8);
            // c_phone
            rs.getString(9);
            credit = rs.getString(10);
            // c_credit_lim
            rs.getString(11);
            // c_discount
            rs.getBigDecimal(12);
            balance = rs.getBigDecimal(13);
            // c_since
            rs.getTimestamp(14);
            rs.close();
        }
        balance = balance.add(amount);
        if (credit.equals("BC")) {
            prep = prepare("SELECT C_DATA INTO FROM CUSTOMER "
                    + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
            prep.setInt(1, cId);
            prep.setInt(2, cdId);
            prep.setInt(3, wId);
            rs = db.query(prep);
            rs.next();
            String cData = rs.getString(1);
            rs.close();
            String cNewData = "| " + cId + " " + cdId + " " + wId
                    + " " + dId + " " + warehouseId + " " + amount + " "
                    + cData;
            if (cNewData.length() > 500) {
                cNewData = cNewData.substring(0, 500);
            }
            prep = prepare("UPDATE CUSTOMER SET C_BALANCE=?, C_DATA=? "
                    + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
            prep.setBigDecimal(1, balance);
            prep.setString(2, cNewData);
            prep.setInt(3, cId);
            prep.setInt(4, cdId);
            prep.setInt(5, wId);
            db.update(prep, "updateCustomer");
        } else {
            prep = prepare("UPDATE CUSTOMER SET C_BALANCE=? "
                    + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
            prep.setBigDecimal(1, balance);
            prep.setInt(2, cId);
            prep.setInt(3, cdId);
            prep.setInt(4, wId);
            db.update(prep, "updateCustomer");
        }
        // MySQL bug?
//        String h_data = w_name + "    " + d_name;
        String hData = wName + " " + dName;
        prep = prepare("INSERT INTO HISTORY (H_C_D_ID, H_C_W_ID, H_C_ID, H_D_ID, "
                + "H_W_ID, H_DATE, H_AMOUNT, H_DATA) "
                + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
        prep.setInt(1, cdId);
        prep.setInt(2, wId);
        prep.setInt(3, cId);
        prep.setInt(4, dId);
        prep.setInt(5, warehouseId);
        prep.setTimestamp(6, datetime);
        prep.setBigDecimal(7, amount);
        prep.setString(8, hData);
        db.update(prep, "insertHistory");
        db.commit();
    }

    private void processOrderStatus() throws SQLException {
        int dId = random.getInt(1, bench.districtsPerWarehouse);
        boolean byName;
        String last = null;
        int cId = -1;
        if (random.getInt(1, 100) <= 60) {
            byName = true;
            last = random.getLastname(random.getNonUniform(255, 0, 999));
        } else {
            byName = false;
            cId = random.getNonUniform(1023, 1, bench.customersPerDistrict);
        }
        PreparedStatement prep;
        ResultSet rs;

        prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1");
        db.update(prep, "updateDistrict");
        if (byName) {
            prep = prepare("SELECT COUNT(C_ID) FROM CUSTOMER "
                    + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=?");
            prep.setString(1, last);
            prep.setInt(2, dId);
            prep.setInt(3, warehouseId);
            rs = db.query(prep);
            rs.next();
            int namecnt = rs.getInt(1);
            rs.close();
            if (namecnt == 0) {
                // TODO TPC-C: check if this can happen
                db.rollback();
                return;
            }
            prep = prepare("SELECT C_BALANCE, C_FIRST, C_MIDDLE, C_ID "
                    + "FROM CUSTOMER "
                    + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=? "
                    + "ORDER BY C_FIRST");
            prep.setString(1, last);
            prep.setInt(2, dId);
            prep.setInt(3, warehouseId);
            rs = db.query(prep);
            if (namecnt % 2 != 0) {
                namecnt++;
            }
            for (int n = 0; n < namecnt / 2; n++) {
                rs.next();
            }
            // c_balance
            rs.getBigDecimal(1);
            // c_first
            rs.getString(2);
            // c_middle
            rs.getString(3);
            rs.close();
        } else {
            prep = prepare("SELECT C_BALANCE, C_FIRST, C_MIDDLE, C_LAST "
                    + "FROM CUSTOMER "
                    + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
            prep.setInt(1, cId);
            prep.setInt(2, dId);
            prep.setInt(3, warehouseId);
            rs = db.query(prep);
            rs.next();
            // c_balance
            rs.getBigDecimal(1);
            // c_first
            rs.getString(2);
            // c_middle
            rs.getString(3);
            // c_last
            rs.getString(4);
            rs.close();
        }
        prep = prepare("SELECT MAX(O_ID) "
                + "FROM ORDERS WHERE O_C_ID=? AND O_D_ID=? AND O_W_ID=?");
        prep.setInt(1, cId);
        prep.setInt(2, dId);
        prep.setInt(3, warehouseId);
        rs = db.query(prep);
        int oId = -1;
        if (rs.next()) {
            oId = rs.getInt(1);
            if (rs.wasNull()) {
                oId = -1;
            }
        }
        rs.close();
        if (oId != -1) {
            prep = prepare("SELECT O_ID, O_CARRIER_ID, O_ENTRY_D "
                    + "FROM ORDERS WHERE O_ID=?");
            prep.setInt(1, oId);
            rs = db.query(prep);
            rs.next();
            oId = rs.getInt(1);
            // o_carrier_id
            rs.getInt(2);
            // o_entry_d
            rs.getTimestamp(3);
            rs.close();
            prep = prepare("SELECT OL_I_ID, OL_SUPPLY_W_ID, OL_QUANTITY, "
                    + "OL_AMOUNT, OL_DELIVERY_D FROM ORDER_LINE "
                    + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?");
            prep.setInt(1, oId);
            prep.setInt(2, dId);
            prep.setInt(3, warehouseId);
            rs = db.query(prep);
            while (rs.next()) {
                // o_i_id
                rs.getInt(1);
                // ol_supply_w_id
                rs.getInt(2);
                // ol_quantity
                rs.getInt(3);
                // ol_amount
                rs.getBigDecimal(4);
                // ol_delivery_d
                rs.getTimestamp(5);
            }
            rs.close();
        }
        db.commit();
    }

    private void processDelivery() throws SQLException {
        int carrierId = random.getInt(1, 10);
        Timestamp datetime = new Timestamp(System.currentTimeMillis());
        PreparedStatement prep;
        ResultSet rs;

        prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1");
        db.update(prep, "updateDistrict");
        for (int dId = 1; dId <= bench.districtsPerWarehouse; dId++) {
            prep = prepare("SELECT MIN(NO_O_ID) FROM NEW_ORDER "
                    + "WHERE NO_D_ID=? AND NO_W_ID=?");
            prep.setInt(1, dId);
            prep.setInt(2, warehouseId);
            rs = db.query(prep);
            int noId = -1;
            if (rs.next()) {
                noId = rs.getInt(1);
                if (rs.wasNull()) {
                    noId = -1;
                }
            }
            rs.close();
            if (noId != -1) {
                prep = prepare("DELETE FROM NEW_ORDER "
                        + "WHERE NO_O_ID=? AND NO_D_ID=? AND NO_W_ID=?");
                prep.setInt(1, noId);
                prep.setInt(2, dId);
                prep.setInt(3, warehouseId);
                db.update(prep, "deleteNewOrder");
                prep = prepare("SELECT O_C_ID FROM ORDERS "
                        + "WHERE O_ID=? AND O_D_ID=? AND O_W_ID=?");
                prep.setInt(1, noId);
                prep.setInt(2, dId);
                prep.setInt(3, warehouseId);
                rs = db.query(prep);
                rs.next();
                // o_c_id
                rs.getInt(1);
                rs.close();
                prep = prepare("UPDATE ORDERS SET O_CARRIER_ID=? "
                        + "WHERE O_ID=? AND O_D_ID=? AND O_W_ID=?");
                prep.setInt(1, carrierId);
                prep.setInt(2, noId);
                prep.setInt(3, dId);
                prep.setInt(4, warehouseId);
                db.update(prep, "updateOrders");
                prep = prepare("UPDATE ORDER_LINE SET OL_DELIVERY_D=? "
                        + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?");
                prep.setTimestamp(1, datetime);
                prep.setInt(2, noId);
                prep.setInt(3, dId);
                prep.setInt(4, warehouseId);
                db.update(prep, "updateOrderLine");
                prep = prepare("SELECT SUM(OL_AMOUNT) FROM ORDER_LINE "
                        + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?");
                prep.setInt(1, noId);
                prep.setInt(2, dId);
                prep.setInt(3, warehouseId);
                rs = db.query(prep);
                rs.next();
                BigDecimal amount = rs.getBigDecimal(1);
                rs.close();
                prep = prepare("UPDATE CUSTOMER SET C_BALANCE=C_BALANCE+? "
                        + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?");
                prep.setBigDecimal(1, amount);
                prep.setInt(2, noId);
                prep.setInt(3, dId);
                prep.setInt(4, warehouseId);
                db.update(prep, "updateCustomer");
            }
        }
        db.commit();
    }

    private void processStockLevel() throws SQLException {
        int dId = (terminalId % bench.districtsPerWarehouse) + 1;
        int threshold = random.getInt(10, 20);
        PreparedStatement prep;
        ResultSet rs;

        prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1");
        db.update(prep, "updateDistrict");

        prep = prepare("SELECT D_NEXT_O_ID FROM DISTRICT "
                + "WHERE D_ID=? AND D_W_ID=?");
        prep.setInt(1, dId);
        prep.setInt(2, warehouseId);
        rs = db.query(prep);
        rs.next();
        int oId = rs.getInt(1);
        rs.close();
//        prep = prepare("SELECT COUNT(DISTINCT S_I_ID) "
//                + "FROM ORDER_LINE, STOCK WHERE OL_W_ID=? AND "
//                + "OL_D_ID=? AND OL_O_ID<? AND "
//                + "OL_O_ID>=?-20 AND S_W_ID=? AND "
//                + "S_I_ID=OL_I_ID AND S_QUANTITY<?");
//        prep.setInt(1, warehouseId);
//        prep.setInt(2, d_id);
//        prep.setInt(3, o_id);
//        prep.setInt(4, o_id);
        prep = prepare("SELECT COUNT(DISTINCT S_I_ID) "
                + "FROM ORDER_LINE, STOCK WHERE OL_W_ID=? AND "
                + "OL_D_ID=? AND OL_O_ID<? AND "
                + "OL_O_ID>=? AND S_W_ID=? AND "
                + "S_I_ID=OL_I_ID AND S_QUANTITY<?");
        prep.setInt(1, warehouseId);
        prep.setInt(2, dId);
        prep.setInt(3, oId);
        prep.setInt(4, oId - 20);
        prep.setInt(5, warehouseId);
        prep.setInt(6, threshold);
        // TODO this is where HSQLDB is very slow
        rs = db.query(prep);
        rs.next();
        // stockCount
        rs.getInt(1);
        rs.close();
        db.commit();
    }

    private PreparedStatement prepare(String sql) throws SQLException {
        PreparedStatement prep = (PreparedStatement) prepared.get(sql);
        if (prep == null) {
            prep = db.prepare(sql);
            prepared.put(sql, prep);
        }
        return prep;
    }

}
