package org.jcon.util.setting;

import org.jcon.param.util.build.BuildParamEvent;
import org.jcon.param.util.build.BuildParamListener;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import java.util.Hashtable;
import java.util.Vector;

/**
 * This class manages Setting storage and retreival.
 * A "Setting" is a user editable system property. We use
 * the word "Setting" to avoid confusion with "Parameter".
 * <p>
 * Note that if the database is manually edited, changes
 * show up here because we (currently) read the database
 * each time. If this slows the app then the value should
 * be cached. This applies to getActualValue().
 *
 * @author Jack Harich
 */
public class SettingStore implements BuildParamListener {

//---------- Private Fields ------------------------------
private SettingSource settingSource;
private SettingDefMgr settingDefMgr;

// Key = setting name, Object = String value
private Hashtable localValues = new Hashtable();

// All String keys
private Vector buildParamKeys = new Vector();

private static final String NULL  = "#NULL#";

//---------- BuildParamListener Implementation -----------
public String[] getBuildParamKeys() {
    return DataLib.convertVectorToStringArray(buildParamKeys);
}
public void processBuildParamEvent(BuildParamEvent evt) {

    // We handle all the same and assume the key was
    // set correctly in _Container.parex
    String value = getLocalValue(evt.getBuildValue());
    //print(".replaceParamText() - value = '" + value + "'");
    evt.setReplacement(value);
}
//----- Associated Methods
public void addBuildParamKey(String key) {
    //print(".addBuildParamKey() - key = " + key);
    buildParamKeys.addElement(key);
}
public void removeBuildParamKey(String key) {
    buildParamKeys.removeElement(key);
}
//---------- Properties ----------------------------------
//----- settingSource, required
public void setSettingSource(SettingSource settingSource) {
    this.settingSource = settingSource;    
}
//----- settingDefMgr, required
public void setSettingDefMgr(SettingDefMgr settingDefMgr) {
    this.settingDefMgr = settingDefMgr;
}
//---------- Public Methods ------------------------------
/**
 * Sets the "local value" for the name, replacing any
 * previous local value. Null values are supported. The
 * local overrides the stored value. thus allowing different
 * processes to use different values. For example the user
 * may set the local value of the setting named
 * "Awards.CurrentFiscalYear".
 * <p>
 * Throws an IllegalArgumentException if the Setting is
 * not in the schema.
 */
public void setLocalValue(String name, String value) {
    assertSettingExists(name);
    if (value == null) value = NULL;
    localValues.put(name, value);
}
private void assertSettingExists(String name) {
    if (! settingDefMgr.hasSettingDef(name)) throw new
        IllegalArgumentException("Setting '" + name + "' is not defined.");    
}
/**
 * Returns the local value of the named setting. 
 * The algorithm is: <p> <pre>
 * - Check local values 
 * - If not found, check database
 * - If not found, add initial row to database
 * - Return value
 * </pre> <p>
 * Note local values override stored values.
 * <p>
 * Throws an IllegalArgumentException if the Setting is
 * not in the schema.
 */
public String getLocalValue(String name) {
    // Check local values
    String value = (String)localValues.get(name);
    if (value == NULL) return null;
    if (value != null) return value;
    
    // If not found, check database
    // If not found, add initial row to database
    return getActualValue(name);
} 
/**
 * Returns the actual value of the Setting in the database.
 * If not found then the initial row is automatically added.
 * <p>
 * Throws an IllegalArgumentException if the Setting is
 * not in the schema.
 */
public String getActualValue(String name) {
    assertSettingExists(name);
    return readValue(name); // Move here ???
}
/**
 * Returns the requested SettingDef, which is useful for
 * driving a setting editor. 
 * <p>
 * Throws an IllegalArgumentException if the Setting is
 * not in the schema.
 */
public SettingDef getSettingDef(String name) {
    return settingDefMgr.getSettingDef(name);
}

//---------- Private Methods -----------------------------
// Returns the value
private String readValue(String name) {
    ReadSettingResponse response = settingSource.readSetting(name);
    
    switch(response.getResponseType()) {
        case ReadSettingResponse.EXCEPTION:
            GenLib.exception("SettingStore.readValue()",
                response.getFailureText(), 
                response.getFailureException());
            return null; // Failure

        case ReadSettingResponse.NOT_IN_DATABASE:
            // Row not in database so add it
            return addInitialSetting(name);            

        case ReadSettingResponse.IN_DATABASE:
            return response.getValue();

        default:
            throw new IllegalStateException(
                "Unknown response type " + response.getResponseType());
    }
}
// Adds the first row for this name, using InitialValue
private String addInitialSetting(String name) {
  
    SettingDef def = getSettingDef(name);
    String initialValue = def.getInitialValue();
    
    String problem = settingSource.addInitialSetting(name, initialValue);
    if (problem != null) {
         GenLib.error("SettingStore.addInitialSetting()",
             problem + "\nReturning initial value anyhow.");   
    }
    return initialValue;
}
//--- Std
private static void print(String text) {
    System.out.println("SettingStore" + text);
}

} // End class
