package org.jcon.df.work;

import org.jcon.df.Database;
import org.jcon.df.DatastoreServices;
import org.jcon.df.column.ColumnDef;
import org.jcon.df.Entity;
import org.jcon.df.request.AddOneRow;
import org.jcon.df.request.ReadRow;
import org.jcon.df.request.ReadRowSet;
import org.jcon.df.request.RemoveRows;
import org.jcon.df.request.UpdateRow;
import org.jcon.param.Param;
import org.jcon.ui.MessageBox;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import java.util.Vector;

import org.jcon.df.edit.Test;

/**
 * Represents a source of data for a single RowSet.
 * Responsible for populating and maintaining the RowSet.
 * <p>
 * A DataSource may also be involved in a parent/child
 * relationship. This requires keeping the child
 * synchronized with the parent. We implement this by the
 * child listening for parent events and manipulating
 * itself accordingly.
 *
 * @author Jack Harich
 */
public class DataSource implements Runnable, RowSetListener {

//---------- Private Fields ------------------------------
private DatastoreServices services;
private ReadRowSet        readRowSet;
private Database database;
private String   ID;
private Param    myParam;
private RowSet   rowSet = new RowSet();
private Vector   listeners = new Vector();
private Entity   entity;
private String   focusColumnID;

// Parent/child related
private boolean            isChild;
private DataSourceSupplier dataSourceSupplier;
private String             parentDataSourceID; // null if none
private String             parentLinkColumnName; // null if none
private String             parentEntityName;
private Object             parentLinkValue;

private boolean localTest = false; // ***********

//---------- Initialization ------------------------------
public DataSource() {
    if (localTest) {
        entity = new Entity();
        entity.setParam(Test.createEntityParam());
    }
}
//---------- Runnable Implementation ---------------------
// Must use a new thread since blocking dialog has own thread
public void run() {
    try {
        Thread.currentThread().sleep(250);
    } catch(InterruptedException ex) {
        print(".run() - Thread mysteriously interrupted");
    }
    // Put focus on the field with the problem
    rowSet.requestFocus(focusColumnID);
    focusColumnID = null;
}
//---------- RowSetListener Implementation ---------------
// We are listening to only our parent, so no need to
// check event source
public void processRowSetEvent(RowSetEvent evt) {
    //print(".processEditEvent() - " + evt + " - " + entity.getName());

    switch(evt.getEventType()) {
        case RowSetEvent.CURRENT_ROW_INDEX_CHANGED:
            synchChildRowSet(evt.getCurrentRow());
            break;
    }
}
//---------- Properties ----------------------------------
//----- ID
public void setID(String ID) {
    this.ID = ID;
}
public String getID() {
    return ID;
}
//----- database
public void setDatabase(Database database) {
    this.database = database;
}
public Database getDatabase() {
    return database;
}
//----- entity
public void setEntity(Entity entity) {
    this.entity = entity;
}
public Entity getEntity() {
    return entity;
}
//----- Other
public void setDatastoreServices(DatastoreServices services) {
    this.services = services;
}
public void setParam(Param param) {
    myParam = param;

    // Set database
    String dbName = myParam.getString("DataRequest.Database");
    database = services.createDatabase(dbName);
    //print(".setParam() - got database " + dbName + " - " + database.getName());

    // Set entity
    String entityName = myParam.getString("Entity");
    entity = database.getSchema().getEntity(entityName);
    //print("setParam() - got entity " + entity.getName());

    // Parent/child - These are null if none
    parentDataSourceID = myParam.getString("ParentDataSourceID");
    parentLinkColumnName = myParam.getString("ParentLinkColumnName");
    isChild = (parentDataSourceID != null ? true : false);
}
public void setDataSourceSupplier(DataSourceSupplier dataSourceSupplier) {
    this.dataSourceSupplier = dataSourceSupplier;
}
//---------- Events --------------------------------------
public void addRowSetListener(RowSetListener listener) {
    rowSet.addRowSetListener(listener);
}
public void removeRowSetListener(RowSetListener listener) {
    rowSet.removeRowSetListener(listener);
}
//---------- Public Methods ------------------------------
public void initChild() {
    if (isChild) {
        // As a child we delay RowSet population until
        // parent sends appropriate event
        DataSource parentDataSource =
            dataSourceSupplier.getDataSource(parentDataSourceID);
        parentDataSource.addRowSetListener(this);
        parentEntityName = parentDataSource.getEntity().getName();
    }
}
public void start() {
    if (! isChild) refreshRowSet();
}
// For future use as response to user gesture, etc
public void refresh() {
    refreshRowSet();
}
//----- New row transaction
public void beginNewRow() {
    rowSet.beginNewRow(entity.createNewRow());
}
public void commitNewRow() {
    Row row = rowSet.acquireEditRow();
    if (isChild) {
        // Add link to parent to row
        String columnID = entity.getName() + "." + parentLinkColumnName;
        row.setValue(columnID, parentLinkValue);
        //print(".commitNewRow() - row = " + row + " --- parentLinkValue = " + parentLinkValue);
        debugParentLinkValue(".commitNewRow()");
    }
    if (! validateRow(row, true)) return;
    // Add row to database
    String entityName = entity.getName();
    AddOneRow request = new AddOneRow();
    request.setRow(row);
    request.setEntityName(entityName);

//print(".commitNewRow() - before perform: " + request.getState());
    request = (AddOneRow)database.performRequest(request);
//print(".commitNewRow() - after perform: " + request.getState());

    // Use result to update row in rowSet with newMID
    if (request.isSuccessful()) {
        String newMID = request.getNewMID();
        row.setValue(entityName + ".MID", newMID);
        row = refreshRow(row);
        if (row != null) {
            rowSet.commitNewRow(row);
        } else {
            rollbackNewRow();
        }
    } else {
        request.presentFailure("DataSource.commitNewRow()");
    }
}
public void rollbackNewRow() {
    rowSet.rollbackNewRow();
}
//----- Change row transaction
public void beginChangeRow() {
    rowSet.beginChangeRow();
}
public void commitChangeRow() {
    Row row = rowSet.acquireEditRow();
    if (! validateRow(row, false)) return;

    // Add currentRow MID and TStamp to row
    String entityName = entity.getName();
    String midColumnID = entityName + ".MID";
    String timeStampColumnID = entityName + ".TStamp";

    Row currentRow = rowSet.getCurrentRow(); // Old row

    String mid = currentRow.getValue(midColumnID).toString();
    row.setValue(midColumnID, mid);

    // Note TStamp is currently null in MSAccess since
    // not automatically maintained by database
    Object timeStampValue = currentRow.getValue(timeStampColumnID);
    String timeStamp = (timeStampValue == null ? null : timeStampValue.toString());
    row.setValue(timeStampColumnID, timeStamp);

    // Update row in database
    UpdateRow request = new UpdateRow();
    request.setRow(row);
    request.setEntityName(entityName);
    request.getFilter().addExpression(
        midColumnID, "=", mid);
    //print(".commitChangeRow() - before perform");
    request = (UpdateRow)database.performRequest(request);
    //print(".commitChangeRow() - after perform");

    // Use result to update row in rowSet
    if (request.isSuccessful()) {
        row = refreshRow(row);
        if (row != null) {
            rowSet.commitChangeRow(row);
        } else {
            rollbackChangeRow();
        }
    } else {
        request.presentFailure("DataSource.commitChangeRow()");
    }
}
public void rollbackChangeRow() {
    rowSet.rollbackChangeRow();
}
//----- Other
public void removeCurrentRow() {
    Row row = rowSet.getCurrentRow();
    String entityName = entity.getName();

    // Remove row from database
    RemoveRows request = new RemoveRows();
    request.addEntity(entityName);

    String columnID = entityName + ".MID";
    request.getFilter().addExpression(
        columnID, "=", row.getValue(columnID).toString());

    request = (RemoveRows)database.performRequest(request);

    // Use result to remove row from rowSet
    if (request.isSuccessful()) {
        rowSet.removeCurrentRow();
    } else {
        request.presentFailure("DataSource.removeCurrentRow()");
    }
}
public void moveTo(String position) {
    //print(".moveTo() - " + position);
    rowSet.moveTo(position);
}
// Returns null if not found
public ColumnDef getColumnDef(String columnID) {
    // *** Was
    //String columnName = DataLib.getLastDelimited(columnID, '.');
    //return entity.getColumnDef(columnName);
    return database.getSchema().getColumnDef(columnID);
}
//---------- Private Methods -----------------------------
// Filter myself to parent link value
private void synchChildRowSet(Row currentParentRow) {
    if (currentParentRow == null) return;
    parentLinkValue = currentParentRow.getValue(parentEntityName + ".MID");
    // Example: "AwardMID = 123"
    String filterLine = parentLinkColumnName + " = " + parentLinkValue;
    //print(".synchChildRowSet() - filterLine = " + filterLine);
    // Overwrites any existing filter
    myParam.put("DataRequest.FilterLine", filterLine);
    refresh();
    debugParentLinkValue(".synchChildRowSet()");
}
private void debugParentLinkValue(String text) {
    //print(text + " parentLinkValue = " + parentLinkValue +
    //" in " + this + ", " + entity.getName());
}
// Refresh to get current TStamp and join values
// Returns refreshed row or null if failure
private Row refreshRow(Row row) {
    // Create a ReadRowSet
    readRowSet = new ReadRowSet();
    readRowSet.setParam(myParam.getParam("DataRequest"),
        database.getSchema());
    readRowSet.getFilter().addAnd();

    // Restrict query to this row
    String midColumnID = entity.getName() + ".MID";
    String mid = row.getValue(midColumnID).toString();
    readRowSet.getFilter().addExpression(midColumnID, "=", mid);

    database.performRequest(readRowSet);
    if (! readRowSet.isSuccessful()) {
        readRowSet.presentFailure();
        return null;
    }
    // Read first and only row
    if (readRowSet.hasMoreRows()) {
        Row firstRow = readRowSet.nextRow();
        readRowSet.close();
        return firstRow;
    } else {
        // Should have one row but doesn't
        GenLib.error("DataSource.refreshRow()",
            "ReadRowSet is empty but should have one row.");
        readRowSet.close();
        return null;        
    }
}
private void refreshRowSet() {
    // Create a ReadRowSet
    readRowSet = new ReadRowSet();
    readRowSet.setParam(myParam.getParam("DataRequest"),
        database.getSchema());
    database.performRequest(readRowSet);
    if (! readRowSet.isSuccessful()) {
        readRowSet.presentFailure();
        return;
    }
    // Read rows
    Vector rows = new Vector();
    while (readRowSet.hasMoreRows()) {
        Row row = readRowSet.nextRow();
        rows.addElement(row);
    }
    readRowSet.close();
    // Pass rows to rowSet - The magic call that does alot
    // Will cause views to populate themselves via events
    rowSet.replaceAllRows(rows);
}
private boolean validateRow(Row row, boolean isAdd) {
    // Returns null of valid
    InvalidRow invalidRow;
    if (isAdd) {
        invalidRow = entity.validateAddRow(row);
    } else {
        invalidRow = entity.validateChangeRow(row);
    }
    if (invalidRow == null) {
        return true;
    } else {
        // We must block to allow requestFocus() to work
        GenLib.helpfulHint(invalidRow.getText());
        // Proceed after block released
        focusColumnID = invalidRow.getColumnID();
        new Thread(this).start();
        return false;
    }
}
//--- Std
private static void print(String text) {
    System.out.println("DataSource" + text);
}

} // End class
