package org.jcon.ba.lib;

import org.jcon.ba.system.BasketServices;
import org.jcon.ba.system.BeanActionInit;
import org.jcon.ba.system.BeanPriv;
import org.jcon.param.Param;
import org.jcon.param.ParamDriven;
import org.jcon.param.ParamDrivenInfo;
import org.jcon.util.DataLib;
import org.jcon.util.PropMap;
import org.jcon.util.msg.Message;
import org.jcon.util.msg.MessageDef;
import org.jcon.util.msg.MessageService;
import java.util.Enumeration;

/**
* This class provides systemwide properties (SPs).
* Conceptually a property is a key/value pair where the
* key is a unique string in the system and the value is
* any object including null. Properties are used for 
* configuring the system to behave differently for different
* customers, markets, or runtime situations.
*
* <p>
* SPs are define with initial values in the parex file.
* ex:
 <pre>
* // SystemProperties.parex
* // null values are  represented by not having a value
* SystemProperties has:
*     host= is: 127.0.0.1
*     port= is: 
*     End: SystemProperties
* </pre>
*
* These values can be overridden with command line arguments.
*
* ex:
* <pre>
*   java <i>root class name</i> host=255.255.255.255
*   java <i>root class name</i> port=8001 host=1.1.1.1 
* </pre>
*
* <p>
* SPs that can be dynamically set will be defined in the future
* through the use of messages.
*
* <p>
* SPs are read and used by messages.
*
* <p>
* To use this class, use setMessageService("AcquireSystemArgs", ...) in the
* system root container parex, and then get the desired
* property via a ContainerServices acquire Message whose
* name is "AcquireSystemProperty".
*
* <p>
* The returned Message will contain the property 
* "CommandArgs" which has the value String[]. These are 
* the exact command args and will be null if none.
*
* <p>
* If the Message contains the property "PropertyName" then
* this class will return the Object value of that name or
* null if none in the property "PropertyValue". 
* If the property "PropertyName" does not exist then an
* IllegalArgumentException will be thrown. 
*
* <p> The 
* Message can then be queried with the usual
* methods such as isTrue(), getInt(), get(), etc using the
* property name "PropertyValue".
*
* <p>
* If the Message contains the property "CommandArgName"
* then the Message property "HasCommandArg" will be set to
* true if the CommandArgs array has that value and false
* if not. This is a convenience, since the same result
* could be obtained with the CommandArgs value. This is
* case insensitive.
*
* <p>
* Property names are case insensitive. This class converts
* them all to lowercase for internal use. A property name
* can identify a nested value with period delmited keys
* ex: 
* <pre>
* translator.port
* </pre>
*
* <p>
* This is a good example of a reusable BA business object
* with a single, small, well chosen responsibility.
*
* @author Jack Harich
* @author revised October 30, 1998 by Joshua Marinacci
*/

/* quick synop */

public class SystemProperties implements BeanPriv,
	MessageService, ParamDriven, BeanActionInit {
	
//---------- Private Fields ------------------------------
// Properties
private BasketServices basketServices;
private Param param;
// Internal
private String[]       originalCommandArgs;    // original command line arguments

protected Param properties;
		
//---------- BeanPriv Implementation ---------------------
// Called BEFORE ConfigLines, such as addCommandProperty()
// this will set all of the bean
public void setBasketServices(BasketServices services) {
    basketServices = services;
    originalCommandArgs = (String[])basketServices.getPropMap().get("CommandArgs");
}
//---------- MessageService Implementation ---------------
public void serviceMessage(Message message) {
    String messageName = message.getName();

    if (message.getName().equals("AcquireSystemProperty")) {
        // print(".serviceMessage() - Returning args " + originalCommandArgs);
        message.set("CommandArgs", originalCommandArgs);
        
        providePropertyValue(message);
		checkCommandArgRequest(message);

    } else {
        print(".processMessage() - Unknown messageName '" + messageName + "'");
    }
}
public MessageDef[] getServiceMessageDefs() {
    MessageDef[] defs = new MessageDef[1];
    defs[0] = new MessageDef("AcquireSystemProperty", this);
    
    defs[0].add("CommandArgs", String[].class, "The system command line arguments.");
    
    defs[0].add("PropertyName", String.class, "The requested property name.");    
    defs[0].add("PropertyValue", Object.class, "The requested value or null.");    
    
    defs[0].add("CommandArgName", String.class, "The command arg value.");    
    defs[0].add("HasCommandArg", Boolean.TYPE, "True if we have the command arg value.");    
    
    return defs;
}    	
//---------- ParamDriven Implementation ------------------
public void setParam(Param param) {
	this.param = param;	
}
public Param getParam() {
	return param;
}
public boolean applyNewParam(Param newParam) {
	setParam(newParam);
	return true;
}
public ParamDrivenInfo getParamDrivenInfo() {
	return null;
}
//---------- BeanActionInit Implementation ---------------	
public void init() {
	//	print(".init() - param is: " + param);

	properties = new Param();
	// get references to the two Params
    Param otherProperties = param.getParam("OtherProperties");    
    mergeParam(otherProperties, properties);
	//print(" - LocaleID = " + properties.getString("LocaleID"));
    // Convert keys to lowercase
    Param cmdParam = new Param();
    mergeParam(param.getParam("CommandLineProperties"), cmdParam);
	
	// merge the command line Param into the properties Param.
	mergeParam(cmdParam, properties);
	//print(" - LocaleID = " + properties.getString("LocaleID"));
	
	// Always null if run in Bean Assembler editor
	if (originalCommandArgs == null) return;
	    
    String[] cmdKeys = cmdParam.getDataKeys();
    // --- For each actual command line arg
    for (int i = 0; i < originalCommandArgs.length; i++) {
        String arg = originalCommandArgs[i];
    	// --- Search through the command line Param keys
    	for (int j = 0; j < cmdKeys.length; j++) {
    	    String cmdKey = cmdKeys[j];
            // If the command line argument starts with
            // an already defined command line Param key,
            // then replace it in the properties
            if (startsWithIgnoreCase(arg, cmdKey)) {
            	String value = arg.substring(cmdKey.length());
            	properties.put(cmdKey, value);
            }
    	}
    }
}
// ******** MOVE TO DataLib AFTER TESTING
/**
* Returns true if the text starts with the prefix, false
* if not. This is a case insensitive test designed to make
* case insensitive work and hence higher usability easier.
*/
private boolean startsWithIgnoreCase(String text, String prefix) {
	boolean startsWith = false;
	if (prefix.length() > text.length()) {
		//print(" - startsWith = false");
		startsWith = false;
	} else {
		String textPrefix = text.substring(0, prefix.length());
		//print(" - Before text = " + text + ", textPrefix = " + textPrefix);
		startsWith = prefix.equalsIgnoreCase(textPrefix);
	}
	//print(" - Returning " + startsWith);
	return startsWith;
}
/** 
* Merges everything in source into destination. Also
* converts all keys to lowercase for case insensitivity.
*/
public void mergeParam(Param source, Param destination) {
	String[] keys = source.getDataKeys();
	for(int i=0; i<keys.length; i++) {
		String key = keys[i];
        destination.put(key.toLowerCase(), source.get(key));
	}
}
/** If the text begins with the String prefix, then this
* method will return all text after the prefix, else it will
* return null.
*/
public String getPrefixValue(String text, String prefix) {
    if(text.startsWith(prefix)) {
        return DataLib.replaceToken(text, prefix, "");
    } else {
        return null;
    }
}	
//---------- Private Methods -----------------------------	
// only get stuff out of properties instead of multiple
// propmaps.
private void providePropertyValue(Message message) {
    if (message.containsKey("PropertyName")) {
        String propertyName = message.getString("PropertyName").toLowerCase();
        Object value = null;
        
        if (properties.get(propertyName) != null) {
            value = properties.get(propertyName);            
        } else {
        	// it's not here so we do nothing ??  and set the message to null ??
            print(".checkPropertyRequest() - Property name '"
            + propertyName + "' not found, returning null, value = " + value);
        }
        message.set("PropertyValue", value);
	//    	print(".providePropertyValue() - Provided '" + value + "' for " + propertyName);
	//	print(" props = " + properties);
    }
}
private void checkCommandArgRequest(Message message) {
    if (message.containsKey("CommandArgName")) {
        String argName = 
            message.getString("CommandArgName").toLowerCase();
        boolean contains = DataLib.arrayContainsValueIgnoreCase(originalCommandArgs, argName);
        message.setBoolean("HasCommandArg", contains);
    }
}
//--- Std
private static void print(String text) {
    System.out.println("SystemProperties" + text);
}

} // End class
