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

import java.io.IOException;
import java.net.URL;

import com.sun.speech.freetts.FeatureSet;
import com.sun.speech.freetts.Item;
import com.sun.speech.freetts.ProcessException;
import com.sun.speech.freetts.Relation;
import com.sun.speech.freetts.Utterance;
import com.sun.speech.freetts.UtteranceProcessor;
import com.sun.speech.freetts.relp.Sample;
import com.sun.speech.freetts.relp.SampleInfo;


/**
 * Generates the Unit Relation of an Utterance from the
 * Segment Relation.
 */
public class DiphoneUnitSelector implements UtteranceProcessor {

    // the UnitDatabase to use
    private DiphoneUnitDatabase diphoneDatabase;
        
    // The Utterance that this DiphoneUnitSelector works on
    // private Utterance utterance;
    
    
    /**
     * Constructs a DiphoneUnitSelector.
     *
     * @param url the URL for the unit database. If the URL path ends
     *     with a '.bin' it is assumed that the DB is a binary database,
     *     otherwise, its assumed that its a text database1
     *
     * @throws IOException if an error occurs while loading the
     *     database
     *
     */
    public DiphoneUnitSelector(URL url) throws IOException {
	if (url == null) {
	    throw new IOException("Can't load unit database");
	}
	boolean binary = url.getPath().endsWith(".bin");
	diphoneDatabase = new DiphoneUnitDatabase(url, binary);
    }
    
    /**
     * Get the sample info for the underlying database.
     * @return the sample info object
     */
    public SampleInfo getSampleInfo() {
        return diphoneDatabase.getSampleInfo();
    }
    
    /**
     * Generates the Unit Relation from the Segment Relation.
     *
     * @param  utterance  the utterance to generate the Unit Relation
     *
     * @throws ProcessException if an IOException is thrown during the
     *         processing of the utterance
     */
    public void processUtterance(Utterance utterance) throws ProcessException {

	if (utterance.getRelation(Relation.SEGMENT) == null) {
	    throw new IllegalStateException
		("DiphoneUnitSelector: Segment relation does not exist");
	}
	
	utterance.setObject(SampleInfo.UTT_NAME,
		diphoneDatabase.getSampleInfo());
	createUnitRelation(utterance);
    }

    /**
     * Creates the Unit Relation in the given utterance from
     * the diphone units and their some associated information from
     * the units database.
     *
     * @param utterance the utterance that gets the new unit relation
     */
    private void createUnitRelation(Utterance utterance) {

	Item segmentItem0, segmentItem1;
	float end0, end1;
	int targetEnd;
	
	Item unitItem0, unitItem1;
		
	String diphoneName;
			
	Relation unitRelation = utterance.createRelation(Relation.UNIT);
	Relation segmentRelation = utterance.getRelation(Relation.SEGMENT);

	for (segmentItem0 = segmentRelation.getHead();
		segmentItem0 != null && segmentItem0.getNext() != null;
		segmentItem0 = segmentItem1) {
	    segmentItem1 = segmentItem0.getNext();
	    diphoneName = segmentItem0.getFeatures().getString("name") + "-" +
		segmentItem1.getFeatures().getString("name");
	    

	    // First half of diphone
	    end0 = segmentItem0.getFeatures().getFloat("end");
	    targetEnd = (int) (end0 * 
		    diphoneDatabase.getSampleInfo().getSampleRate());
	    unitItem0 = createUnitItem(unitRelation, diphoneName, targetEnd, 1);
	    segmentItem0.addDaughter(unitItem0);
	    
	    // Second half of diphone
	    end1 = segmentItem1.getFeatures().getFloat("end");
	    targetEnd = (int) (((end0 + end1)/2.0) * 
		    	diphoneDatabase.getSampleInfo().getSampleRate());
	    unitItem1 = createUnitItem(unitRelation, diphoneName, targetEnd, 2);
	    segmentItem1.addDaughter(unitItem1);
	}
    }

    /**
     * Returns a new Item (a Unit) in the given Relation, and
     * sets the new Item to the given diphone name, target end,
     * unit entry (index in the database), and unit part (1 or 2).
     *
     * @param unitRelation the relation that gets the new item
     * @param diphoneName the name of the dipohone
     * @param targetEnd the time at the end of this unit
     * @param unitPart the item can be in the first(1) or second part (2)
     */
    private Item createUnitItem(Relation unitRelation,
				String diphoneName,
				int targetEnd,
				int unitPart) {
	Diphone diphone = (Diphone) diphoneDatabase.getUnit(diphoneName);
	if (diphone == null) {
	    System.err.println
		("FreeTTS: unit database failed to find entry for: " + 
		 diphoneName);
	}
	Item unit = unitRelation.appendItem();
	FeatureSet unitFeatureSet = unit.getFeatures();

	unitFeatureSet.setString("name", diphoneName);
	unitFeatureSet.setInt("target_end", targetEnd);
	unitFeatureSet.setObject("unit", new DiphoneUnit(diphone, unitPart));
	// unitFeatureSet.setInt("unit_part", unitPart);
	return unit;
    }

    /**
     * Returns a string representation of this object.
     *
     * @return a string representation of this object
     */
    public String toString() {
	return "DiphoneUnitSelector";
    }
}


/**
 * A wrapper around the Diphone class that turns the
 * diphone into a unit
 */
class DiphoneUnit implements com.sun.speech.freetts.Unit {

    private Diphone diphone;
    private int unitPart;

    /**
     * Contructs a diphone unit given a diphone and unit part.
     *
     * @param diphone the diphone to wrap
     * @param unitPart which half (1 or 2) does this unit represent
     */
    public DiphoneUnit(Diphone diphone, int unitPart) {
	this.diphone = diphone;
	this.unitPart = unitPart;
    }

    /**
     * Returns the name of this Unit.
     *
     * @return the name of the unit
     */
    public String getName() {
	return diphone.getName();
    }

    /**
     * Returns the size of the unit.
     *
     * @return the size of the unit
     */
    public int getSize() {
	return diphone.getUnitSize(unitPart);
    }

    /**
     * Retrieves the nearest sample.
     *
     * @param index the ideal index
     *
     * @return the nearest Sample
     */
    public Sample getNearestSample(float index) {
    	return diphone.nearestSample(index, unitPart); 
    }

    /**
     * Returns a string representation of this object.
     *
     * @return a string representation of this object
     */
    public String toString() {
	return getName();
    }


    /** 
     * Dumps this unit.
     */
    public void dump()  {
	diphone.dump();
    }
}
