/*
 * FreeTTSClient.java  (2001)
 *
 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
 *
 * Unpublished - rights reserved under the Copyright Laws of the United States.
 *
 * Sun Microsystems, Inc. has intellectual property rights relating to
 * technology embodied in the product that is described in this document. In
 * particular, and without limitation, these intellectual property rights may
 * include one or more of the U.S. patents listed at http://www.sun.com/patents
 * and one or more additional patents or pending patent applications in the
 * U.S. and in other countries.
 *
 * SUN PROPRIETARY/CONFIDENTIAL.
 *
 * U.S. Government Rights - Commercial software. Government users are subject
 * to the Sun Microsystems, Inc. standard license agreement and applicable
 * provisions of the FAR and its supplements.
 *
 * Use is subject to license terms.
 *
 * This distribution may include materials developed by third parties. Sun, Sun
 * Microsystems, the Sun logo, Java, Jini, Solaris and Sun Ray are trademarks
 * or registered trademarks of Sun Microsystems, Inc. in the U.S. and other
 * countries.
 *
 * UNIX is a registered trademark in the U.S. and other countries, exclusively
 * licensed through X/Open Company, Ltd.
 */
package com.sun.voip;

import com.sun.voip.Logger;

import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;

import com.sun.speech.freetts.util.Utilities;

import com.sun.speech.freetts.audio.AudioPlayer;
import javax.sound.sampled.AudioFormat;

import java.io.IOException;

/**
 * Implements a Java Client for the Client/Server demo. For details about
 * the protocol between client and server, consult the file
 * <code>Protocol.txt</code>.
 */
public class FreeTTSClient {

    private static boolean initialized = false;

    private static int sampleRate =	Utilities.getInteger("sampleRate", 16000).intValue();

    private static Voice voice;
    private static String voice16kName = Utilities.getProperty("voice16kName", "kevin16");

    private FreeTTSClient() {
    }

    public static void initialize() throws IOException {
	if (initialized) {
	    return;
	}

	initialized = true;

        VoiceManager voiceManager = VoiceManager.getInstance();
	voice = voiceManager.getVoice(voice16kName);
        voice.allocate();
    }

    /**
     * Run the TTS protocol.
     */
    public static int[] textToSpeech(String text) throws IOException {
	long startTime = System.currentTimeMillis();

	try {
	    ServerAudioPlayer serverAudioPlayer = new ServerAudioPlayer();

	    voice.setAudioPlayer(serverAudioPlayer);
	    voice.speak(text);

	    int[] linearData = serverAudioPlayer.getLinearData();

	    if (Logger.logLevel >= Logger.LOG_INFO) {
	        Logger.println("TTS time for '" + text + "' "
		    + (System.currentTimeMillis() - startTime));
	    }

	    return linearData;
	} catch (Exception ioe) {
	    ioe.printStackTrace();
	}
	return null;
    }

}

/**
 * Implements the AudioPlayer for the freetts Client/Server demo.
 * This SocketAudioPlayer basically sends synthesized wave bytes to the
 * client.
 */
class ServerAudioPlayer implements AudioPlayer {

    private AudioFormat audioFormat;
    private boolean debug = false;
    private int bytesToPlay = 0;
    private int bytesPlayed = 0;

    private byte[] byteLinearData;

    public ServerAudioPlayer() {
    }

    /**
     * Sets the audio format to use for the next set of outputs. Since
     * an audio player can be shared by a number of voices, and since
     * voices can have different AudioFormats (sample rates for
     * example), it is necessary to allow clients to dynamically set
     * the audio format for the player.
     *
     * @param format the audio format
     */
    public void setAudioFormat(AudioFormat format) {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("setAudioFormat");
	}
	this.audioFormat = format;
    }

    /**
     * Retrieves the audio format for this player
     *
     * @return the current audio format
     *
     */
    public AudioFormat getAudioFormat() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("getAudioFormat");
	}
	return this.audioFormat;
    }

    /**
     * Pauses all audio output on this player. Play can be resumed
     * with a call to resume. Not implemented in this Player.
     */
    public void pause() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("pause");
	}
    }

    /**
     * Resumes audio output on this player. Not implemented in this Player.
     */
    public void resume() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("resume");
	}
    }

    /**
     * Prepares for another batch of output. Larger groups of output
     * (such as all output associated with a single FreeTTSSpeakable)
     * should be grouped between a reset/drain pair.
     */
    public void reset() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("reset");
	}
    }

    /**
     * Flushes all the audio data to the Socket.
     *
     * @return <code>true</code> all the time
     */
    public boolean drain() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("drain");
	}
	return true;
    }

    /**
     *  Starts the output of a set of data. Audio data for a single
     *  utterance should be grouped between begin/end pairs.
     *
     * @param size the size of data in bytes to be output before
     *    <code>end</code> is called.
     */
    public void begin(int size) {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("begin size " + size);
	}
    }

    /**
     * Starts the first sample timer (none in this player)
     */
    public void startFirstSampleTimer() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("startFirstSampleTimer");
	}
    }

    /**
     *  Signals the end of a set of data. Audio data for a single
     *  utterance should be groupd between <code> begin/end </code> pairs.
     *
     *  @return <code>true</code> if the audio was output properly,
     *          <code> false</code> if the output was cancelled
     *          or interrupted.
     *
     */
    public boolean end() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("end bytesPlayed " + bytesPlayed);
	}

	if (bytesPlayed < bytesToPlay) {
	    int bytesNotPlayed = bytesToPlay - bytesPlayed;
	    write(new byte[bytesNotPlayed], 0, bytesNotPlayed);
	}

	bytesToPlay = 0;
	bytesPlayed = 0;
	return true;
    }

    /**
     * Cancels all queued output. All 'write' calls until the next
     * reset will return false. Not implemented in this Player.
     *
     */
    public void cancel() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("cancel");
	}
    }

    /**
     * Waits for all audio playback to stop, and closes this AudioPlayer.
     * Not implemented in this Player.
     */
    public void close() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("close");
	}
    }

    /**
     * Returns the current volume. The volume is specified as a number
     * between 0.0 and 1.0, where 1.0 is the maximum volume and 0.0 is
     * the minimum volume. Not implemented in this Player.
     *
     * @return the current volume (between 0 and 1)
     */
    public float getVolume() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("getVolume");
	}
	return -1;
    }

    /**
     * Sets the current volume. The volume is specified as a number
     * between 0.0 and 1.0, where 1.0 is the maximum volume and 0.0 is
     * the minimum volume. Not implemented in this Player.
     *
     * @param volume the new volume (between 0 and 1)
     */
    public void setVolume(float volume) {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("setVolume");
	}
    }

    /**
     * Gets the amount of audio played since the last resetTime.
     * Not implemented in this Player.
     *
     * @returns the amount of audio in milliseconds
     */
    public long getTime() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("getTime");
	}
	return -1;
    }

    /**
     * Resets the audio clock. Not implemented in this Player.
     */
    public void resetTime() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("reset");
	}
    }

    /**
     * Shows metrics for this audio player. Not implemented in this Player.
     */
    public void showMetrics() {
	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("showMetrics");
	}
    }

    /**
     * Writes the given bytes to the audio stream
     *
     * @param audioData audio data to write to the device
     *
     * @return <code>true</code> of the write completed successfully,
     *          <code> false </code>if the write was cancelled.
     */
    public boolean write(byte[] audioData) {
	return write(audioData, 0, audioData.length);
    }


    /**
     * Writes the given bytes to the byte linear data buffer
     *
     * @param audioData audio data to write to the device
     * @param offset the offset into the buffer
     * @param size the number of bytes to write.
     *
     * @return <code>true</code> of the write completed successfully,
     *          <code> false </code>if the write was cancelled.
     */
    public boolean write(byte[] audioData, int offset, int size) {
	/*
	 * get a new buffer which will hold all of the data
	 */
        byte[] b = new byte[bytesPlayed + audioData.length];

        for (int i = 0; i < bytesPlayed; i++) {
            b[i] = byteLinearData[i];	// copy old data
	}

	for (int i = 0; i < size; i++) {
	    b[i + bytesPlayed] = audioData[i + offset];  // new data
	}

        byteLinearData = b;

	bytesPlayed += size;

	if (Logger.logLevel >= Logger.LOG_DETAIL) {
	    Logger.println("bytesPlayed " + bytesPlayed);
	}
	return true;
    }

    /**
     * Return the data in an int array.
     * @return data int[] voice data
     */
    public int[] getLinearData() {
	return AudioConversion.bytesToInts(byteLinearData);
    }

}