/**
 * Portions Copyright 2001-2003 Sun Microsystems, Inc.
 * Portions Copyright 1999-2001 Language Technologies Institute, 
 * Carnegie Mellon University.
 * All Rights Reserved.  Use is subject to license terms.
 * 
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL 
 * WARRANTIES.
 */
package com.sun.speech.freetts;

import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Represents a node in a Relation. Items can have shared contents but 
 * each item has its own set of Daughters. The shared contents of an
 * item (represented by ItemContents) includes the feature set for the
 * item and the set of all relations that this item is contained in.
 * An item can be contained in a number of relations and as daughters
 * to other items. This class is used to keep track of all of these
 * relationships. There may be many instances of item that reference
 * the same shared ItemContents. 
 */
public class Item implements Dumpable {
    private Relation ownerRelation;
    private ItemContents contents;
    private Item parent;
    private Item daughter;
    private Item next;
    private Item prev;


    /**
     * Creates an item. The item is coupled to a particular
     * Relation. If shared contents is null a new sharedContents is
     * created.
     *
     * @param relation the relation that owns this item
     * @param sharedContents the contents that is shared with others.
     *   If null, a new sharedContents is created.
     */
    public Item(Relation relation, ItemContents sharedContents) {
	ownerRelation = relation;
	if (sharedContents != null) {
	    contents = sharedContents;
	} else {
	    contents = new ItemContents();
	}
	parent = null;
	daughter = null;
	next = null;
	prev = null;


	getSharedContents().addItemRelation(relation.getName(), this);
    }

    /**
     * Finds the item in the given relation that has the same shared
     * contents.
     *
     * @param relationName the relation of interest
     *
     * @return the item as found in the given relation or null if not
     *   found
     */
    public Item getItemAs(String relationName) {
	return getSharedContents().getItemRelation(relationName);
    }


    /**
     * Retrieves the owning Relation.
     *
     * @return the relation that owns this item
     */
    public Relation getOwnerRelation() {
	return ownerRelation;
    }

    /**
     * Retrieves the shared contents for this item.
     *
     * @return the shared item contents
     */
    public ItemContents getSharedContents() {
	return contents;
    }

    /**
     * Determines if this item has daughters.
     *
     * @return true if this item has daughters
     */
    public boolean hasDaughters() {
	return daughter != null;
    }

    /**
     * Retrieves the first daughter of this item.
     *
     * @return the first daughter or null if none
     */
    public Item getDaughter() {
	return daughter;
    }
    
    /**
     * Retrieves the Nth daughter of this item.
     *
     * @param which the index of the daughter to return
     *
     * @return the Nth daughter or null if none at the given index
     */
    public Item getNthDaughter(int which) {
	Item d = daughter;
	int count = 0;
	while (count++ != which && d != null) {
	    d = d.next;
	}
	return d;
    }

    /**
     * Retrieves the last daughter of this item.
     *
     * @return the last daughter or null if none at the given index
     */
    public Item getLastDaughter() {
	Item d = daughter;
	if (d == null) {
	    return null;
	}
	while (d.next != null) {
	    d = d.next;
	}
	return d;
    }

    /**
     * Adds the given item as a daughter to this item.
     *
     * @param item the new daughter
     */
    public Item addDaughter(Item item) {
	Item newItem;
	ItemContents  contents;

	Item p = getLastDaughter();

	if (p != null) {
	    newItem = p.appendItem(item);
	} else {
	    if (item == null) {
		contents = new ItemContents();
	    } else {
		contents = item.getSharedContents();
	    }
	    newItem = new Item(getOwnerRelation(), contents);
	    newItem.parent = this;
	    daughter = newItem;
	}
	return newItem;
    }

    /**
     * Creates a new Item, adds it as a daughter to this item
     * and returns the new item.
     *
     * @return the newly created item that was added as a daughter
     */
    public Item createDaughter() {
	return addDaughter(null);
    }


    /**
     * Returns the parent of this item.
     *
     * @return the parent of this item
     */
    public Item getParent() {
	Item n;
	for (n = this; n.prev != null; n = n.prev) {
	    if (n == null) {
		return null;
	    }
	}
	return n.parent;
    }


    /**
     * Sets the parent of this item.
     *
     * @param parent the parent of this item
     */
    /*
    private void setParent(Item parent) {
	this.parent = parent;
    }
    */

    /**
     * Returns the utterance associated with this item.
     *
     * @return the utterance that contains this item
     */
    public Utterance getUtterance() {
	return getOwnerRelation().getUtterance();
    }

    /**
     * Returns the feature set of this item.
     *
     * @return the feature set of this item
     */
    public FeatureSet getFeatures() {
	return getSharedContents().getFeatures();
    }

    /**
     * Dumps out this item to the given output stream.
     *
     * @param out where to send the output
     * @param pad the leading whitspace
     */
    public void dump(PrintWriter out, int pad, String title) {
	String itemName = title + ":" + toString();
	getFeatures().dump(out, pad, itemName);
	if (hasDaughters()) {
	    Item daughter = getDaughter();
	    while (daughter != null) {
		daughter.dump(out, pad + 8, "d");
		daughter = daughter.next;
	    }
	}
    }

    /**
     * Finds the feature by following the given path.
     * Path is a string of
     *   ":" or "." separated strings with the following interpretations:
     * <ul>
     * <li> n - next item
     * <li> p - previous item
     * <li> parent - the parent
     * <li> daughter - the daughter
     * <li> daughter1 - same as daughter
     * <li> daughtern - the last daughter
     * <li> R:relname - the item as found in the given relation 'relname'
     * </ul>
     * The last element of the path will be interpreted as a
     * voice/language specific feature function (if present) or an
     * item feature name. If the feature function exists it will be
     * called with the item specified by the path, otherwise, a
     * feature will be retrieved with the given name. If neither exist
     * than a String "0" is returned.
     *
     * @param pathAndFeature the path to follow
     */
    public Object findFeature(String pathAndFeature) {
	int lastDot;
	String feature;
	String path;
	Item item;
	FeatureProcessor fp;

	Voice voice = getOwnerRelation().getUtterance().getVoice();
	Object results = null;
	

	lastDot = pathAndFeature.lastIndexOf(".");
	// string can be of the form "p.feature" or just "feature"

	if (lastDot == -1) {
	    feature = pathAndFeature;
	    path = null;
	} else {
	    feature = pathAndFeature.substring(lastDot + 1);
	    path = pathAndFeature.substring(0, lastDot);
	}


	item = findItem(path);
	if (item != null) {
            fp = voice.getFeatureProcessor(feature);

            if (fp != null) {
                try {
                    results = fp.process(item);
                } catch (ProcessException pe) {
                    System.err.println("Trouble while processing " +
                                       fp.toString());
                }
            } else {
                results = item.getFeatures().getObject(feature);
            }
        }
	results =  (results == null)
		? "0" 
		: results;

    // System.out.println("FI " + pathAndFeature + " are " + results);

	return results;
    }

    /** 
     * Finds the item specified by the given path.
     
     * Path is a string of ":" or
     * "." separated strings with the following interpretations:
     * <ul>
     * <li> n - next item
     * <li> p - previous item
     * <li> parent - the parent
     * <li> daughter - the daughter
     * <li> daughter1 - same as daughter
     * <li> daughtern - the last daughter
     * <li> R:relname - the item as found in the given relation 'relname'
     * </ul>
     * If the given path takes us outside of the bounds of the item
     * graph, then list access exceptions will be thrown.
     *
     * @param path the path to follow
     *
     * @return the item at the given path
     */
    public Item findItem(String path) {
	Item pitem = this;
	StringTokenizer tok;
	
	if (path == null) {
	    return this;
	}

	tok = new StringTokenizer(path, ":.");

	while (pitem != null && tok.hasMoreTokens()) {
	    String token = tok.nextToken();
	    if (token.equals("n")) {
		pitem = pitem.getNext();
	    } else if (token.equals("p")) {
		pitem = pitem.getPrevious();
	    } else if (token.equals("nn")) {
		pitem = pitem.getNext();
		if (pitem != null) {
		    pitem = pitem.getNext();
		}
	    } else if (token.equals("pp")) {
		pitem = pitem.getPrevious();
		if (pitem != null) {
		    pitem = pitem.getPrevious();
		}
	    } else if (token.equals("parent")) {
		pitem = pitem.getParent();
	    } else if (token.equals("daughter") || token.equals("daughter1")) {
		pitem = pitem.getDaughter();
	    } else if (token.equals("daughtern")) {
		pitem = pitem.getLastDaughter();
	    } else if (token.equals("R")) {
		String relationName = tok.nextToken();
		pitem = pitem.getSharedContents().getItemRelation(relationName);
	    } else {
		System.out.println("findItem: bad feature " + token +
			" in " + path);
	    }
	}
	return pitem;
    }


    /**
     * Gets the next item in this list.
     *
     * @return the next item or null
     */
    public Item getNext() {
	return next;
    }


    /**
     * Gets the previous item in this list.
     *
     * @return the previous item or null
     */
    public Item getPrevious() {
	return prev;
    }


    /**
     * Appends an item in this list after this item.
     *
     * @param originalItem new item has shared contents with this 
     *   item (or * null)
     *
     * @return the newly appended item
     */
    public Item appendItem(Item originalItem) {
	ItemContents contents;
	Item newItem;

	if (originalItem == null) {
	    contents = null;
	} else {
	    contents = originalItem.getSharedContents();
	}

	newItem = new Item(getOwnerRelation(), contents);
	newItem.next = this.next;
	if (this.next != null) {
	    this.next.prev = newItem;
	}

	attach(newItem);

	if (this.ownerRelation.getTail() == this) {
		this.ownerRelation.setTail(newItem);
	}
	return newItem;
    }

    /**
     * Attaches/appends an item to this one.
     *
     * @param item the item to append
     */
    void attach(Item item) {
	this.next = item;
	item.prev = this;
    }

    /**
     * Prepends an item in this list before this item.
     *
     * @param originalItem new item has shared contents with this 
     *   item (or * null)
     *
     * @return the newly appended item
     */
    public Item prependItem(Item originalItem) {
	ItemContents contents;
	Item newItem;

	if (originalItem == null) {
	    contents = null;
	} else {
	    contents = originalItem.getSharedContents();
	}

	newItem = new Item(getOwnerRelation(), contents);
	newItem.prev = this.prev;
	if (this.prev != null) {
	    this.prev.next = newItem;
	}
	newItem.next = this;
	this.prev = newItem;
	if (this.parent != null) {
	    this.parent.daughter = newItem;
	    newItem.parent = this.parent;
	    this.parent = null;
	}
	if (this.ownerRelation.getHead() == this) {
	    this.ownerRelation.setHead(newItem);
	}
	return newItem;
    }



    // Inherited from object
    public String toString() {
	// if we have a feature called 'name' use that
	// otherwise fall back on the default.
	String name = getFeatures().getString("name");
	if (name == null) {
	    name = "";
	}
	return name;
    }

    /**
     * Determines if the shared contents of the two items are the same.
     *
     * @param otherItem the item to compare
     *
     * @return true if the shared contents are the same
     */
    public boolean equalsShared(Item otherItem) {
	if (otherItem == null) {
	    return false;
	} else {
	    return getSharedContents().equals(otherItem.getSharedContents());
	}
    }
}
