package org.jcon.ba.system;

import org.jcon.param.Param;
import org.jcon.param.ParamDriven;
import org.jcon.util.TNode;
import java.io.File;

/**
 * An item is a node is a system tree. An item may have a
 * Basket and/or reusable bean. A system tree
 * is constructed by assemblying and configuring items.
 * The concept of composing system trees with reusable
 * Beans is the heart of the Bean Assembler.
 * <p>
 * An item's state can be directly manipulated in the tree
 * with a popup menu or through BasketServices. The State
 * Transformation Table for this magic is: <p> <pre>
 *
 * InitialState    Method      NewState
 *  CREATED         init()      INITIALIZED
 *  CREATED         start()     RUNNING
 *  INITIALIZED     start()     RUNNING
 *  RUNNING         pause()     PAUSED
 *  PAUSED          resume()    RUNNING
 *  RUNNING         close()     CREATED  </pre>
 *
 * @author Jack Harich
 */
public class Item implements java.io.Serializable {

//---------- Public Fields -------------------------------
// All related classes should use these constants
// The int values should remain in ascending sequence
public static final int CREATED     = 1;
public static final int INITIALIZED = 2;
public static final int RUNNING     = 3;
public static final int PAUSED      = 4;

//---------- Private Fields ------------------------------
private int         itemState = CREATED;
private ItemSystem  itemSystem;
private String      itemName;
private Basket      myBasket; // null if none
private BeanWrapper beanWrapper;   // null if none
private TNode       node;
private Object      markerClass; // null if use parent's
private final String      jarSeparator = "/";

//---------- Initialization ------------------------------
/**
 * The normal Item constructor, which declares the parent
 * item for this item. This allows a hierarchy of items.
 */
public Item(Item parentItem) {
    node = new TNode(this);
    node.setAllowsChildren(false); // Default, see setBasket()
    setParent(parentItem);
}
//---------- Properties ----------------------------------
//----- itemName
public void setItemName(String itemName) {
    this.itemName = itemName;
}
public String getItemName() {
    return itemName;
}
//----- itemSystem
public void setItemSystem(ItemSystem itemSystem) {
    this.itemSystem = itemSystem;
}
public ItemSystem getItemSystem() {
    return itemSystem;
}
//----- myBasket
public boolean isContainer() {
    return (myBasket != null ? true : false);
}
public void setBasket(Basket basket) {
    myBasket = basket;
    if (isContainer()) {
        node.setAllowsChildren(true);
    } else {
        node.setAllowsChildren(false);
    }
}
public Basket getBasket() {
    return (myBasket != null ? myBasket : getParentItem().getBasket() );
}
//----- beanWrapper
public boolean isBeanWrapper() {
    return (beanWrapper != null ? true : false);
}
public void setBeanInstance(Object beanInstance) {
    beanWrapper = new BeanWrapper();
    BasketServices basketServices = null;
    if (getParentItem() == null) {
        myBasket.getBasketServices();
    } else {
        basketServices = getParentItem().getBasket().getBasketServices();
    }
    beanWrapper.setInstance(beanInstance, basketServices);
}
public Object getBeanInstance() {
    return beanWrapper.getInstance();
}
//----- markerClass
/**
 * The root and containers reusing trees have a markerClass.
 * For other items this is null
 */
public void setMarkerClass(Object markerClass) {
    this.markerClass = markerClass;
}
/**
 * If my markerClass is null search upward until found.
 * RECURSIVE.
 */
public Object getMarkerClass() {
    if (markerClass == null) {
        return getParentItem().getMarkerClass();
    } else {
        return markerClass;
    }
}
//----- itemState related
public boolean isStartable() {
    if (itemState == CREATED || itemState == INITIALIZED) {
        return true;
    } else {
        return false;
    }
}
public boolean isPausable() {
    return (itemState == RUNNING ? true : false);
}
public boolean isResumable() {
    return (itemState == PAUSED ? true : false);
}
public boolean isClosable() {
    return ((itemState == RUNNING || itemState == PAUSED)
        ? true : false);
}
public int getItemState() {
//print(".getItemState() - state = " + itemState);
    return itemState;
}
public String getItemStateString() {
    if (itemState == CREATED) return "Created";
    if (itemState == INITIALIZED) return "Initialized";
    if (itemState == RUNNING) return "Running";
    if (itemState == PAUSED) return "Paused";
    return "#UnknownState#";
}
/**
 * Returns the normal item state if isContainer, else
 * returns the "true" state of the BeanWrapper.
 */
public int getTrueItemState() {
    if (isContainer()) {
        return itemState;
    } else {
        return beanWrapper.getTrueState();
    }
}
//----- Root and node related
public TNode getNode() {
    return node;
}
public TNode getRootNode() {
    return node.getRootNode();
}
public boolean isRoot() {
    return (getParentItem() == null ? true : false);
}
public Item getRootItem() {
    return (Item)getRootNode().getUserObject();
}
/**
 * Returns a period delimited path of item names. This
 * starts at the root if includeRoot is true.
 */
public String getPath(boolean includeRoot) {
    String path = "";
    TNode[] nodes = node.getPathNodes();
    int first = (includeRoot ? 0 : 1);
    for (int i = first; i < nodes.length; i++) {
        if (i > first) path += ".";
        Item item = (Item)nodes[i].getUserObject();
        path += item.getItemName();
    }
    return path;
}
//----- Other
public boolean isParamDrivenBean() {
    if (! isBeanWrapper()) {
        return false;
    } else {
        return beanWrapper.isParamDriven();
    }
}
/**
 * Sets the item's parent. This MUST be called after creating
 * a new Item with the no-arg constructor. If
 * parentItem is null then this must be the tree root item.
 * This method can be used to move items in a tree.
 */
public void setParent(Item parentItem) {
    node.removeFromTree();
    if (parentItem != null) {
        parentItem.getNode().add(node);
    }
}
public Item getParentItem() {
    TNode parentNode = node.getParentNode();
    if (parentNode == null) {
        return null;
    } else {
        return (Item)parentNode.getUserObject();
    }
}
/**
 * Returns the delimited marker path for this item. Due to
 * the BA design this starts at the parent item for a
 * BeanWrapper and at the item for a Basket.
 */
public String getMarkerClassPath(boolean isBasket) {
    if (isBasket) {
        return buildMarkerClassPath("");
    } else {
        return getParentItem().buildMarkerClassPath("");
    }
}
/**
 * Used for the ConfigLines invocations. If isBeanWrapper()
 * then the getBeanInstance() is returned, else if
 * isContainer() then getBasket() is returned, else null is
 * returned, which is usually an error.
 *
 * Thus if an Item is a BeanWrapper and a Container then
 * the BeanWrapper "shadows" the Container, and should
 * implement appropriate interfaces such as MessageListener,
 * MessageSource, etc.
 */
public Object getInvocationInstance() {
    if (isBeanWrapper()) return getBeanInstance();
    if (isContainer()) return getBasket();
    return null;
}
//---------- Public Methods ------------------------------
// NOTE how we call Basket before Bean in all cases except
// close().
// *** Assert proper state ***
//----- The "Big 6"
public void init() {
    if (beanWrapper != null) beanWrapper.init();
    itemState = INITIALIZED;
}
public void start() {
    if (myBasket != null) myBasket.start();
    if (beanWrapper != null) beanWrapper.start();
    itemState = RUNNING;
}
public void pause() {
    if (myBasket != null) myBasket.pause();
    if (beanWrapper != null) beanWrapper.pause();
    itemState = PAUSED;
}
public void resume() {
    if (myBasket != null) myBasket.resume();
    if (beanWrapper != null) beanWrapper.resume();
    itemState = RUNNING;
}
public String canClose() {
    if (myBasket != null) {
        String problem = myBasket.canClose();
        if (problem != null) return problem;
    }
    if (beanWrapper != null) {
        return beanWrapper.canClose();
    } else {
        return null; // Yes
    }
}
public void close() {
    if (beanWrapper != null) beanWrapper.close();
    if (myBasket != null) myBasket.close();
    itemState = CREATED;
}
//---------- Private Methods -----------------------------
// RECURSIVE - Stops when ??? my markerClass != null
private String buildMarkerClassPath(String path) {
    //if (getParentItem().getMarkerClass() == null) {
    //if (getParentItem().markerClass == null) {
    if (markerClass == null) {
		// CMC Changed from File.separator to forward slash because
		// File.seperator causes failure when using jar files.
        // old: path = itemName + File.separator + path;
        path = itemName + jarSeparator + path;

        //print(".buildMarkerClassPath() - appended path = '" + path + "'");

        return getParentItem().buildMarkerClassPath(path);
    } else {
        //print(".buildMarkerClassPath() - final path = '" + path + "'");
        return path;
    }
}
//--- Std
private static void print(String text) {
    System.out.println("Item" + text);
}

} // End class
