import java.io.*;
import com.ms.dll.*;
import cnrg.itx.datax.*;
import xvoice.*;

/**
 * Class implementing a stream source. This class will take any InputStream and
 * synchronize it to make it a valid <code>Source</code>.
 */
public class cupsStreamSource implements Source, Runnable
{
	/** @dll.import("wavutildll") */
	private static native int convertWavefile(char[] infile, char[] outfile, int rate, int bits);

	/**
	 * Thread start/stop flags
	 */
	private boolean start;
	
	/**
	 * Attribute to store the input stream represented by this source.
	 */
	private InputStream inputStream;
	
	/**
	 * Attribute for storing the stream statistics.
	 */
	private Stats streamStats;
	
	/**
	 * Attribute for storing the Channel reference.
	 */
	private Channel channel;
	
	/**
	 * Attribute for storing the device instance number. This is a static attribute and will be
	 * incremented in the constructor. This represents the total number of instances of
	 * the stream source.
	 */
	private static int instances = 0;
	
	/**
	 * Attribute for storing the device ID. This is the value of the instance at the moment.
	 */
	private int iDevice;
	
	/**
	 * Attribute for storing the number of bytes written by this source.
	 */
	private int bytesWritten = 0;
	
	/**
	 * Attribute to store the thread object.
	 */
	private Thread thread;
	
	/**
	 * Should this source implement flow control?
	 */
	private boolean flowControl;
	
	/**
	 * How long should the transfer thread wait between writes?
	 */
	private long waitTime;
	
	/**
	 * How many buffers should the transfer thread keep?
	 */
	private int bufferSize;
	
	/**
	 * How many samples are currently buffered?
	 */
	private int numBuffered;
	
	/**
	 * Record of last call to System.currentTimeMillis()
	 */
	private long startTime;
	
	private boolean mute;
	
	/**
	 * pointer to the MS text to speech object
	 */
	private DirectSSRaw m_speaker;
	
	/**
	 * temp files to be used at runtime
	 */
	private String[] tempFiles = new String[3];
	
	/**
	 * semaphore for synchronization between TTS and stream thread
	 */
	private boolean hasMoreData = false;
	private boolean stuck = false;
	
	/**
	 * linked list of texts
	 */
	private List head;
	private List tail;
	
	/**
	 * tts mode
	 */
	private int m_speechMode;
	
	/**
	 * used to kill thread
	 */
	private boolean die = false;
			
	public static final int RATE        = 8000;
	public static final int SAMPLE_SIZE = 400;
	public static final int BITS        = 8;
	
	/**
	 * Constructor to make a StreamSource from any input stream (flow control)
	 * @param inputStream The input Stream
	 * @param channel The audio channel
	 * @param bufferTime The amount of data to keep buffered on the output stream in ms
	 */
	public cupsStreamSource(Channel channel, long bufferTime, DirectSSRaw p_sp)
	{
		// Increment the number of instances and assign the device id
		instances++;
		iDevice = instances;

		this.channel = channel;
		
		// Create a new strem stats object
		streamStats = new Stats();
		
		// Initialize thread flag
		start = false;
		
		// Flow control calculations
		flowControl = true;
		waitTime = (SAMPLE_SIZE * 1000) / RATE;							//((samples/buffer) * (ms/sec)) / (samples/sec) = ms/buffer
		bufferSize = (int)(((RATE/1000) * bufferTime) / SAMPLE_SIZE);	// (samples/ms) * ms = samples
		numBuffered = 0;
		
		m_speaker = p_sp;
		tempFiles[0] = "T000001.wav";
		tempFiles[1] = "T000002.wav";
		tempFiles[2] = "T000003.wav";
//		m_speaker.setRealTime(800);
		m_speaker.setFileName(tempFiles[0]);
		try {
			inputStream = new FileInputStream("ringin.wav");
		}
		catch (FileNotFoundException fnfe) 
		{
		}
	}	
	
	/**
	 * Method to close the input stream.
	 */
	public void close()
	{
		// Stop the thread
		stop();
		
		// Close the input stream
		try
		{
			inputStream.close();
		}
		catch(IOException e)
		{
			// Ignore for now..
		}
	}
	
	/**
	 * Method to mute the input stream. This method will either block or resume
	 * the stream according to the value passed to it.
	 * @param mute true to mute and false to resume the stream
	 * @return boolean The previous mute state
	 */
	public boolean mute(boolean mute)
	{
		this.mute = mute;
		return mute;
	}
	
	/**
	 * Method to get the statistics of the input stream.
	 * @return Stats The statistics object
	 */
	public Stats getStatistics ()
	{
		return streamStats; 
	}
	
	/**
	 * Method to start the source thread.
	 */
	public void start()
	{
		if (start)
			return;
	
		start = true;
	
		thread = new Thread(this);
		thread.start();
	}
	
	/**
	 * Method to play some text from the source
	 */
	public void playText(String p_text) 
	{
		//m_speechMode = m_speaker.Find("ModeName=Mary (for Telephone)");
		//m_speaker.Select(m_speechMode);
		m_speaker.Speak(p_text);
		hasMoreData = true;	
	}


	
	/**
	 * Method which acts as a thread and pushes data to the channel.
	 */
	public void run()
	{
		long interval = 0;
		int retval = 0;
		byte[] audioBuffer = new byte[SAMPLE_SIZE];
		long newWaitTime;
		int tempIndex = 0;
		
		// Initialize the system time for flow control purposes
		startTime = System.currentTimeMillis();
		
		// Are we done?
		try
		{
			while (start)
			{
				// Try to read in some bytes. Note that not all the bytes may be read
				// at one go.
				retval = inputStream.read(audioBuffer, 0, SAMPLE_SIZE);
				
				// what to do if there is no more data
				if(retval == -1) 
				{

					inputStream.close();
					while(m_speaker.getSpeaking() != 0 || !hasMoreData) 
					{
						if (die == true) return;
					}
					stuck = true;
//					m_speaker.setRealTime(800);
					m_speaker.setFileName(tempFiles[1-tempIndex]);
					convertWavefile(String2CString.String2CString(tempFiles[tempIndex]), String2CString.String2CString(tempFiles[2]), 8000, 8);
					inputStream = new FileInputStream(tempFiles[2]);
					stuck = false;
					hasMoreData = false;
					tempIndex = 1-tempIndex;
					//m_speechMode = m_speaker.Find("ModeName=Mary (for Telephone)");
					//m_speaker.Select(m_speechMode);
					while(head!=null) 
					{
						hasMoreData = true;
						m_speaker.Speak(head.m_string);
						head = head.m_next;
					}
					continue;
				}
				
				// Flow control algorithm
				if (flowControl)
				{
					numBuffered++;
					newWaitTime = waitTime - (System.currentTimeMillis() - startTime);
					if (numBuffered > bufferSize)
					{
						if (newWaitTime < 0)
						{
							// Will not count time from last buffer!  --> Therefore catches up.
							System.out.println("<StreamSource> --> falling behind on data transfer!");
						}
						else
						{
							// Wait until it is possible to send again.
							try
							{
								this.thread.sleep(newWaitTime);
							}
							catch (InterruptedException e)
							{
								System.out.println("<StreamSource> --> thread exiting");
								return;
							}
						}
					}
					else
					{
						if (waitTime > 0)
						{
							// Wait for half the time to begin filling buffer on remote end.
							try
							{
								this.thread.sleep(newWaitTime / 2);
							}
							catch (InterruptedException e)
							{
								System.out.println("<StreamSource> --> thread exiting");
								return;
							}
						}
					}
					
					// Get the new system time
					startTime = System.currentTimeMillis();					
				}
				
				// Push the data into the channel if the device is not muted
				if(!mute)
				{
					channel.push(audioBuffer);
					
					bytesWritten += audioBuffer.length;
					streamStats.addStat("<Stream Source " + iDevice + "> Bytes written ", new Integer(bytesWritten));
				}
			}
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
    }

	/**
	 * Method to stop the thread.
	 */
    public void stop()
	{	
		if (start == false)
			return;
		
		// Stop the active thread
		start = false;
		
		// Wait for the thread to join
		if (thread != null && thread.isAlive())
		{
			try
			{
				die = true;
				m_speaker.setFileName("");
				thread.join();
				System.out.print("cupsStreamSource exiting ...\n");
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		
		thread = null;
    }
	
	/**
    * Returns a collection of properties supported.
    */ 
   public PropertiesCollection getProperties() throws DataException
   {
	   return null;
   }

   /**
    * Sets the given properties collection into the device
    */
   public void setProperties(PropertiesCollection pc) throws DataException
   {
   }

   /**
    * Interface to set the given properties collection into the device. WOrks under the 
    * assumption that this is the properties collection of the peer.
    */
   public void setPeerProperties(PropertiesCollection pc) throws DataException
   {

   }
   
	class List 
	{
		public String m_string;
		public List m_next;
	}
}
