/**
 * Portions Copyright 2001-2005 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.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;

import com.sun.speech.freetts.audio.AudioPlayer;
import com.sun.speech.freetts.audio.JavaClipAudioPlayer;
import com.sun.speech.freetts.audio.MultiFileAudioPlayer;
import com.sun.speech.freetts.audio.NullAudioPlayer;
import com.sun.speech.freetts.audio.RawFileAudioPlayer;
import com.sun.speech.freetts.audio.SingleFileAudioPlayer;

/**
 * Standalone utility that directly interacts with a CMUDiphoneVoice.
 */
public class FreeTTS {
    /** Logger instance. */
    private static final Logger LOGGER =
        Logger.getLogger(FreeTTS.class.getName());

    /** Version number. */
    public final static String VERSION = "FreeTTS 1.2.2";
    private Voice voice;
    private static AudioPlayer audioPlayer = null;
    private boolean silent = false;
    private String audioFile = null;
    private boolean multiAudio = false;
    private boolean streamingAudio = false;
    private InputMode inputMode = InputMode.INTERACTIVE;

    /**
     * Constructs a default FreeTTS with the kevin16 voice.
     */
    public FreeTTS() {
        VoiceManager voiceManager = VoiceManager.getInstance();
        voiceManager.getVoice("kevin16");
    }

    /**
     * Creates a FreeTTS object with the given Voice.
     * 
     * @param voice
     *            the voice to use
     */
    public FreeTTS(Voice voice) {
        this.voice = voice;
    }

    /**
     * Starts this FreeTTS Synthesizer by loading the void and creating a new
     * AudioPlayer.
     */
    public void startup() {
        voice.allocate();
        if (!getSilentMode()) {
            if (audioFile != null) {
                AudioFileFormat.Type type = getAudioType(audioFile);
                if (type != null) {
                    if (multiAudio) {
                        audioPlayer = new MultiFileAudioPlayer(
                                getBasename(audioFile), type);
                    } else
                        audioPlayer = new SingleFileAudioPlayer(
                                getBasename(audioFile), type);
                } else {
                    try {
                        audioPlayer = new RawFileAudioPlayer(audioFile);
                    } catch (IOException ioe) {
                        System.out.println("Can't open " + audioFile + " "
                                + ioe);
                    }
                }
            } else if (!streamingAudio) {
                audioPlayer = new JavaClipAudioPlayer();
            } else {
                try {
                    audioPlayer = voice.getDefaultAudioPlayer();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }

        if (audioPlayer == null) {
            audioPlayer = new NullAudioPlayer();
        }

        if (false) {
            System.out.println("Using " + audioPlayer);
        }

        voice.setAudioPlayer(audioPlayer);
    }

    /**
     * Returns the audio type based upon the extension of the given file
     * 
     * @param file
     *            the file of interest
     * 
     * @return the audio type of the file or null if it is a non-supported type
     */
    private AudioFileFormat.Type getAudioType(String file) {
        AudioFileFormat.Type[] types = AudioSystem.getAudioFileTypes();
        String extension = getExtension(file);

        for (int i = 0; i < types.length; i++) {
            if (types[i].getExtension().equals(extension)) {
                return types[i];
            }
        }
        return null;
    }

    /**
     * Given a filename returns the extension for the file
     * 
     * @param path
     *            the path to extract the extension from
     * 
     * @return the extension or <code>null</code> if none
     */
    private static String getExtension(String path) {
        int index = path.lastIndexOf(".");
        if (index == -1) {
            return null;
        } else {
            return path.substring(index + 1);
        }
    }

    /**
     * Given a filename returns the basename for the file
     * 
     * @param path
     *            the path to extract the basename from
     * 
     * @return the basename of the file
     */
    private static String getBasename(String path) {
        int index = path.lastIndexOf(".");
        if (index == -1) {
            return path;
        } else {
            return path.substring(0, index);
        }
    }

    /**
     * Shuts down this FreeTTS synthesizer by closing the AudioPlayer and voice.
     */
    public void shutdown() {
        audioPlayer.close();
        voice.deallocate();
    }

    /**
     * Converts the given text to speech based using processing options
     * currently set in FreeTTS.
     * 
     * @param text
     *            the text to speak
     * 
     * @return true if the utterance was played properly
     */
    public boolean textToSpeech(String text) {
        return voice.speak(text);
    }

    /**
     * Converts the given text to speech based using processing options
     * currently set in FreeTTS.
     * 
     * @param text
     *            the text to speak
     * 
     * @return true if the utterance was played properly
     */
    private boolean batchTextToSpeech(String text) {
        boolean ok;
        voice.startBatch();
        ok = textToSpeech(text);
        voice.endBatch();
        return ok;
    }

    /**
     * Reads the file pointed to by the given path and renders each line as
     * speech individually.
     */
    private boolean lineToSpeech(String path) {
        boolean ok = true;
        voice.startBatch();
        try {
            BufferedReader reader = new BufferedReader(new FileReader(path));
            String line;

            while ((line = reader.readLine()) != null && ok) {
                ok = textToSpeech(line);
            }
            reader.close();
        } catch (IOException ioe) {
            LOGGER.severe("can't read " + path);
            throw new Error(ioe);
        }
        voice.endBatch();

        return ok;

    }

    /**
     * Returns the voice used by FreeTTS.
     * 
     * @return the voice used by freetts
     */
    protected Voice getVoice() {
        return voice;
    }

    /**
     * Converts the text contained in the given stream to speech.
     * 
     * @param is
     *            the stream containing the text to speak
     */
    public boolean streamToSpeech(InputStream is) {
        boolean ok;
        voice.startBatch();
        ok = voice.speak(is);
        voice.endBatch();
        return ok;
    }

    /**
     * Converts the text contained in the given path to speech.
     * 
     * @param urlPath
     *            the file containing the text to speak
     * 
     * @return true if the utterance was played properly
     */
    public boolean urlToSpeech(String urlPath) {
        boolean ok = false;
        try {
            URL url = new URL(urlPath);
            InputStream is = url.openStream();
            ok = streamToSpeech(is);
        } catch (IOException ioe) {
            System.err.println("Can't read data from " + urlPath);
        }
        return ok;
    }

    /**
     * Converts the text contained in the given path to speech.
     * 
     * @param filePath
     *            the file containing the text to speak
     * 
     * @return true if the utterance was played properly
     */
    public boolean fileToSpeech(String filePath) {
        boolean ok = false;
        try {
            InputStream is = new FileInputStream(filePath);
            ok = streamToSpeech(is);
        } catch (IOException ioe) {
            System.err.println("Can't read data from " + filePath);
        }
        return ok;
    }

    /**
     * Turns audio playing on and off.
     * 
     * @param silent
     *            if true, don't play audio
     */
    public void setSilentMode(boolean silent) {
        this.silent = silent;
    }

    /**
     * Gets silent mode.
     * 
     * @return true if in silent mode
     * 
     * @see #setSilentMode
     */
    public boolean getSilentMode() {
        return this.silent;
    }

    /**
     * Sets the input mode.
     * 
     * @param inputMode
     *            the input mode
     */
    public void setInputMode(InputMode inputMode) {
        this.inputMode = inputMode;
    }

    /**
     * Returns the InputMode.
     * 
     * @return the input mode
     * 
     * @see #setInputMode
     */
    public InputMode getInputMode() {
        return this.inputMode;
    }

    /**
     * Sets the audio file .
     * 
     * @param audioFile
     *            the audioFile
     */
    public void setAudioFile(String audioFile) {
        this.audioFile = audioFile;
    }

    /**
     * Sets multi audio. If true, and an audio file has been set output will be
     * sent to multiple files
     * 
     * @param multiAudio
     *            if <code>true</code> send output to multiple files.
     */
    public void setMultiAudio(boolean multiAudio) {
        this.multiAudio = multiAudio;
    }

    /**
     * Sets streaming audio. If true, output will be sent to
     * 
     * @param streamingAudio
     *            if <code>true</code> stream audio
     */
    public void setStreamingAudio(boolean streamingAudio) {
        this.streamingAudio = streamingAudio;
    }

    /**
     * Prints the usage message for FreeTTS.
     */
    static void usage(String voices) {
        System.out.println(VERSION);
        System.out.println("Usage:");
        System.out.println("    -detailedMetrics: turn on detailed metrics");
        System.out.println("    -dumpAudio file : dump audio to file ");
        System.out.println("    -dumpAudioTypes : dump the possible"
                + " output types");
        System.out.println("    -dumpMultiAudio file : dump audio to file ");
        System.out.println("    -dumpRelations  : dump the relations ");
        System.out.println("    -dumpUtterance  : dump the final utterance");
        System.out
                .println("    -dumpASCII file : dump the final wave to file as ASCII");
        System.out.println("    -file file      : speak text from given file");
        System.out.println("    -lines file     : render lines from a file");
        System.out.println("    -help           : shows usage information");
        System.out.println("    -voiceInfo      : print detailed voice info");
        System.out.println("    -metrics        : turn on metrics");
        System.out.println("    -run  name      : sets the name of the run");
        System.out.println("    -silent         : don't say anything");
        System.out.println("    -streaming      : use streaming audio player");
        System.out.println("    -text say me    : speak given text");
        System.out.println("    -url path       : speak text from given URL");
        System.out.println("    -verbose        : verbose output");
        System.out.println("    -version        : shows version number");
        System.out.println("    -voice VOICE    : " + voices);
    }

    /**
     * Starts interactive mode on the given FreeTTS. Reads text from the console
     * and gives it to FreeTTS to speak. terminates on end of file.
     * 
     * @param freetts
     *            the engine
     */
    private static void interactiveMode(FreeTTS freetts) {
        try {
            while (true) {
                String text;
                BufferedReader reader;
                reader = new BufferedReader(new InputStreamReader(System.in));
                System.out.print("Enter text: ");
                System.out.flush();
                text = reader.readLine();
                if ((text == null) || (text.length() == 0)) {
                    freetts.shutdown();
                    System.exit(0);
                } else {
                    freetts.batchTextToSpeech(text);
                }
            }
        } catch (IOException e) {
        }
    }

    /**
     * Dumps the possible audio output file types
     */
    private static void dumpAudioTypes() {
        AudioFileFormat.Type[] types = AudioSystem.getAudioFileTypes();

        for (int i = 0; i < types.length; i++) {
            System.out.println(types[i].getExtension());
        }
    }

    /**
     * The main entry point for FreeTTS.
     */
    public static void main(String[] args) {

        String text = null;
        String inFile = null;
        boolean dumpAudioTypes = false;
        Voice voice = null;

        VoiceManager voiceManager = VoiceManager.getInstance();
        String voices = voiceManager.toString();

        // find out what Voice to use first
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-voice")) {
                if (++i < args.length) {
                    String voiceName = args[i];
                    if (voiceManager.contains(voiceName)) {
                        voice = voiceManager.getVoice(voiceName);
                    } else {
                        System.out.println("Invalid voice: " + voiceName);
                        System.out.println("  Valid voices are " + voices);
                        System.exit(1);
                    }
                } else {
                    usage(voices);
                    System.exit(1);
                }
                break;
            }
        }

        if (voice == null) { // default Voice is kevin16
            voice = voiceManager.getVoice("kevin16");
        }

        if (voice == null) {
            throw new Error("The specified voice is not defined");
        }
        FreeTTS freetts = new FreeTTS(voice);

        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-metrics")) {
                voice.setMetrics(true);
            } else if (args[i].equals("-detailedMetrics")) {
                voice.setDetailedMetrics(true);
            } else if (args[i].equals("-silent")) {
                freetts.setSilentMode(true);
            } else if (args[i].equals("-streaming")) {
                freetts.setStreamingAudio(true);
            } else if (args[i].equals("-verbose")) {
                Handler handler = new ConsoleHandler();
                handler.setLevel(Level.ALL);
                Logger.getLogger("com.sun").addHandler(handler);
                Logger.getLogger("com.sun").setLevel(Level.ALL);
            } else if (args[i].equals("-dumpUtterance")) {
                voice.setDumpUtterance(true);
            } else if (args[i].equals("-dumpAudioTypes")) {
                dumpAudioTypes = true;
            } else if (args[i].equals("-dumpRelations")) {
                voice.setDumpRelations(true);
            } else if (args[i].equals("-dumpASCII")) {
                if (++i < args.length) {
                    voice.setWaveDumpFile(args[i]);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-dumpAudio")) {
                if (++i < args.length) {
                    freetts.setAudioFile(args[i]);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-dumpMultiAudio")) {
                if (++i < args.length) {
                    freetts.setAudioFile(args[i]);
                    freetts.setMultiAudio(true);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-version")) {
                System.out.println(VERSION);
            } else if (args[i].equals("-voice")) {
                // do nothing here, just skip the voice name
                i++;
            } else if (args[i].equals("-help")) {
                usage(voices);
                System.exit(0);
            } else if (args[i].equals("-voiceInfo")) {
                System.out.println(VoiceManager.getInstance().getVoiceInfo());
                System.exit(0);
            } else if (args[i].equals("-text")) {
                freetts.setInputMode(InputMode.TEXT);
                // add the rest of the args as text
                StringBuffer sb = new StringBuffer();
                for (int j = i + 1; j < args.length; j++) {
                    sb.append(args[j]);
                    sb.append(" ");
                }
                text = sb.toString();
                break;
            } else if (args[i].equals("-file")) {
                if (++i < args.length) {
                    inFile = args[i];
                    freetts.setInputMode(InputMode.FILE);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-lines")) {
                if (++i < args.length) {
                    inFile = args[i];
                    freetts.setInputMode(InputMode.LINES);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-url")) {
                if (++i < args.length) {
                    inFile = args[i];
                    freetts.setInputMode(InputMode.URL);
                } else {
                    usage(voices);
                }
            } else if (args[i].equals("-run")) {
                if (++i < args.length) {
                    voice.setRunTitle(args[i]);
                } else {
                    usage(voices);
                }
            } else {
                System.out.println("Unknown option:" + args[i]);
            }
        }

        if (dumpAudioTypes) {
            dumpAudioTypes();
        }

        freetts.startup();

        if (freetts.getInputMode() == InputMode.TEXT) {
            freetts.batchTextToSpeech(text);
        } else if (freetts.getInputMode() == InputMode.FILE) {
            freetts.fileToSpeech(inFile);
        } else if (freetts.getInputMode() == InputMode.URL) {
            freetts.urlToSpeech(inFile);
        } else if (freetts.getInputMode() == InputMode.LINES) {
            freetts.lineToSpeech(inFile);
        } else {
            interactiveMode(freetts);
        }

        if (freetts.getVoice().isMetrics() && !freetts.getSilentMode()) {
            // [[[ TODO: get first byte timer times back in ]]]
            // freetts.getFirstByteTimer().showTimes();
            // freetts.getFirstSoundTimer().showTimes();
        }

        freetts.shutdown();
        System.exit(0);
    }
}
