package org.jcon.df.work;

import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import java.util.Vector;

/**
 * Contains a set of Rows, providing methods to populate,
 * navigate, etc. Notifies RowSetListeners of significant
 * state changes with a RowSetEvent.
 *
 * @author Jack Harich
 */
public class RowSet {

//---------- Public Fields -------------------------------
public static final int EMPTY = -1;

//---------- Private Fields ------------------------------
private RowSetReader rowSetReader;
private Vector rows = new Vector();
private int    currentIndex = EMPTY;
private String currentPosition;
private String focusColumnID;
private Vector listeners = new Vector();
private Row    newRow;

// Immovable (0 or 1 row), Beginning, Middle, End
private String moveState;

// View, Add, Change
private String editState;

//---------- Initialization ------------------------------
public RowSet() {
    reinitialize();
}
private void reinitialize() {
    currentIndex = EMPTY;
    currentPosition = null;
    moveState = null;
    editState = null;
    moveTo("First");
    setEditState("View");
}
//---------- Properties ----------------------------------
public boolean isEmpty() {
    return rows.isEmpty();
}
/**
 * Returns a clone of the current row or null if none.
 * The current row is the newRow if in "Add" edit state.
 */
public Row getCurrentRow() {
    Row row = null;
    if (newRow != null) {
        row = (Row)newRow.clone();
    } else if (isEmpty()) {
        row = null;
    } else {
        row = getRowAt(currentIndex);
        row = (Row)row.clone();
    }
    //print(".getCurrentRow() - " + row);
    return row;
}
public String getMoveState() {
    return moveState;
}
public String getEditState() {
    return editState;
}
public int getRowCount() {
    return rows.size();
}
public int getCurrentIndex() {
    return currentIndex;
}
public String getFocusColumnID() {
    return focusColumnID;
}
public RowSetReader getRowSetReader() {
    if (rowSetReader == null) {
        rowSetReader = new RowSetReader();
        rowSetReader.setRowSet(this);
    }
    return rowSetReader;
}
//---------- Events --------------------------------------
public void addRowSetListener(RowSetListener listener) {
    //print(".addRowSetListener() - adding " + listener.getClass().getName());
    listeners.addElement(listener);
}
public void removeRowSetListener(RowSetListener listener) {
    listeners.removeElement(listener);
}
//---------- Public Methods ------------------------------
//========== New row transaction
/**
 * Begins a "new row transaction" by changing to Add edit
 * state and making the current row the new row. This
 * method MUST be immediately
 * followed by commitNewRow() or rollbackNewRow().
 * During this transaction no other mutate methods can be
 * called on this RowSet. *** ENFORCE
 */
public void beginNewRow(Row row) {
    newRow = row;

    setEditState("Add");
    fireEvent(RowSetEvent.CLEAR_ROW);
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
}
/**
 * Commits the transaction by adding the new row to the end
 * of the row set, moving to Last and changing to View edit state.
 */
public void commitNewRow(Row row) {
    newRow = null;
    rows.addElement(row);

    // Fire ROWS_INSERTED
    RowSetEvent evt = new RowSetEvent(RowSetEvent.ROWS_INSERTED, this);
    evt.setFirstIndexAffected(rows.size() - 1);
    evt.setLastIndexAffected(rows.size() - 1);
    fireEvent(evt);

    //print(".commitNewRow() - \n" + listRowSet());
    moveTo("Last"); // causes INDEX and DATA CHANGED
    setEditState("View");
}
/**
 * Rolls back the transaction by dereferencing the new row,
 * returning to View edit state and firing a
 * CURRENT_ROW_CHANGED event, which will restore the
 * original row.
 */
public void rollbackNewRow() {
    newRow = null;

    setEditState("View");
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
}
//========== Change row transaction
/**
 * Begins a "change row transaction" by changing to Change
 * edit state. This method MUST be immediately
 * followed by commitChangeRow() or rollbackChangeRow().
 * During this transaction no other mutate methods can be
 * called on this RowSet. *** ENFORCE
 */
public void beginChangeRow() {
    setEditState("Change");
}
/**
 * Commits the transaction by setting the current row to be
 * the provided row and changing to View edit state.
 */
public void commitChangeRow(Row row) {
    rows.setElementAt(row, currentIndex);
    setEditState("View");
    // Will cause provided row to be displayed
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
}
/**
 * Rolls back the transaction by returning to View edit
 * state and firing a CURRENT_ROW_CHANGED event, which will
 * restore the original row.
 */
public void rollbackChangeRow() {
    setEditState("View");
    // Will cause original row to be restored
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
}
//========== Other
/**
 * Removes the current row. The current index remains the
 * same unless the last row was removed, in which case the
 * new last row becomes the current index. If the RowSet
 * becomes empty there is no current row.
 */
public void removeCurrentRow() {
    rows.removeElementAt(currentIndex);

    // Fire ROWS_DELETED
    RowSetEvent evt = new RowSetEvent(RowSetEvent.ROWS_DELETED, this);
    evt.setFirstIndexAffected(currentIndex);
    evt.setLastIndexAffected(currentIndex);
    fireEvent(evt);

    // Reset currentIndex
    if (isEmpty()) {
        currentIndex = EMPTY;
    } else if (currentIndex > rows.size() - 1) {
        currentIndex = rows.size() - 1;
    } else {
        // Do nothing
    }
    checkMoveStateForChange();
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
    fireEvent(RowSetEvent.CURRENT_ROW_INDEX_CHANGED);
}
/**
 * Returns the row being edited by acquiring it with an
 * "acquire" event.
 */
public Row acquireEditRow() {
    RowSetEvent evt = fireEvent(RowSetEvent.ACQUIRE_EDIT_ROW);
    Row row = evt.getEditRow(); // *** ???
        //print(" - acquired " + row);
    if (row == null) {
        GenLib.error("RowSet.acquireEditRow()",
            "Acquire event failed to acquire a Row.");
    }
    return row;
}
/**
 * Moves the CurrentIndex to the relative position, which
 * has valid values First, Previous, Next, End or int.
 */
public void moveTo(String position) {
    //print(".moveTo() - " + position);
    position = position.intern();
    int size = rows.size();

    // Set new currentIndex
    if (isEmpty()) {
        currentIndex = EMPTY;

    } else if (rows.size() == 1) {
        currentIndex = 0;

    } else if (DataLib.isInt(position)) {
        int newIndex = Integer.valueOf(position).intValue();
        if (0 <= newIndex && newIndex <= size - 1) {
            currentIndex = newIndex;
        } else {
            // Swing bug, ignore -1
            //print(".moveTo() - invalid position " + position);
            //GenLib.beep();
            return;
        }

    } else if (position == "First") {
        currentIndex = 0;

    } else if (position == "Previous") {
        if (currentIndex > 0) currentIndex--;

    } else if (position == "Next") {
        if (currentIndex < size - 1) currentIndex++;

    } else if (position == "Last") {
        currentIndex = size - 1;

    } else {
        GenLib.error("RowSet.moveTo()", "Unknown position '" + position + "'.");
        return;
    }
    // Fire events
    fireEvent(RowSetEvent.CURRENT_ROW_INDEX_CHANGED);
    fireEvent(RowSetEvent.CURRENT_ROW_DATA_CHANGED);
    checkMoveStateForChange();
    //print(".moveTo() - \n" + listRowSet()); // ***
}
/**
 * Replaces all rows in the RowSet, causing reinitialization
 * and firing of events numerous events.
 */
public void replaceAllRows(Vector newRows) {
    rows = newRows;
    reinitialize();
    fireEvent(RowSetEvent.ALL_ROWS_CHANGED);
}
/**
 * Fires a REQUEST_FOCUS event with the property FocusColumnID.
 * The proper listener will requestFocus for the bound
 * component with the ColumnID.
 */
public void requestFocus(String columnID) {
    focusColumnID = columnID;
    fireEvent(RowSetEvent.REQUEST_FOCUS);
}
public Row getRowAt(int index) {
    return (Row)rows.elementAt(index);
}
public String listRowSet() {
    String text = "";
    for (int i = 0; i < rows.size(); i++) {
        text += getRowAt(i).toString() + "\n";
    }
    return text;
}
//---------- Private Methods -----------------------------
// Fires MOVE_STATE_CHANGED if changed
private void checkMoveStateForChange() {
    if (rows.size() < 2) {
        setMoveState("Immovable");
    } else if (currentIndex == 0) {
        setMoveState("Beginning");
    } else if (currentIndex == rows.size() - 1) {
        setMoveState("End");
    } else {
        setMoveState("Middle");
    }
}
private void setMoveState(String newMoveState) {
    if (! newMoveState.equals(moveState)) {
        moveState = newMoveState;
        fireEvent(RowSetEvent.MOVE_STATE_CHANGED);
    }
}
private void setEditState(String newEditState) {
    if (! newEditState.equals(editState)) {
        editState = newEditState;
        fireEvent(RowSetEvent.EDIT_STATE_CHANGED);
    }
}
private RowSetEvent fireEvent(int eventType) {
    RowSetEvent evt = new RowSetEvent(eventType, this);
    return fireEvent(evt);
}
private RowSetEvent fireEvent(RowSetEvent evt) {
    //print(".fireEvent() - firing " + evt.getEventTypeString());
    Vector list;
    synchronized(this) {
        list = (Vector)listeners.clone();
    }
    for (int i = 0; i < list.size(); i++) {
        RowSetListener listener = (RowSetListener)listeners.elementAt(i);
        //print(".fireEvent() - to " + listener);
        listener.processRowSetEvent(evt);
    }
    return evt; // For acquire events
}
//--- Std
private static void print(String text) {
    System.out.println("RowSet" + text);
}

} // End class
