package org.jcon.df.edit;

import org.jcon.ba.system.BeanActionClose;
import org.jcon.ba.system.BeanActionStart;
import org.jcon.df.Schema;
import org.jcon.df.DatastoreServices;
import org.jcon.df.edit.module.Module;
import org.jcon.df.edit.task.Task;
import org.jcon.df.edit.task.TaskContext;
import org.jcon.df.edit.task.TaskFactory;
import org.jcon.df.work.DataSource;
import org.jcon.df.work.DataSourceSupplier;
import org.jcon.param.Param;
//import org.jcon.param.ParamConverter;
import org.jcon.param.ParamDrivenInfo;
import org.jcon.param.ParamDrivenSupply;
import org.jcon.param.ParamSupplier;
import org.jcon.param.util.ParamReplacer;
import org.jcon.ui.MessageBox;
import org.jcon.util.BenchMark;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import org.jcon.util.msg.Message;
import org.jcon.util.msg.MessageListener;
import org.jcon.util.service.ContainerServices;
import org.jcon.util.service.ContainerServicesUser;
//import org.jcon.util.setting.SettingStore;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

/**
 * The CONTROLLER and entry point for data edits. It
 * uses DataView for the VIEW and DataSource and RowSet
 * for the MODEL.
 * Test: java org.jcon.df.edit.DataEditor
 *
 * @author Jack Harich
 */
public class DataEditor implements
    ParamDrivenSupply, EditListener, BeanActionStart,
    BeanActionClose, MessageListener, DataSourceSupplier,
    ContainerServicesUser {

//---------- Private Fields ------------------------------
private static boolean    DEBUG = true;

private ParamSupplier     paramSupplier;
private Param             actualParam;  // Never changed
private Param             useParam;     // Settings replaced, optionally built from meta param
private Task              task;
private DatastoreServices datastoreServices;
private ContainerServices containerServices;
private boolean           started;

// config 
private String  configStartMessageName; // null if none
private ParamReplacer paramReplacer = new ParamReplacer();

// Key = viewName, Object = DataView
private Hashtable views = new Hashtable();

// Key = DataSourceID, Object = DataSource
private Hashtable sources = new Hashtable();

//---------- Initialization ------------------------------
public DataEditor() {
}
//---------- ParamDrivenSupply Implementation ------------
public void setParamSupplier(ParamSupplier paramSupplier) {
    this.paramSupplier = paramSupplier;
}
public void setParam(Param param) {
    actualParam = param;
}
public Param getParam() {
    if (actualParam == null) {
        paramSupplier.supplyParam(this);
        paramSupplier = null;
    }
    return actualParam;
}
public boolean applyNewParam(Param newParam) {
    actualParam = newParam;
    boolean wasStarted = started;
    close();
    if (wasStarted) start();
    return true; // Successful
}
public ParamDrivenInfo getParamDrivenInfo() {
    return null;
}
//---------- EditListener Implementation -----------------
public void processEditEvent(EditEvent evt) {
    //print(".processEditEvent() - " + evt.getEventTypeString());
    int eventType = evt.getEventType();
    
    if (task != null) {
        if (task.handleEditEvent(evt) == true) return;
    }
    // dataSourceID and source not used
    if (eventType == EditEvent.CLOSE) {
        closeViewRequested(evt.getViewName());
        return;
    }
    // Some eventTypes are ignored by this class
    if (eventType > EditEvent.NON_TYPICAL) return;
    
    // Set dataSourceID and source
    String dataSourceID = evt.getDataSourceID();
    if (dataSourceID == null) {
        GenLib.error("DataEditor.processEditEvent()",
            "dataSourceID is null in " + evt.getEventTypeString());
        return;
    }
    DataSource source = (DataSource)sources.get(dataSourceID);
    if (source == null) {
        GenLib.error("DataEditor.processEditEvent()",
            "No source for dataSourceID '" + dataSourceID + "'.");
        return;
    }
    //print(".processEditEvent() - " + evt.getEventTypeString() + ", " + dataSourceID);
    // Handle specific eventType
    switch(eventType) {
        case EditEvent.MOVE_TO:
            source.moveTo(evt.getPosition());
            break;
        case EditEvent.BEGIN_ADD:
            source.beginNewRow();
            break;
        case EditEvent.BEGIN_CHANGE:
            source.beginChangeRow();
            break;
        case EditEvent.BEGIN_DELETE:
            if (deleteConfirmed()) source.removeCurrentRow();
            break;
        case EditEvent.OKAY_ADD:
            source.commitNewRow();
            break;
        case EditEvent.OKAY_CHANGE:
            source.commitChangeRow();
            break;
        case EditEvent.CANCEL_ADD:
            source.rollbackNewRow();
            break;
        case EditEvent.CANCEL_CHANGE:
            source.rollbackChangeRow();
            break;
        case EditEvent.ACQUIRE_COLUMN_DEF:
            evt.setColumnDef(source.getColumnDef(
                evt.getColumnID()));
            break;
        case EditEvent.ROW_INDEX_ACTIVATED:
            rowIndexActivated(evt.getRowIndex());
            break;
        case EditEvent.ACQUIRE_DATABASE:
            evt.setDatabase(source.getDatabase());
            break;

        default:
            GenLib.error("DataEditor.processEditEvent()",
                "Unknown event type " + eventType);
            return;
    }
}
//---------- BeanActionClose Implementation --------------
public String canClose() {
    return null; // Yes *** later ask views if dirty
}
// Revert to state after setParam(), keep datastoreServices
public void close() {
    // Close views
    Enumeration enum = views.elements();
    while (enum.hasMoreElements()) {
        DataView view = (DataView)enum.nextElement();
        view.close();
    }
    views.clear();

    sources.clear(); // Close sources ???
    useParam = null;
    task = null;
    started = false;
    System.gc(); // To fix apparent bug when calling start() second time
    // *** Need to notify MainMenu to enable selection
}
//---------- BeanActionStart Implementation --------------
// *** Need to notify MainMenu to disable selection since started
public void start() {
    //if (DEBUG) BenchMark.endStep("DataEditor Pre Start");

    if (actualParam == null) getParam();
    //if (DEBUG) BenchMark.endStep("DataEditor getParam()");
    if (started) {
        // Help the user by putting the focus back on the views
        showViews();
        return;
    }
    if (datastoreServices == null) {
        datastoreServices = (DatastoreServices)
            containerServices.getService("DatastoreServices");
    }
    createTask();
    //if (DEBUG) BenchMark.endStep("DataEditor createTask()");

    initSources();   // Must be before initViews()
    if (DEBUG) BenchMark.endStep("DataEditor initSources()");

    initViews();
    if (DEBUG) BenchMark.endStep("DataEditor initViews()");

    startSources();  // Will fire event to populate views
    if (DEBUG) BenchMark.endStep("DataEditor startSources()");

    showViews();     // To avoid showing while populating
    if (DEBUG) BenchMark.endStep("DataEditor showViews()");

    started = true;
    //if (DEBUG) print(".start() TOTAL " + BenchMark.getTimeElapsed(1));
}
// Sets task, useParam. VERY awkward.
private void createTask() {
    Param settingsReplacedParam = actualParam;
    
    // Replace dynamic settings, a cool feature
    // Do this EACH time started, to use latest local value

    // Config param properties. Awkward, may mod as more added.
    if (configStartMessageName != null) settingsReplacedParam.put("StartMessageName", configStartMessageName);
    settingsReplacedParam = paramReplacer.apply(settingsReplacedParam);
    
    // Use factory to create task with param
    String paramType = settingsReplacedParam.getString("ParamType");
    TaskFactory factory = new TaskFactory();

    String dbName;
    if (paramType.startsWith("Config")) {
        dbName = settingsReplacedParam.getString("DataRequest.Database");
    } else {
        dbName = settingsReplacedParam.getString("MainSource.DataRequest.Database");
    }

    factory.setTaskContext(new TaskContext(this, dbName));
    task = factory.createTask(settingsReplacedParam);

    if (paramType.startsWith("Config")) {
        useParam = task.buildUseParam();
    } else {
        // Param is complete
        useParam = settingsReplacedParam;
    }
}
//---------- MessageListener Implementation --------------
public void processMessage(Message message) {
    if (actualParam == null) getParam();
    String name = message.getName();
    //print(".processMessage() - name = '" + name + "', getStartMessageName() = " + getStartMessageName());
    if (name == getStartMessageName()) start();
}
public String[] loadMessageInterests() {
    String name = getStartMessageName();
    if (name == null) {
        return new String[] {};
    } else {
        return new String[] {name};
    }
}
//---------- DataSourceSupplier Implementation -----------
// Returns null if not found
public DataSource getDataSource(String dataSourceID) {
    return (DataSource)sources.get(dataSourceID);
}
//---------- ContainerServicesUser Implementation --------
public void setContainerServices(ContainerServices services) {
    containerServices = services;
}
public String[] getContainerServicesInterests() {
    return new String[] {"DatastoreServices"};    
}
//---------- Properties ----------------------------------
public Schema getSchema(String dbName) {
    return datastoreServices.getSchema(dbName);
}
//----- configStartMessage
public void setStartMessageName(String name) {
    configStartMessageName = name.intern();    
}
//---------- Public Methods ------------------------------
public DataView getView(String viewName) {
    return (DataView)views.get(viewName);
}
/**
* Adds the text pair. When the Param is used all occurances
* of oldText will be replaced with the newText.
*/
public void addReplaceInParam(String oldText, String newText) {
    paramReplacer.add(oldText, newText);
}    
//---------- Private Methods -----------------------------
private String getStartMessageName() {
    if (actualParam == null) {
        return null;
    } else if (configStartMessageName != null) {
        return configStartMessageName;
    } else {
        return actualParam.getString("StartMessageName");
    }
}
// Show any views not visible, ignore rowIndex
// This will show detail for browse
private void rowIndexActivated(int rowIndex) {
    Enumeration enum = views.elements();
    while (enum.hasMoreElements()) {
        DataView view = (DataView)enum.nextElement();
        if (! view.isVisible()) view.setVisible(true);
    }
}
private void closeViewRequested(String viewName) {
    if (views.size() == 1) {
        close();
    } else {
        DataView view = getView(viewName);
        if (view.isPrimeView()) {
            close();
        } else {
            view.setVisible(false);
        }
    }
}
private void initViews() {
    views.clear();
    String text = useParam.getString("ViewNames");
    String[] names = DataLib.convertDelimStringToArray(text, ", ");
    for (int i = 0; i < names.length; i++) {
        String name = names[i];
        //if (DEBUG) print(".initViews() - Preparing view " + name);
        DataView view = new DataView();
        views.put(name, view);
        view.setViewName(name);
        view.setEditListener(this);
        view.setParam(useParam.getParam(name));
        view.init();
    }
}
private void initSources() {
    sources.clear();
    String text = useParam.getString("DataSourceIDs");
    String[] names = DataLib.convertDelimStringToArray(text, ", ");
    for (int i = 0; i < names.length; i++) {
        String name = names[i];
        DataSource source = new DataSource();
        sources.put(name, source);
        source.setID(name);
        source.setDatastoreServices(datastoreServices);
        source.setDataSourceSupplier(this);
        source.setParam(useParam.getParam(name));
    }
}
private void initChildSources() {
    Enumeration enum = sources.elements();
    while (enum.hasMoreElements()) {
        DataSource source = (DataSource)enum.nextElement();
        provideRowSetListeners(source);
        source.initChild();
    }
}
private void startSources() {
    initChildSources();
    Enumeration enum = sources.elements();
    while (enum.hasMoreElements()) {
        DataSource source = (DataSource)enum.nextElement();
        provideRowSetListeners(source);
        source.start();
    }
}
// Show only prime views
//      EditFields - Shows the only view
//      BrowseEditFields - Shows browse view
private void showViews() {
    Enumeration enum = views.elements();
    while (enum.hasMoreElements()) {
        DataView view = (DataView)enum.nextElement();
        if (view.isPrimeView()) {
            // Prevent block bug if view is modal
            new Viewer(view).showView();            
        }
    }
}
// For each view, gets its modules
// If the module has the same dataSourceID as the source
// then we call source.addRowSetListener(module);
private void provideRowSetListeners(DataSource source) {
    String dataSourceID = source.getID();
    Enumeration enum = views.elements();
    while (enum.hasMoreElements()) {
        DataView view = (DataView)enum.nextElement();
        Vector modules = view.getAllModules();
        for (int i = 0; i < modules.size(); i++) {
            Module module = (Module)modules.elementAt(i);
            String moduleDataSourceID = module.getDataSourceID();
            //print(".provideRowSetListeners() - moduleDataSourceID = " + moduleDataSourceID);
            if (moduleDataSourceID == null) continue;

            if (moduleDataSourceID.equals(dataSourceID)) {
                // The call
                source.addRowSetListener(module);
            }
        }
    }
}
// Return true if user really wants to delete
private boolean deleteConfirmed() {
    // First confirmation
    MessageBox box1 = new MessageBox();
    box1.setTitle("Delete Confirmation");
    String answer1 = box1.askYesNoBlock("Delete this record?");
    if (answer1.equals("No")) return false;
    // Second confirmation
    MessageBox box2 = new MessageBox();
    box2.setTitle("Second Delete Confirmation");
    String answer2 = box2.askYesNoBlock(
        "A delete is permanent.\n" +
        "Deleted records are gone forever.\n\n" +
        "Do you really want to delete this record?");
    return (answer2.equals("Yes") ? true : false);
}
//--- Std
private static void print(String text) {
    System.out.println("DataEditor" + text);
}
//========== Inner Classes ===============================
private class Viewer implements Runnable {

private DataView view;

public Viewer(DataView view) {
    this.view = view;
}
//---------- Runnable Implementation ---------------------
public void run() {
    try {
        view.setVisible(true);
    } catch(Exception ex) {
        GenLib.exception("TaskLogic.Viewer.run()",
            "Failure in view.setVisible(true)", ex);
    }
}
//---------- Public Methods ------------------------------
public void showView() {
    new Thread(this).start();
}
    
} // End inner class

} // End outer class
