package org.jcon.ba.system;

import org.jcon.param.*;
import org.jcon.util.DataLib;
import org.jcon.util.GenLib;
import org.jcon.util.msg.Message;
import org.jcon.util.service.ContainerServices;
import java.io.File;
import java.util.Hashtable;

/**
 * Represents a system of Items. Key responsibilities are:
 * <p> <pre>
 * - Creating an ItemSystem by bootstrapping the root item.
 *
 * - Create various instances. This is centralized here due
 * to the complexity and tight coupling involved, and to
 * make future modifications easier.
 *
 * - getItemForInstance() to support MessageChain. </pre>
 *
 * @author Jack Harich
 */
public class ItemSystem {

//---------- Private Fields ------------------------------
private ItemSystemMgr itemSystemMgr;
private Object        rootMarkerClass;
private Item          rootItem;
private ParamStore    paramStore;

// Key = Object Basket, Object = Item
private Hashtable basketItems = new Hashtable();

// Key = Object bean instance, Object = Item
private Hashtable beanInstanceItems = new Hashtable();

//---------- Initialization ------------------------------
public ItemSystem(String markerClassName,
        ItemSystemMgr itemSystemMgr) {
    this.itemSystemMgr = itemSystemMgr;
    paramStore = itemSystemMgr.getParamStoreDefault();
    rootItem = createRootItem(markerClassName);
}
//---------- Properties ----------------------------------
public Object getRootMarkerClass() {
    return rootMarkerClass;
}
public Item getRootItem() {
    return rootItem;
}
public ItemSystemMgr getItemSystemMgr() {
    return itemSystemMgr;
}
public ParamStore getParamStore() {
    return paramStore;
}
//---------- Public Methods ------------------------------
// Root needs to have optional BeanWrapper, how ??? later
private Item createRootItem(String markerClassName) {
    // Bootstrap so use bogus creator ItemDef
    ItemDef rootItemDef = new ItemDef();
    rootItemDef.setMarkerClassName(markerClassName);
    rootItemDef.setContainer(true);

    // Possibly an ItemSystemMarker
    Object marker = GenLib.createInstance(markerClassName);
    if (marker instanceof ItemSystemMarker) {
        ItemSystemMarker rootMarker = (ItemSystemMarker)marker;
        // Set custom features
        if (rootMarker.getParamStore() != null) {
            paramStore = rootMarker.getParamStore();
        }
        if (rootMarker.getBeanClassName() != null) {
            // Mimic container ItemDef definition
            rootItemDef.setBeanClassName(
                rootMarker.getBeanClassName());
            // A class name, not an item name
            // *** Need setParamMarkerName() ***
            rootItemDef.setParamMarkerItemName(
                rootMarker.getParamMarkerClassName());
            rootItemDef.setParamFileName(
                rootMarker.getParamFileName());
        }
    }
    // Must use createItem() for consistency

    // The root name is not used anywhere yet for dependency,
    // just for visual, use markerClassName. However it is
    // used as key by ItemSystemMgr.
    return createItem(markerClassName, rootItemDef, null);
}
public Item createItem(String itemName, ItemDef itemDef,
        Item parentItem) {
//print(".createItem() " + itemName + " - Entered");
    Item item = new Item(parentItem);
    item.setItemName(itemName);
    item.setItemSystem(this);
    // MarkerClass - null or set
    String markerClassName = itemDef.getMarkerClassName();
    if (markerClassName != null) {
//print(".createItem() " + itemName + " - setting MarkerClass with " + markerClassName);
        // *** Better to get from parent item's basket ???
        Object marker = GenLib.createInstance(markerClassName);
        item.setMarkerClass(marker);
    }
    // Basket - null or set
    if (itemDef.isContainer()) {
        item.setBasket(createBasket(item));
    }
    // Bean wrapper - null or set
    String beanClassName = itemDef.getBeanClassName();
    if (beanClassName != null) {
        Object instance = GenLib.createInstance(beanClassName);
        item.setBeanInstance(instance);
        if (instance instanceof ParamDriven) {
            provideBeanParam(item, itemDef);
        }
    }
    // Done
    itemCreated(item);
    return item;
}
/**
 * This MUST be called when an item is being removed from
 * the system.
 */
public void removeItem(Item item) {
    if (item.isContainer()) {
        basketItems.remove(item.getBasket());
    }
    if (item.isBeanWrapper()) {
        beanInstanceItems.remove(item.getBeanInstance());
    }
}
/**
 * Returns the item containing the instance. This one of the
 * crucial responsibilities of this class. To keep this
 * method reliable items must be created and removed from
 * a system using this class. Also, an item's Basket or
 * BeanWrapper should never be changed without notifying
 * this class.
 */
public Item getItemForInstance(Object instance) {
    if (instance instanceof Basket) {
        return (Item)basketItems.get(instance);
    } else {
        return (Item)beanInstanceItems.get(instance);
    }
}
// Returns rebuilt Param or same Param if no change
public static Param buildParam(Param param, ContainerServices cs) {
    String originalText = param.getText();
    Message message = new Message("BuildParam");
    message.set("OldParamText", originalText);
    cs.sendMessage(message);
    String fullText = message.getString("NewParamText");
    
    if (fullText != null) {
        //print(".buildParam() - fullText received");    
        return param.cloneUsingFullOriginalText(fullText, originalText); 
        
    } else {
        //print(".buildParam() - no fullText"); 
        return param;
    }
}
//---------- Private Methods -----------------------------
private Basket createBasket(Item ownerItem) {
//print(".createBasket() - " + ownerItem.getItemName() + " MC " + ownerItem.getMarkerClass());
    ParamAccessor accessor = new ParamAccessor();
    accessor.setMarkerClass(ownerItem.getMarkerClass());
// *** Bug potential here ***
    // (isBasket) = true
    String resourceName =
        ownerItem.getMarkerClassPath(true) + "_Container.parex";
    accessor.setResourceName(resourceName);

    Param param = paramStore.readParam(accessor);
    Basket basket = new Basket();
    basket.setOwnerItem(ownerItem); // item null if root 
    basket.setParam(buildParam(param, basket.getContainerServices()));
    return basket;
}
private void itemCreated(Item item) {
    if (item.isContainer()) {
        basketItems.put(item.getBasket(), item);
    }
    if (item.isBeanWrapper()) {
        beanInstanceItems.put(item.getBeanInstance(), item);
    }
}
private void provideBeanParam(Item item,
            ItemDef itemDef) {

    String itemName = itemDef.getItemName();
    String paramMarkerClassName = itemDef.getParamMarkerClassName();
    
    // Get paramMarkerItemName
    String paramMarkerItemName = itemDef.getParamMarkerItemName();
    if (paramMarkerItemName == null) paramMarkerItemName = "this"; // Default

    // Get paramFileName
    String paramFileName = itemDef.getParamFileName();
    if (paramFileName == null) paramFileName =
        DataLib.upperCaseFirstLetter(itemName) + ".parex"; // Default

    // Get markerInstance, possibly build paramFileName
    Object markerInstance;
    if (paramMarkerClassName != null) {
        markerInstance = GenLib.createInstance(paramMarkerClassName);
        // The paramFileName will be custom, set above.
    
    } else if (paramMarkerItemName.equals("this")) {
        // *** This section UNTESTED except in root - JH
        markerInstance = item.getMarkerClass();
        String path = item.getMarkerClassPath(false);
        paramFileName = path + paramFileName;
        
    } else if (paramMarkerItemName.equals("root")) {
        markerInstance = item.getRootItem().getMarkerClass(); 
        
    } else if (item.isRoot()) {
        // Root bean must have class name in paramMarkerItemName
        markerInstance = GenLib.createInstance(paramMarkerItemName);
        
    } else {
        // paramMarkerItemName is an item name.
        // Most common. Use default param location, which
        // is in the basket's folder
        Basket basket = item.getParentItem().getBasket();
        markerInstance = basket.getLiveItem(paramMarkerItemName)
            .getBeanInstance();
    }
    // Get and set Param
    ParamAccessor accessor = new ParamAccessor();
    accessor.setMarkerClass(markerInstance);
    accessor.setResourceName(paramFileName);

    ItemParamSupplier supplier = new ItemParamSupplier(
        paramStore, accessor,
        item.getParentItem().getBasket().getContainerServices());

    ParamDriven paramDriven = (ParamDriven)item.getBeanInstance();
    if (paramDriven instanceof ParamDrivenSupply) {
        ((ParamDrivenSupply)paramDriven).setParamSupplier(supplier);
    } else {
        supplier.supplyParam(paramDriven);
    }
}
//--- Std
private static void print(String text) {
    System.out.println("ItemSystem" + text);
}

} // End class
