package org.jcon.ba.system;

import org.jcon.param.Param;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import org.jcon.util.msg.MessageListener;
import java.util.Vector;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

/**
 * Defines a method to be invoked by a module while
 * initializing itself. A collection of invocations thus
 * defines how a module initializes its contents.
 *
 * If instanceName starts with $ then the item's BeanWrapper
 * instance will be used. This is useful if an item is
 * both a Container and BeanWrapper. See related doc in
 * Basket.
 *
 * @author Jack Harich
 */
public class Invocation {

//---------- Private Fields ------------------------------
private String instanceName;
private String methodName;
private Vector args = new Vector();
private String originalLine; // For debugging

//---------- Initialization ------------------------------
/**
 * Creates an Invocation with a particular instance and
 * method name. If the method has any arguments they must
 * be added using addArgument().
 */
public Invocation(String instanceName, String methodName) {
    this.instanceName = instanceName;
    this.methodName = methodName;
}
/**
 * Creates an Invocation from a String, which must follow
 * a particular format for parsing. Currently
 * we support only arguments of the format:
 *      itemName.methodName()
 *      itemName.methodName(argType argValue, <more>)
 * argType may be ML, Object or a className.
 * (See code in addArgument() for argTypes supported)
 * argValue is itemName or literal matching argType.
 * No argType may be used for String, boolean or int.
 */
public Invocation(String line) {
    try {
        line = line.trim();
        originalLine = line;
        // Parse itemName
        // *** unreliable if (line.indexOf(".") == -1) line = "this." + line;
        int dotIndex = line.indexOf(".");

        instanceName = line.substring(0, dotIndex);
        line = line.substring(dotIndex + 1).trim();
        // Parse methodName
        int openParenIndex = line.indexOf("(");
        methodName = line.substring(0, openParenIndex);
        line = line.substring(openParenIndex + 1).trim();
        // Parse arguments
        if (line.startsWith( ")" )) {
            return; // No arguments
        } else {
            // Remove ")" at end of line
            line = line.substring(0, line.length() - 1);
            parseArguments(line);
        }
    //print("() - line = '" + line + "'");
    } catch(Exception ex) {
        GenLib.exception("Invocation()", "Constructor" +
            " failure on line:\n'" + line + "'", ex);
        System.exit(1);
    }
}
// Line is what's between open and close parens
private void parseArguments(String line) {
    while (true) {
        line = line.trim();
        // Set commaIndex, -1 if none
        int commaIndex = -1;
        if (line.startsWith("\"")) {
            // Find first comma after second quote
            int secondQuoteIndex = line.indexOf("\"", 1);
            commaIndex = line.indexOf(",", secondQuoteIndex);
        } else {
            commaIndex = line.indexOf(",");
        }

/**        // Find first comma not in quotes
        int commaIndex;
        int firstQuoteIndex = line.indexOf("\"");
        if (firstQuoteIndex == -1) {
            commaIndex = line.indexOf(",");
        } else {
            // Assume first quote is first character
            int secondQuoteIndex = line.indexOf("\"", firstQuoteIndex + 1);
print("secondQuoteIndex = " + secondQuoteIndex);
            commaIndex = line.indexOf(",", secondQuoteIndex);
        } */

        // Extract portions
        String thisArg;
        if (commaIndex == -1) { // Last arg found
            thisArg = line;
            line = null;
        } else {
            thisArg = line.substring(0, commaIndex);
            line = line.substring(commaIndex + 1).trim();
            //print(" - more args on line '" + line + "'");
        }
        // Parse this argument
//print(" - thisArg = '" + thisArg + "' " + commaIndex);
        parseOneArgument(thisArg);
        // Done ?
        if (line == null) break;
    }
}
private void parseOneArgument(String line) {
    String argType = null;
    String argValue = null;
    // String argType
    if (line.startsWith("\"") ) {
        argType = "String";
        line = line.substring(1);
        int quoteIndex = line.indexOf("\"");
        argValue = line.substring(0, quoteIndex);
    // int
    } else if (DataLib.isInt(line)) {
        argType = "int";
        argValue = line;
    // boolean - DataLib.isBoolean() not used for speed
    } else if (line.equals("true") || line.equals("false")) {
        argType = "boolean";
        argValue = line;
    } else {
        // Parse argType, which cannot contain a space
        int spaceIndex = line.indexOf(" ");
        argType = line.substring(0, spaceIndex);
        line = line.substring(spaceIndex + 1).trim();
        // Parse argValue
        argValue = line;
     }
    //print(" argType = '" + argType + "', value = '" + argValue + "'");
    addArgument(argType, argValue);
}
//---------- Public Methods ------------------------------
/**
 * Invokes the method using module items, returning the
 * method result, which is null if a void method.
 */
public Object invoke(Basket basket) {
    //print(".invoke() - " + originalLine);
    Object instance = basket.getItemInstance(instanceName);
    // If start() call through Item. Important feature.
    if (methodName.equals("start") && args.size() == 0) {
        if ((instance instanceof BeanActionStart) ||
                (instance instanceof Basket)) {
            basket.getLiveItem(instanceName).start();
            return null;
        }
    }
    // Build argTypes, argValues
    Class[]  argTypes  = new Class[args.size()];
    Object[] argValues = new Object[args.size()];

    for (int i = 0; i < argValues.length; i++) {
        Arg arg = (Arg)args.elementAt(i);
        argTypes[i] = arg.type;
        if (arg.isItem) {
            argValues[i] = basket.getItemInstance(arg.stringValue);
            //print(" - " + i + " getting item " + arg.stringValue + ", value = " + argValues[i]);
            
        } else {
            argValues[i] = arg.value;
        }
    }
    // Create Method, invoke
    //print(" argTypes.length " + argTypes.length);
    //print(" argTypes[0] " + argTypes[0]);
    try {
        Method method = instance.getClass().getMethod(
            methodName, argTypes);
        return method.invoke(instance, argValues);

    } catch(NoSuchMethodException ex) {
        // May occur if Basket has Bean. 
        // If instance is basket, try the bean
        if ((instance instanceof Basket) &&
                instanceName != "this" &&
                basket.getLiveItem(instanceName).isBeanWrapper()) {
            Object beanInstance = basket.getLiveItem(instanceName).getBeanInstance();
            try {
                Method method = beanInstance.getClass().getMethod(
                    methodName, argTypes);
                return method.invoke(beanInstance, argValues);  
                
            } catch(Exception ex2) {
                // *** We should differentiate the display
                // To show this is a basket with a bean
                return showInvokeException(ex2, instanceName,
                    instance, methodName, originalLine);            
            }
        } else {
            return showInvokeException(ex, instanceName,
                instance, methodName, originalLine);
        }
    } catch(Exception ex) {
        return showInvokeException(ex, instanceName,
            instance, methodName, originalLine);
    }
}
//---------- Private Methods -----------------------------
private Object showInvokeException(Exception ex,
                String instanceName, Object instance, 
                String methodName, String originalLine) {
                
    GenLib.exception("Invocation.invoke()", "Failure on " +
        "instanceName: " + instanceName + " " + instance.getClass() +
        "\n methodName: " + methodName +
        "\n line: " + originalLine, ex);
    return null;    
}  
private static void print(String text) {
    System.out.println("Invocation" + text);
}
private void addArgument(String argType,
        String argValue) {

    Arg arg = new Arg(argType, argValue);

    // *** Support more types later *** 99
    if (argType.equals("ML")) {
        arg.type  = MessageListener.class;
        arg.value = argValue;
        arg.isItem = true;

    } else if (argType.equals("Object")) {
        arg.type  = Object.class;
        arg.value = argValue;
        arg.isItem = true;

    } else if (argType.equals("String")) {
        arg.type  = String.class;
        arg.value = argValue;

    } else if (argType.equals("boolean")) {
        arg.type  = Boolean.TYPE;
        arg.value = Boolean.valueOf(argValue);

    } else if (argType.equals("int")) {
        arg.type  = Integer.TYPE;
        arg.value = Integer.valueOf(argValue);

    } else { // Args are className and itemName
        arg.initNamedItem();
    }
    args.addElement(arg);
}
//========== Inner Classes ===============================
class Arg {
    boolean isItem = false;

    Class   type;   // For creating Method
    Object  value;  // For method.invoke() args[]

    String stringType; // ClassName if isItem
    String stringValue; // ItemName if isItem

    Arg(String stringType, String stringValue) {
        this.stringType = stringType;
        this.stringValue = stringValue;
    }
    void initNamedItem() {
        isItem = true;
        try {
            type = Class.forName(stringType);
        } catch(Exception ex) {
            GenLib.exception("Invocation.Arg()",
                "Cannot get class for '" + stringType + "'", ex);
            ex.printStackTrace();
        }
    }

} // End inner class

} // End outer class
