/**
 * Portions Copyright 2001 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.util.ArrayList;
import java.util.List;

import com.sun.speech.freetts.lexicon.Lexicon;

/**
 * Annotates an utterance with <code>Relation.SYLLABLE</code>,
 * <code>Relation.SYLLABLE_STRUCTURE</code>, and
 * <code>Relation.SEGMENT</code>.
 * To determine stress, the <code>isStressed</code> method relies upon
 * a phone ending in the number "1".  Subclasses should override
 * <code>isStressed</code> and <code>deStress</code> if stresses are
 * determined in other ways.
 *
 * @see Relation#SEGMENT
 * @see Relation#SYLLABLE
 * @see Relation#SYLLABLE_STRUCTURE
 */
public class Segmenter implements UtteranceProcessor {
    private final static String STRESS = "1";
    private final static String NO_STRESS = "0";

    /**
     * Annotates an utterance with <code>Relation.SYLLABLE</code>,
     * <code>Relation.SYLLABLE_STRUCTURE</code>, and
     * <code>Relation.SEGMENT</code>.
     *
     * @param utterance the utterance to process/tokenize
     *
     * @see Relation#SEGMENT
     * @see Relation#SYLLABLE
     * @see Relation#SYLLABLE_STRUCTURE
     *
     * @throws ProcessException if an IOException is thrown during the
     *   processing of the utterance
     */
    public void processUtterance(Utterance utterance) throws ProcessException {

	// preconditions
        if (utterance.getRelation(Relation.WORD) == null) {
            throw new IllegalStateException(
                "Word relation has not been set");
        } else if (utterance.getRelation(Relation.SYLLABLE) != null) {
            throw new IllegalStateException(
                "Syllable relation has already been set");
        } else if (utterance.getRelation(Relation.SYLLABLE_STRUCTURE)
                   != null) {
            throw new IllegalStateException(
                "SylStructure relation has already been set");
        } else if (utterance.getRelation(Relation.SEGMENT) != null) {
            throw new IllegalStateException(
                "Segment relation has already been set");
        }

	String stress = NO_STRESS;
	Relation syl = utterance.createRelation(Relation.SYLLABLE);
	Relation sylstructure =
            utterance.createRelation(Relation.SYLLABLE_STRUCTURE);
	Relation seg = utterance.createRelation(Relation.SEGMENT);
	Lexicon lex = utterance.getVoice().getLexicon();
	List syllableList = null;

	for (Item word = utterance.getRelation(Relation.WORD).getHead();
			word != null; word = word.getNext()) {
	    Item ssword = sylstructure.appendItem(word);
	    Item sylItem = null;   // item denoting syllable boundaries
	    Item segItem = null;   // item denoting phonelist (segments)
	    Item sssyl = null;     // item denoting syl in word

	    String[] phones = null;

	    Item token = word.getItemAs("Token");
	    FeatureSet featureSet = null;

	    if (token != null) {
		Item parent = token.getParent();
		featureSet = parent.getFeatures();
	    }
	    
	    if (featureSet != null && featureSet.isPresent("phones")) {
		phones = (String[]) featureSet.getObject("phones");
	    } else {
		phones = lex.getPhones(word.toString(), null);
	    }

	    for (int j = 0; j < phones.length; j++) {
		if (sylItem == null) {
		    sylItem = syl.appendItem();
		    sssyl = ssword.addDaughter(sylItem);
		    stress = NO_STRESS;
		    syllableList = new ArrayList();
		}
		segItem = seg.appendItem();
		if (isStressed(phones[j])) {
		    stress = STRESS;
		    phones[j] = deStress(phones[j]);
		}
		segItem.getFeatures().setString("name", phones[j]);
		sssyl.addDaughter(segItem);
		syllableList.add(phones[j]);
		if (lex.isSyllableBoundary(syllableList, phones, j + 1))  { 
		    sylItem =  null;
		    if (sssyl != null) {
			sssyl.getFeatures().setString("stress", stress);
		    }
		}
	    }
	}
    }

    /**
     * Determines if the given phonemene is stressed.
     * To determine stress, this method relies upon
     * a phone ending in the number "1".  Subclasses should override this
     * method if stresses are determined in other ways.
     *
     * @param phone the phone to check
     *
     * @return true if the phone is stressed, otherwise false
     */
    protected boolean isStressed(String phone) {
	return phone.endsWith("1");
    }

    /**
     * Converts stressed phoneme to regular phoneme.  This method
     * merely removes the last character of the phone.  Subclasses
     * should override this if another method is to be used.
     *
     * @param phone the phone to convert
     *
     * @return de-stressed phone
     */
    protected String deStress(String phone) {
	String retPhone = phone;
	if (isStressed(phone)) {
	    retPhone = phone.substring(0, phone.length() - 1);
	}
	return retPhone;
    }

    /**
     * Returns the simple name of this class.
     *
     * @return the simple name of this class
     */
    public String toString() {
        return "Segmenter";
    }
}

