/*
 * InventoryItem.java
 */

import java.sql.*;

/** An object of the InventoryItem class represents a record in the
 * inventory table.
 *
 * @author  Timothy Paul Fox
 * @version July 25, 2004, 9:47 PM
 */
public class InventoryItem {
    
    private ItemID itemID;
    private String catalogID;
    private String itemCost;
    private java.sql.Date dateAdded;
    private java.sql.Date dateRemoved;
    private String whyRemoved;
    
    private Statement userStmt;
    private Connection dbWire;
    private ResultSet rowRS;
    private boolean isNewRow;
    private String fieldInsertList;
    private String valueInsertList;
    private String updateList;
    
    private boolean costChanged;
    private boolean removedFlag;
    private boolean reasonChanged;

    
    {
        itemID = null;
        catalogID = null;
        dateAdded = dateRemoved = null;
        itemCost = whyRemoved = null;

        userStmt = null;
        dbWire = null;
        rowRS = null;
        isNewRow = false;
        resetAllChangeFlags();
    }
    
    private void resetAllChangeFlags() {
        costChanged = removedFlag = reasonChanged = false;
    } // end resetAllChangeFlags
    
    private String quoteForSQL(String s) {
        StringBuffer sb = new StringBuffer(s);
        int qPos = -1;
        int lastQPos = 0;
        do { // escape any internal apostrophes
            //  O'Reilly --> O''Reilly
            qPos = sb.indexOf("'", lastQPos);
            if (qPos >= 0) {
                sb.insert(qPos,'\'');
                lastQPos = qPos + 2;
            }
        } while (qPos >= 0);
        sb.insert(0, '\'');
        sb.append('\'');
        return sb.toString();
    } // end quoteForSQL
    
    private void makeUpdateList(boolean changeAll) {
        int fieldCount = 0;
        StringBuffer sb = new StringBuffer("");
        
        if ((itemCost != null) && (changeAll || costChanged)) {
            sb.append("cost = " + quoteForSQL(itemCost));
            fieldCount++;
        }
        if ((dateRemoved != null) && (removedFlag)) {
            if (fieldCount > 0) 
                sb.append(", ");
            sb.append("date_removed = " + 
                        quoteForSQL(dateRemoved.toString()) );
            fieldCount++;
        }
        if ((whyRemoved != null) && (changeAll || reasonChanged)) {
            if (fieldCount > 0) 
                sb.append(", ");
            sb.append("why_removed = " + quoteForSQL(whyRemoved) );
            fieldCount++;
        }
        updateList = sb.toString();
    } // end makeUpdateList
    
    private void makeInsertLists() {
        StringBuffer fieldList = new StringBuffer("(");
        StringBuffer valueList = new StringBuffer("(");
        fieldList.append("item_id");
        valueList.append(quoteForSQL(itemID.toString()));
        fieldList.append(", ");
        valueList.append(", ");
        fieldList.append("catalog_id");
        valueList.append(quoteForSQL(catalogID.toString()));
        fieldList.append(", ");
        valueList.append(", ");
        fieldList.append("date_added");
        valueList.append(quoteForSQL(dateAdded.toString()));
        fieldList.append(")");
        valueList.append(")");
        
        fieldInsertList = fieldList.toString();
        valueInsertList = valueList.toString();
        
    } // end makeInsertLists
    
    /** Creates a new record in the inventory table. 
     * The record created includes a new ItemID and the current date.
     * @param catID the CatalogID of the item to be added to inventory
     * @param dummy this parameter does nothing except differentiate this 
     * constructor from the one that finds an existing inventory record. 
     * @throws Exception probably SQLException, from a JDBC access attempt
     */    
    public InventoryItem(String catID, int dummy) throws Exception {
        isNewRow = true;
        itemID = new ItemID();
        try {
            CatalogItem testCat = new CatalogItem(catID);
            catalogID = testCat.getCatalogID();
        }
        catch (Exception e) {
            throw new IllegalArgumentException(
            "InventoryItem(): could not confirm a catalog entry for " +
              catID);
        }
        dateAdded = new java.sql.Date(System.currentTimeMillis());

        makeInsertLists();

        try {        
            makeDbConnection("root"); // opens dbWire, userStmt
            
            int result = putToRow(
                    "INSERT INTO inventory " + fieldInsertList +
                    " VALUES " + valueInsertList );
            if (result != 1) 
                throw new SQLException(
                        "InventoryItem: new record not written");
        }
        finally {
            if (userStmt != null) userStmt.close();
            if (dbWire != null) dbWire.close();
        }
    } // end InventoryItem -- new record constructor

    /** writes any pending changes to the inventory 
     * table.
     * @throws Exception whatever JDBC throws interacting with the database
     */    
    public void update() throws Exception {
        makeUpdateList(false); // make list for changed fields only
        try {        
            makeDbConnection("root"); // opens dbWire, userStmt
            
            int result = putToRow(
                    "UPDATE inventory " +
                    "SET " + updateList +
                    " WHERE (item_id = " +
                    quoteForSQL(itemID.toString()) + ")");
            if (result != 1) 
                throw new SQLException(
                        "InventoryItem: update() error");
            resetAllChangeFlags();
        }
        finally {
            if (userStmt != null) userStmt.close();
            if (dbWire != null) dbWire.close();
        }
    } // end update()
    
    /** Creates an InventoryItem object that represents an existing 
     * record in the inventory table. 
     * Finds a record in the inventory table whose 
     * primary key matches knownID, then creates an InventoryItem object 
     * and copies the record's data to it.
     * @param knownID the primary key (ItemID) in an 
     * inventory record
     * @throws Exception from not contacting the database, 
     * or not finding a matching record
     */    
    public InventoryItem(String knownID) throws Exception {
        itemID = new ItemID(knownID);
        try {
            makeDbConnection("root");
            getFromRow("SELECT * from inventory " +
                       "WHERE (item_id = " +
                       quoteForSQL(itemID.toString()) + ")");
            rowRS.first();
            
            catalogID = rowRS.getString("catalog_id");
            itemCost = rowRS.getString("cost");
            dateAdded = rowRS.getDate("date_added");
            dateRemoved = rowRS.getDate("date_removed");
            whyRemoved = rowRS.getString("why_removed");
        }
        finally {
            if (userStmt != null) userStmt.close();
            if (dbWire != null) dbWire.close();
        }
    } // end InventoryItem -- initializer to find existing record
    
    private void getFromRow(String query) throws Exception {
        rowRS = userStmt.executeQuery(query);
    }
    
    private void makeDbConnection(String user) throws Exception {
        LibManDbConnection plug;
        
        if (dbWire != null) {
            dbWire.close();
        }
        plug = new LibManDbConnection(user);
        dbWire = plug.connect();
        userStmt = dbWire.createStatement();
    } // end makeDbConnection
    
    private int putToRow(String updateSpec) throws Exception{
        return userStmt.executeUpdate(updateSpec);
    }
    
    /** shows the contents of the InventoryItem object.
     * @return The output will be formatted as
* InventoryItem(itemID):
* CatalogItem(catalogID),
* Added (dateAdded)
* Removed (dateRemoved), (whyRemoved)
* * NOTE: the 'Removed' line will not be present unless * dateRemoved has been set. */ public String toString() { StringBuffer sb = new StringBuffer("InventoryItem("); sb.append(itemID.toString() + "): \n"); sb.append("CatalogItem("); sb.append(catalogID + "), \n"); sb.append("Added " + dateAdded.toString() + "\n"); if (dateRemoved != null) { sb.append("Removed " + dateAdded.toString()); if (whyRemoved != null) sb.append(", " + whyRemoved); } sb.append("\n"); return sb.toString(); } // end toString() /** gets the ItemID * @return The primary key value for this record in the * inventory table */ public String getItemID() { return itemID.toString(); } /** gets the CatalogID * @return The primary key value of the item in the * catalog tables that describes this * inventory item. */ public String getCatalogID() { return catalogID; } /** gets the cost of this item. * @return the cost if the cost (an optional property) has been set, * else returns the empty string "". */ public String getCost() { return (itemCost == null)? "" : itemCost; } /** gets the date the item was added to inventory. * @return the date in yyyy-mm-dd form. */ public String getDateAdded() { return (dateAdded == null)? "" : dateAdded.toString(); } /** gets the date the item was removed from active inventory. * @return the date in yyyy-mm-dd form, or the empty string "" * if the item has not been removed.. */ public String getDateRemoved() { return (dateRemoved == null)? "" : dateRemoved.toString(); } /** gets a brief description of why the item was removed * @return the reason, or the empty string "" if the item has * not been removed.. */ public String getWhyRemoved() { return (whyRemoved == null)? "" : whyRemoved; } /** indicates that the item has been removed from active inventory * @return true if the dateRemoved property has * been set, otherwise false */ public boolean wasRemoved() /* throws Exception */ { if ( (!isNewRow) && (dateRemoved != null) && (dateAdded != null) && (dateAdded.compareTo(dateRemoved) <= 0) // OK to remove same day? ) return true; else return false; } /** marks an inventory record as 'removed'. The InventoryItem object * must be one that was created by reading the * inventory table. * @throws Exception any exception from accessing the database, plus * IllegalArgumentException if remove() is called on a record that * has already been retired. * @param why a brief (10 chars max) description of why the item was * removed. (Examples: "lost", "worn out", "defaced") May be null. */ public void remove(String why) throws Exception { int result; if (isNewRow) throw new IllegalArgumentException( "InventoryItem:Cannot remove() a newly created record!"); if (wasRemoved()) throw new IllegalArgumentException( "InventoryItem:Cannot remove() a record more than once!"); dateRemoved = new java.sql.Date(System.currentTimeMillis()); removedFlag = true; setWhyRemoved(why, false); update(); } // end retire() /** sets the item's cost property. * @param newS The Cost string. Maximum storable length is * 6 characters. Must be non-blank to be stored. * @param immediate If this is set true, the new * data will be written immediately to the database. * If false, the database will not be updated * until the update() method is called. * @throws Exception passes along whatever exception the update() * method encounters when trying to update the database. */ public void setCost(String newS, boolean immediate) throws Exception { if (newS == null || newS.length() < 1) return; itemCost = newS; costChanged = true; if (immediate) update(); } /** sets the item's whyRemoved property. * @param newS a brief description of why the item is being removed. * Must be non-blank to be stored. Maximum storable length is * 10 characters. * @param immediate If this is set true, the new * data will be written immediately to the database. * If false, the database will not be updated * until the update() method is called. * @throws Exception passes along whatever exception the update() * method encounters when trying to update the database. */ public void setWhyRemoved(String newS, boolean immediate) throws Exception { if (newS == null || newS.length() < 1) return; whyRemoved = newS; reasonChanged = true; if (immediate) update(); } } // end class CatalogItem