package nxm.audio.lib;

import javax.sound.sampled.SourceDataLine;

import nxm.net.lib.DatagramData;  // Updated to use NET Option tree in 3.7.2 for NXM-3048

/**
   Reads audio data from a DatagramData in its own thread. In order to use this class, after calling the constructor,
   the open method must be called supplying the host and port parameters. The start() method may then be called
   to begin reading the audio data and playing it on the SourceDataLine.
 */
public class DataReader extends Thread {

    private DatagramData dgd    = null;
    private String host         = null;

    private int port            = 0;
    private int timeout         = 1000;
    private int sleeptime       = 250;
    private int audiotype       = 0;
    private int lineNumber  = -1;

    private short[] decodeTable = null;
     //investigate different audio format/conversion scheme

    private boolean verbose     = false;
    private boolean packetDebug = false;
    private boolean run         = true;
    private boolean rtp         = true;

    private byte[] buf = new byte[65536];
    private byte[] ndata = new byte[131072];

    private boolean off = false;
    private float pan = 0;
    private int volume = 0;
    private SourceDataLine line = null;

    /**
       Instantiate a new DataReader

       @param lineNumber Represents the line number of the SourceDataLine for which this DataReaders reads audio data.
       @param dt decode table used for mulaw-formatted audio data
       @param atype audio type: MULAW if atype&lt;=0<br>
       16-bit linear otherwise
       @param line SourceDataLine to which the audio data is written
     */
    public DataReader(int lineNumber,short[] dt, int atype, SourceDataLine line) {
       this.lineNumber = lineNumber;
       this.decodeTable = dt;
       this.audiotype = atype;
       this.line = line;
    }

    /**
       Returns the host of the DatagramData socket
       @return the host name
     */
    public String getHost() { return host; }
    /**
       Returns the port number of the DatagramData socket
       @return the port number
     */
    public int getPort() { return port; }
    /**
       Returns the volume of the SourceDataLine
       @return the volume
     */
    public int getVolume() { return volume; }
    /**
       Returns the pan of the SourceDataLine
       @return the pan
     */
    public float getPan() { return pan; }
    /**
       Returns the SourceDataLine this DataReader is currently writing to.
       @return the SourceDataLine
     */
    public SourceDataLine getLine() { return line; }
    /**
       Returns the line number
       @return the line number
     */
    public int getLineNumber() { return lineNumber; }
    /**
     * Get the audio type (MULAW or 16-bit linear)
     * @return the audio type
     */
    public int getAudioType() { return audiotype; }

    /**
       Sets the host from which the DatagramData reads
       @param host the host name
     */
    public void setHost(String host) { this.host=host; if (dgd!=null) dgd.setHost(host); }
    /**
       Sets the port from which the DatagramData reads
       @param port the port number
     */
    public void setPort(int port) { this.port=port; if (dgd!=null) dgd.setPort(port); }
    /**
       Sets the DatagramData's rtp flag
       @param rtp the rtp flag value (true if rtp, false otherwise)
     */
    public void setRTP(boolean rtp) { this.rtp=rtp; if (dgd!=null) dgd.setRTP(rtp); }
    /**
       Sets the DatagramData's timeout value
       @param timeout the timeout value in milliseconds
     */
    public void setTimeOut(int timeout) { this.timeout=timeout; if (dgd!=null) dgd.setTimeOut(timeout); }
    /**
       Sets the packet debug flag. If true, debug statements are printed out.
       @param packetDebug the debug flag value (defaults to false)
     */
    public void setPacketDebug(boolean packetDebug) { this.packetDebug=packetDebug; if (dgd!=null) dgd.setPacketDebug(packetDebug); }
    /**
       Sets the DatagramData's verbose flag. If true,
       information is printed when the DatagramData's settings have been changed.
       @param verbose the verbose flag value
     */
    public void setVerbose(boolean verbose) { this.verbose = verbose; if (dgd!=null) dgd.setVerbose(verbose); }
    /**
       sets the volume of the SourceDataLine
       @param vol the new volume setting
     */
    public void setVolume(int vol) { this.volume = vol; }
    /**
       Sets the pan of the SourceDataLine
       @param pan the new pan value
     */
    public void setPan(float pan) { this.pan = pan; }
    /**
       sets the AudioType
       @param audiotype the audio type of the data
       <table>
        <caption style="visibility:hidden">""</caption>
        <tr>
             <td><code>if audiotype is &lt;= 0 </code></td>
             <td> - the audio type is MULAW</td>
        </tr>
        <tr>
          <td><code>if audiotype is &gt; 0</code></td>
          <td> - the audio type is 16-bit linear</td>
        </tr>
       </table>
     */
    public void setAudioType(int audiotype) { this.audiotype = audiotype; }
    /**
       Sets the SourceDataLine to which the DatagramData is written.
       @param line the new SourceDataLine
       @return the new SourceDataLine
     */
    public SourceDataLine setLine(SourceDataLine line ) { this.line=line; return line; }
    /**
       Sets the off flag.
       @param off the new off flag value
     */
    public void setOff(boolean off) { this.off = off; }
    /**
       Sets the line number
       @param lineNumber the line number
     */
    public void setLineNumber(int lineNumber) {  this.lineNumber=lineNumber; }

    /**
       Opens the DatagramData with the given host and port.
       @param host the host name
       @param port the port number
       @return true if the DatagramData successfully opened a socket for reading, false otherwise.
     */
    public boolean open(String host, int port) {
        try {
            this.host = host;
            this.port = port;

            dgd = new DatagramData(host, port);

            if (dgd == null) {
                System.err.println("DataReader: Could not open socket");
                return false;
            }

            setVerbose(verbose);
            setPacketDebug(packetDebug);
            setTimeOut(timeout);
            setRTP(rtp);

            dgd.open();

        } catch(Exception e) {
            System.err.println("DataReader: open failed -- " + e);
            return false;
        }
        return true;
    }

    /**
       Reads the DatagramData and writes the data to the SourceDataLine
       @return Returns true if the read was successful, false otherwise
     */
    public boolean read() {
        int outlen = 0;
        short newval = 0;
        int playla,playoutsize;
        try {
          if (dgd==null) return false;
          int offset=0;
          int len = dgd.read(buf);
          if (len<1) return false;
          if (len>0 && line != null) {
            if (audiotype<=0) {
              for (int i=0,j=0;i<len;i++,j+=2) {
                 newval= decodeTable[buf[i]+128 ];
                 ndata[j]=(byte)(newval&0xFF);
                 ndata[j+1]=(byte)(newval>>8);
              }//end for loop

              outlen = len*2;
              if (line==null) { System.err.println("Null Line to play"); return true; }
              try {
                 playla = line.available();
                 if (playla<outlen) playoutsize=playla;
                 else playoutsize=outlen;

                 if (verbose && playoutsize<outlen) System.err.println("Truncated data. Playoutsize="+playoutsize+". Len="+outlen);
                 line.write(ndata,0,playoutsize);
              } catch (Exception e) {
                 System.err.println("Play Failed: " + e);
                 e.printStackTrace();
              }
            } else {
              if (line==null) { System.err.println("Null Line to play"); return true; }
              try {
                 playla = line.available();
                 if (playla<outlen) playoutsize=playla;
                 else playoutsize=len;
                 if (verbose && playoutsize<len) System.err.println("Truncated data. Playoutsize="+playoutsize+". Len="+len);
                 line.write(buf,0,playoutsize);
              } catch (Exception e) {
                  System.err.println("Play Failed: " + e);
                  e.printStackTrace();
              }
            }
          } else {
            return false;
          }
        } catch (Exception e) {
          System.err.println("DataReader: Read failed. "+e);
          e.printStackTrace();
        }
        return true;
    }

    /**
       Run the thread.
     */
    public void run() {
        try {
            run=true;
            while (run) {
                if (dgd==null) { Thread.sleep(timeout); }
                else
                  if (!read()) { Thread.sleep(sleeptime); }
            }
        } catch(Exception e) {}
    }

    /**
       Closes the DatagramData resource
     */
    public void close() {
       try {
          if (dgd != null) {
              dgd.leave();
              dgd.close();
              dgd=null;
          }
       } catch(Exception ex) {
          System.err.println("Exception in DataReader close()" + ex);
       }
    }

    /**
      Stops the thread and closes the DatagramData resource
     */
    public void shutdown() {
        run = false;
        try {
            if (dgd != null) {
                dgd.leave();
                dgd.close();
                dgd=null;
            }
        } catch(Exception ex) {
            System.err.println("Exception in shutdown()" + ex);
        }
        try {
            join();
        } catch (Exception e) {
            System.err.println("Exception waiting for thread to end: " + e);
        }
    }

    private void pack (byte[] buf, int boff, int value, int bytes) {
      for (int i=bytes-1; i>=0; i--) {
        buf[boff+i] = (byte)(value&0xFF);
        value = (value>>8);
      }
    }
}