/**
 * 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.en.us;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import com.sun.speech.freetts.FeatureProcessor;
import com.sun.speech.freetts.Item;
import com.sun.speech.freetts.PartOfSpeech;
import com.sun.speech.freetts.PathExtractor;
import com.sun.speech.freetts.PathExtractorImpl;
import com.sun.speech.freetts.ProcessException;
import com.sun.speech.freetts.Relation;
import com.sun.speech.freetts.Voice;



/**
 * Provides the set of feature processors that are used by this
 * language as part of the CART processing.
 */
public class FeatureProcessors {

    private final static PathExtractor FIRST_SYLLABLE_PATH = 
	new PathExtractorImpl(
      "R:SylStructure.parent.R:Phrase.parent.daughter.R:SylStructure.daughter",
       false);

    private final static PathExtractor LAST_SYLLABLE_PATH = 
	new PathExtractorImpl(
      "R:SylStructure.parent.R:Phrase.parent.daughtern.R:SylStructure.daughter",
      false);

    private final static PathExtractor LAST_LAST_SYLLABLE_PATH = 
	new PathExtractorImpl(
  "R:SylStructure.parent.R:Phrase.parent.daughtern.R:SylStructure.daughtern",
      false);

    private final static PathExtractor SUB_PHRASE_PATH = 
	new PathExtractorImpl("R:SylStructure.parent.R:Phrase.parent.p", false);

    private final static Pattern DOUBLE_PATTERN 
	= Pattern.compile(USEnglish.RX_DOUBLE);

    private final static Pattern DIGITS_PATTERN  
	= Pattern.compile(USEnglish.RX_DIGITS);

    private static Set months;
    private static Set days;

    // the set of month names
    static {
	months = new HashSet();
	months.add("jan");
	months.add("january");
	months.add("feb");
	months.add("february");
	months.add("mar");
	months.add("march");
	months.add("apr");
	months.add("april");
	months.add("may");
	months.add("jun");
	months.add("june");
	months.add("jul");
	months.add("july");
	months.add("aug");
	months.add("august");
	months.add("sep");
	months.add("september");
	months.add("oct");
	months.add("october");
	months.add("nov");
	months.add("november");
	months.add("dec");
	months.add("december");
    }

    // the set of week neames
    static {
	days = new HashSet();
	days.add("sun");
	days.add("sunday");
	days.add("mon");
	days.add("monday");
	days.add("tue");
	days.add("tuesday");
	days.add("wed");
	days.add("wednesday");
	days.add("thu");
	days.add("thursday");
	days.add("fri");
	days.add("friday");
	days.add("sat");
	days.add("saturday");
    }

    // no instances
    private FeatureProcessors() {}

    /**
     * Returns a guess of the part-of-speech.
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class Gpos implements FeatureProcessor {
	PartOfSpeech pos;
	/**
	 * Creates a GPOS with the given part-of-speech table
	 *
	 * @param pos part of speech determiner
	 */
	public Gpos(PartOfSpeech pos) {
	    this.pos = pos;
	}

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return a guess at the part-of-speech for the item
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return pos.getPartOfSpeech(item.toString());
	}
    }


    /**
     * Returns as an Integer the number of syllables in the given
     * word.  This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class WordNumSyls implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the number of syllables in the given word
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item daughter = item.getItemAs(
                Relation.SYLLABLE_STRUCTURE).getDaughter();
	    while (daughter != null) {
		count++;
		daughter = daughter.getNext();
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Counts the number of accented syllables since the last major break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class AccentedSylIn implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the number of accented syllables since the last
	 *    major break
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item ss = item.getItemAs(Relation.SYLLABLE);
	    Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);

	    for (Item p = ss; p != null; p = p.getPrevious() )  {
		if (isAccented(p)) {
		    count++;
		}
		if (p.equalsShared(firstSyllable)) {
		    break;
		}
	    }
	    return Integer.toString(rail(count));
	}
    }


    /**
     * Counts the number of stressed syllables since the last major break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class StressedSylIn implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the number of stresses syllables since the last
	 * major break
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item ss = item.getItemAs(Relation.SYLLABLE);
	    Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);

	    // this should include the first syllable, but
	    // flite 1.1 and festival don't.

	    for (Item p = ss.getPrevious(); 
		    p != null && !p.equalsShared(firstSyllable);
		    p = p.getPrevious() )  {
		if ("1".equals(p.getFeatures().getString("stress"))) {
		    count++;
		}
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Counts the number of stressed syllables until the next major break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class StressedSylOut implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
     	 * @return the number of stressed syllables until the next major break
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item ss = item.getItemAs(Relation.SYLLABLE);
	    Item lastSyllable  = LAST_SYLLABLE_PATH.findItem(item);

	    for (Item p = ss.getNext(); p != null; p = p.getNext() )  {
		if ("1".equals(p.getFeatures().getString("stress"))) {
		    count++;
		}
		if (p.equalsShared(lastSyllable)) {
		    break;
		}
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Returns the length of the string. (generally this is a digit
     * string)
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class NumDigits implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the length of the string
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    String name = item.getFeatures().getString("name");
	    return Integer.toString(rail(name.length()));
	}
    }

    /**
     * Returns true ("1") if the given item is a number between 0 and
     * 32 exclusive, otherwise, returns "0".
     * string)
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class MonthRange implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return returns "1" if the given item is a number between 0
	 * and 32 (exclusive) otherwise returns "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int v = Integer.parseInt(item.getFeatures().getString("name"));
	    if ((v > 0) && (v < 32)) {
		return "1";
	    } else {
		return "0";
	    }
	}
    }

    /**
     * Attempts to guess the part of speech.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class TokenPosGuess implements FeatureProcessor {
	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return  a guess at the part of speech
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    String name = item.getFeatures().getString("name");
	    String dc = name.toLowerCase();
	    if (DIGITS_PATTERN.matcher(dc).matches()) {
		return "numeric";
	    } else if (DOUBLE_PATTERN.matcher(dc).matches()) {
		return "number";
	    } else if (months.contains(dc)) {
		return "month";
	    } else if (days.contains(dc)) {
		return "day";
	    } else if (dc.equals("a")) {
		return "a";
	    } else if (dc.equals("flight")) {
		return "flight";
	    } else if (dc.equals("to")) {
		return "to";
	    } else {
		return "_other_";
	    }
	}
    }

    /**
     * Checks to see if the given syllable is accented.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class Accented implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 * @param  item  the item to process
	 *
	 * @return "1" if the syllable is accented; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    if (isAccented(item)) {
		return "1";
	    } else {
		return "0";
	    }
	}
    }

    /**
     * Find the last accented syllable
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class LastAccent implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 * 
	 * @return the count of the last accented syllable
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;

	    for (Item p = item.getItemAs(Relation.SYLLABLE); 
		      p != null; p = p.getPrevious(), count++)  {
		if (isAccented(p)) {
		    break;
		}
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Finds the position of the phoneme in the syllable
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PosInSyl implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the position of the phoneme in the syllable
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = -1;

	    for (Item p = item.getItemAs(Relation.SYLLABLE_STRUCTURE); 
		      p != null; p = p.getPrevious() )  {
		count++;
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Classifies the the syllable as single, initial, mid or final.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PositionType implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return classifies the syllable as "single", "final",
	 * "initial" or "mid"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    String type;

	    Item s = item.getItemAs(Relation.SYLLABLE_STRUCTURE); 
	    if (s == null) {
		type = "single";
	    } else if (s.getNext() == null) {
		if (s.getPrevious() == null) {
		    type = "single";
		} else {
		    type = "final";
		}
	    } else if (s.getPrevious() == null) {
		type = "initial";
	    } else {
		type = "mid";
	    }
	    return type;
	}
    }


    /**
     * Counts the number of stressed syllables since the last major break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SylIn implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 * 
	 * @return the number of stressed syllables since the last
	 * major break
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item ss = item.getItemAs(Relation.SYLLABLE);
	    Item firstSyllable  = FIRST_SYLLABLE_PATH.findItem(item);

	    for (Item p = ss; p != null; p = p.getPrevious(), count++ )  {
		if (p.equalsShared(firstSyllable)) {
		    break;
		}
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Counts the number of stressed syllables since the last major break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SylOut implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the number of stressed syllables since the last
	 * major break
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item ss = item.getItemAs(Relation.SYLLABLE);
	    Item firstSyllable  = LAST_LAST_SYLLABLE_PATH.findItem(item);

	    for (Item p = ss; p != null; p = p.getNext() )  {
		if (p.equalsShared(firstSyllable)) {
		    break;
		}
		count++;
	    }
	    return Integer.toString(rail(count));
	}
    }


    /**
     * Determines the break level after this syllable
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SylBreak implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  syl  the item to process
	 *
	 * @return the break level after this syllable
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item syl) throws ProcessException {
	    Item ss = syl.getItemAs(Relation.SYLLABLE_STRUCTURE);
	    if (ss == null) {
		return "1";
	    } else if (ss.getNext() != null) {
		return "0";
	    } else if (ss.getParent() == null) {
		return "1";
	    } else {
		return wordBreak(ss.getParent());
	    }
	}
    }



    /**
     * Determines the word break.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class WordBreak implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  word  the item to process
	 *
	 * @return the break level for this word
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item word) throws ProcessException {
	    return wordBreak(word);
	}
    }

    /**
     * Determines the word punctuation.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class WordPunc implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  word  the item to process
	 *
	 * @return the punctuation for this word
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item word) throws ProcessException {
	    return wordPunc(word);
	}
    }

    /**
     * Return consonant cplace 
     *   l-labial a-alveolar p-palatal b-labio_dental d-dental v-velar
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_CPlace implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return consonant cplace
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "cplace");
	}
    }

    /**
     * Return consonant type 
     *   s-stop f-fricative a-affricative n-nasal * l-liquid
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_CType implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return consonant type
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "ctype");
	}
    }

    /**
     * Return consonant voicing 
     *   +=on -=off
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_CVox implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 * 
	 * @return consonant voicing
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "cvox");
	}
    }

    /**
     * Return vowel or consonant
     *   +=on -=off
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_VC implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return vowel or consonant
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "vc");
	}
    }

    /**
     * Return vowel frontness
     *  1-front  2-mid 3-back
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_VFront implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return vowel frontness
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "vfront");
	}
    }

    /**
     * Return vowel height
     *   1-high 2-mid 3-low
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_VHeight implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 * 
	 * @return vowel height
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "vheight");
	}
    }


    /**
     * Return vowel length
     *   s-short l-long d-dipthong a-schwa
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_VLength implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return vowel length
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "vlng");
	}
    }




    /**
     * Return vowel rnd (lip rounding)
     *   lip rounding  +=on -=off
     *
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class PH_VRnd implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return volwel rnd
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    return getPhoneFeature(item, "vrnd");
	}
    }

    /**
     * Determines the onset size of this syllable
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SylOnsetSize implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  syl  the item to process
	 *
	 * @return onset size of this syllable
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item syl) throws ProcessException {
	    int count = 0;
	    Item daughter = syl.getItemAs(
                Relation.SYLLABLE_STRUCTURE).getDaughter();
	    while (daughter != null) {
		if ("+".equals(getPhoneFeature(daughter, "vc"))) {
		    break;
		}
		count++;
		daughter = daughter.getNext();
	    }
	    return Integer.toString(rail(count));
	}
    }


    /**
     * Determines the coda size
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SylCodaSize implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  syl  the item to process
	 *
	 * @return coda size
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item syl) throws ProcessException {
	    int count = 0;
	    Item daughter = syl.getItemAs(
                Relation.SYLLABLE_STRUCTURE).getLastDaughter();

	    while (daughter != null) {
		if ("+".equals(getPhoneFeature(daughter, "vc"))) {
		    break;
		}

		daughter = daughter.getPrevious();
		count++;
	    }
	    return Integer.toString(rail(count));
	}
    }


    /**
     * Checks for fricative
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegCodaFric implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 * 
	 * @return "1" if fricative; else "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segCodaCtype(seg, "f");
	}
    }

    /**
     * Checks for fricative
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegOnsetFric implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 * 
	 * @return "1" if fricative; else "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segOnsetCtype(seg, "f");
	}
    }



    /**
     * Checks for coda stop
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegCodaStop implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if coda stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segCodaCtype(seg, "s");
	}
    }

    /**
     * Checks for onset stop
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegOnsetStop implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if Onset Stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segOnsetCtype(seg, "s");
	}
    }

    /**
     * Checks for coda nasal
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegCodaNasal implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if coda stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segCodaCtype(seg, "n");
	}
    }

    /**
     * Checks for onset nasal
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegOnsetNasal implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if Onset Stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    return segOnsetCtype(seg, "n");
	}
    }

    /**
     * Checks for coda glide
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegCodaGlide implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if coda stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    if (segCodaCtype(seg, "r").equals("0")) {
		return segCodaCtype(seg, "l");
	    }
	    return "1";
	}
    }

    /**
     * Checks for onset glide
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegOnsetGlide implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if coda stop "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    if (segOnsetCtype(seg, "r").equals("0")) {
		return segOnsetCtype(seg, "l");
	    }
	    return "1";
	}
    }


    /**
     * Checks for onset coda 
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegOnsetCoda implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return if onset coda "1"; otherwise "0"
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    Item s = seg.getItemAs(Relation.SYLLABLE_STRUCTURE);
            if (s == null) {
                return "coda";
            }

            s = s.getNext();
	    while (s != null) {
		if ("+".equals(getPhoneFeature(s, "vc"))) {
		    return "onset";
		}

		s = s.getNext();
	    }
            
	    return "coda";
	}
    }

    /**
     * Counts the number of phrases before this one.
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SubPhrases implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  item  the item to process
	 *
	 * @return the number of phrases before this one
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item item) throws ProcessException {
	    int count = 0;
	    Item inPhrase  = SUB_PHRASE_PATH.findItem(item);

	    for (Item p = inPhrase; p != null; p = p.getPrevious() )  {
		count++;
	    }
	    return Integer.toString(rail(count));
	}
    }

    /**
     * Returns the duration of the given segment
     * This is a feature processor. A feature processor takes an item,
     * performs some sort of processing on the item and returns an object.
     */
    public static class SegmentDuration implements FeatureProcessor {

	/**
	 * Performs some processing on the given item.
	 *
	 * @param  seg  the item to process
	 *
	 * @return the duration of the segment as a string.
	 *
	 * @throws ProcessException if an exception occurred during the
	 * processing
	 */
	public String process(Item seg) throws ProcessException {
	    if (seg == null) {
		return "0";
	    } else if (seg.getPrevious() == null) {
		return seg.getFeatures().getObject("end").toString();
	    } else {
		return Float.toString(
			seg.getFeatures().getFloat("end") -
		        seg.getPrevious().getFeatures().getFloat("end")
		   );
	    }
	}
    }

    /**
     * Gets the phoneset feature with the given name
     *
     * @param item item the phoneme of interest
     * @param featureName the feature of interest
     *
     * @return the phone feature for the item
     *
     */

    public static String getPhoneFeature(Item item, String featureName) {
	Voice voice = item.getUtterance().getVoice();
	String feature = voice.getPhoneFeature(item.toString(), featureName);
	return feature;
    }

    /**
     * Classifies the type of word break
     *
     * @param  item  the item to process
     *
     * @return  "4" for a big break, "3" for  a break; otherwise "1"
     *
     * @throws ProcessException if an exception occurred during the
     * processing
     */
    public static String wordBreak(Item item) throws ProcessException {
	Item ww = item.getItemAs(Relation.PHRASE);
	if (ww == null || ww.getNext() != null) {
	    return "1";
	} else {
	    String pname = ww.getParent().toString();
	    if (pname.equals("BB")) {
		return "4";
	    } else if (pname.equals("B")) {
		return "3";
	    } else {
		return "1";
	    }
	}
    }

    /**
     * Gets the punctuation associated with the word
     *
     * @param  item  the word to process
     *
     * @return  the punctuation associated with the word
     *
     * @throws ProcessException if an exception occurred during the
     * processing
     */
    public static String wordPunc(Item item) throws ProcessException {
	Item ww = item.getItemAs(Relation.TOKEN);
	if (ww != null && ww.getNext() != null) {
	    return "";
	} else {
	    if (ww != null && ww.getParent() != null) {
		return ww.getParent().getFeatures().getString("punc");
	    } else {
		return "";
	    }
	}
    }

    /**
     * Tests the coda ctype of the given segment.
     *
     * @param seg the segment to test
     * @param ctype the ctype to check for
     * 
     * @return "1" on match "0" on no match
     */
    private static String segCodaCtype(Item seg, String ctype) {
	Item daughter 
	    = seg.getItemAs(
		Relation.SYLLABLE_STRUCTURE).getParent().getLastDaughter();

	while (daughter != null) {
	    if ("+".equals(getPhoneFeature(daughter, "vc"))) {
		return "0";
	    }
	    if (ctype.equals(getPhoneFeature(daughter, "ctype"))) {
		return "1";
	    }

	    daughter = daughter.getPrevious();
	}
	return "0";
    }

    /**
     * Tests the onset ctype of the given segment.
     *
     * @param  seg  the segment to test to process
     * @param ctype the ctype to check for
     *
     * @return if Onset Stop "1"; otherwise "0"
     *
     */
    private static  String segOnsetCtype(Item seg, String ctype) {
	Item daughter = seg.getItemAs(
	    Relation.SYLLABLE_STRUCTURE).getParent().getDaughter();

	while (daughter != null) {
	    if ("+".equals(getPhoneFeature(daughter, "vc"))) {
		return "0";
	    }
	    if (ctype.equals(getPhoneFeature(daughter, "ctype"))) {
		return "1";
	    }

	    daughter = daughter.getNext();
	}
	return "0";
    }

    /**
     * Determines if the given item is accented
     *
     * @param item the item of interest
     *
     * @return <code>true</code> if the item is accented, otherwise
     * <code>false</code>
     */
    private static boolean isAccented(Item item) {
	return (item.getFeatures().isPresent("accent") ||
	    item.getFeatures().isPresent("endtone"));
    }

    /**
     * Rails an int. flite never returns an int more than 19 from
     * a feature processor, we duplicate that behavior
     * here so that our tests will match.
     *
     * @param val the value to rail
     *
     * @return val clipped to be betweein 0 and 19
     */
    private static int rail(int val) {
	return val > 19 ? 19 : val;
    }
}
