
import java.net.*;
import java.io.*;
import java.util.*;
import java.applet.*;

//--------------------------------------------
/**
 * 
 *  This class represents a SoundDownload of a language sound.
 *  This class will store data about the process of attempting
 *  to download the sound resource from the internet. The class
 *  will also be responsable for actually initiating the download
 *  process by opening a connection to the resource etc.
 *  This class should normally run within a thread begun by the 
 *  DownloadManager class. The SoundDownload can either store the 
 *  downloaded sound file as an object or it can save the sound
 *  to a local file, which is preferable, but possibly not allowed
 *  in an applet environment.
 *
 *  @see <a href=http://bumble.sf.net/lang/web/WebDownload.java>WebDownload</a>
 *  @see "http://bumble.sf.net/lang/web/WebDownload.java"
 *  @author matth3wbishop<at>yahoo<dot>com
 */ 

public class SoundDownload extends Object
{
  //--------------------------------------------
  /** A record from the data file. The SoundDownload doesnt need
   *  to know anything about the word data of the record, just
   *  the sound resource Url. The class design may be incorrect */
  private Record record;
  //--------------------------------------------
  /** The time at which the attempted download was begun */
  private Date startTime;
  //--------------------------------------------
  /** The time at which the attempted download was terminated
   *  whether successfully or unsuccessfully */
  private Date finishTime;
  //--------------------------------------------
  /** A place to store the downloaded sound resource, if the file
   *  cannot be saved to the local disk because of security 
   *  restrictions such as those imposed by an applet */
  private AudioClip soundObject;
  //--------------------------------------------
  /** will store error messages produced when attempting the download
   *  of the data in the data file and in correcting that data. */ 
  private StringBuffer errorMessage;

  //--------------------------------------------
  private String guessedContentType;
  //--------------------------------------------
  private String protocol;
  //--------------------------------------------
  private int httpResponseCode;
  //--------------------------------------------
  private String httpResponseMessage;
  //--------------------------------------------
  private String httpContentType;
  //--------------------------------------------
  private int httpContentLength;
  //--------------------------------------------
  private String httpContentEncoding;

  //--------------------------------------------
  private String failureCause;
  //--------------------------------------------
  private boolean wasSuccessful;
  //--------------------------------------------
  private boolean hasStarted;
  //--------------------------------------------
  private boolean hasFinished;
  //--------------------------------------------
  public static String NEWLINE = System.getProperty("line.separator");

  //--------------------------------------------
  //-- CONSTRUCTORS

  public SoundDownload()
  {
    this.protocol = "";
    this.guessedContentType = "";
    this.httpContentType = "";
    this.httpContentLength = 0;
    this.httpContentEncoding = "";
    this.httpResponseCode = 0;
    this.httpResponseMessage = "";

    this.failureCause = "";
    this.errorMessage = new StringBuffer("");    
    this.hasStarted = false;
    this.wasSuccessful = false;
    this.hasFinished = false;
    this.record = new Record();
    //this.startTime = new Date(0);
    //this.finishTime = new Date(0);
  } //-- constr: ()

  //--------------------------------------------
  public SoundDownload(Record dataRecord)
  {
    this();
    this.record = dataRecord;

  } //-- constr: (Record)


  //-- METHODS

  //--------------------------------------------
  /* advises whether the attempted sound download was carried out successfully */
  public boolean wasSuccessful() 
    { return this.wasSuccessful; }

  //--------------------------------------------
  /** begin the download process. If this class is running in
   *  an applet context then all that will be required in some
   *  situations is to call the getAudioClip method of the 
   *  applet. With most newish jre's this should play different
   *  file formats, but not mp3. If the applet is saving files
   *  to the local file system, then still the getAudioClip 
   *  method should be sufficient */
  public void download()
  {
    
    URL uSoundUrl;
    URLConnection urlc;
    HttpURLConnection httpc;

    this.hasStarted = true;
    this.startTime = new Date();

    try
    {
      uSoundUrl = new URL(this.record.getSoundUrl()); 	  
      this.protocol = uSoundUrl.getProtocol();
    }
    catch (MalformedURLException e)
    {
      this.failureCause = "Illegal Url";
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    }  //-- try
      
    try
    {
      urlc = uSoundUrl.openConnection();
    }     
    catch (IOException e)
    {
      this.failureCause = "Couldnt create a connection";
      //this.errorMessage.append(e.getMessage());
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    }  //-- try


    try
    {
      urlc.connect();
    }     
    catch (UnknownHostException e)
    {
      this.failureCause = "The host '" + uSoundUrl.getHost() + "' could not be found";
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    } 
    catch (IOException e)
    {
      this.failureCause = "Couldnt establish a connection to URL";
      //--
      //-- getMessage provides less information than e.toString
      //this.errorMessage.append(e.getMessage());
      this.errorMessage.append(e);
      this.wasSuccessful = false;
      this.finishTime = new Date();
      this.hasFinished = true; 
      return;
    } //-- try

    //--
    //-- try to guess the content type from the stream
    /*
    try
    {
      InputStream stream = urlc.getInputStream();
      this.guessedContentType =
        URLConnection.guessContentTypeFromStream(stream);
      stream.close();

    }
    catch (IOException e)
    {
      this.errorMessage.append(
       "No stream could be opened from the url so it is");
      this.errorMessage.append(NEWLINE);
      this.errorMessage.append(
       "not likely that a sound object could be created");
    }
    */

    /* cast the connection object so as to find out the precise cause
       of any download problems */
    if (uSoundUrl.getProtocol().equalsIgnoreCase("http"))
    {
      httpc = (HttpURLConnection)urlc;
      try
      {
        this.httpResponseCode = httpc.getResponseCode();
        this.httpResponseMessage = httpc.getResponseMessage();
      }
      catch (IOException e)
      {     
      }

      if (this.httpResponseCode == 404)
      {
        this.finishTime = new Date();
        this.wasSuccessful = false;
        this.hasFinished = true;
        this.failureCause = "file not found on server";
        this.errorMessage.append(
          "Message from host '" + uSoundUrl.getHost() + "'" + NEWLINE +
          this.httpResponseCode + " " + this.httpResponseMessage);
        return;
      }


      if ((this.httpResponseCode < 200) || (this.httpResponseCode > 299))
      {
        this.finishTime = new Date();
        this.wasSuccessful = false;
        this.hasFinished = true;
        this.failureCause = "server refused request";
        this.errorMessage.append(NEWLINE +
          "Server refused request : " + NEWLINE +
          "Server response code   : " + this.httpResponseCode + NEWLINE +
          "Server response message: " + this.httpResponseMessage + NEWLINE);
        return;
      }
     
      this.httpContentType = httpc.getContentType();
      this.httpContentLength = httpc.getContentLength();
      this.httpContentEncoding = httpc.getContentEncoding();

      //-- check if the resource really is audio
      if (!this.httpContentType.startsWith("audio"))
      {
        this.finishTime = new Date();
        this.wasSuccessful = false;
        this.hasFinished = true;
        this.failureCause = "content is not audio";
        return;
      } 

      //-- the static method below may be useful to do further checks 
      //-- on the content type of the resource.
    } //-- if is http


    //-- I believe that this method requires Java 1.2
    this.soundObject = Applet.newAudioClip(uSoundUrl);
    urlc = null;

    //System.out.println("trying to play...");
    //this.soundObject.play();
    this.finishTime = new Date();
    this.wasSuccessful = true;
    this.hasFinished = true;
  } //-- method: download()


  //--------------------------------------------
  /** prints information such as the start time, the finish
   *  time, the duration and the success or failure of the 
   *  download. */
  public String printStatistics()
  {
    StringBuffer sbReturn = new StringBuffer("");


    //-- if the downlaod has not been attempted print a
    //-- brief message and return
    if (this.startTime == null)
    {
      return this.toString();
    }


    sbReturn.append("Tried to get resource >");
    sbReturn.append(this.record.getSoundUrl());
    sbReturn.append(NEWLINE);

    if (this.wasSuccessful)
    {
      sbReturn.append("Result of the download>");
      sbReturn.append("SUCCESS");
      sbReturn.append(NEWLINE);
    }
    else
    {
      sbReturn.append("Result of the download>");
      sbReturn.append("FAILURE");
      sbReturn.append(NEWLINE);
    }

    sbReturn.append("Download duration (s) >");
    sbReturn.append((float)this.getDownloadDuration()/1000);
    sbReturn.append(NEWLINE);


    if (!this.failureCause.equals(""))
    {
      sbReturn.append("Cause of failure      >");
      sbReturn.append(this.failureCause);
      sbReturn.append(NEWLINE);
    }

    sbReturn.append("Protocol used         >");
    sbReturn.append(this.protocol);
    sbReturn.append(NEWLINE); 
    sbReturn.append("Guessed Content Type  >");
    sbReturn.append(this.guessedContentType);
    sbReturn.append(NEWLINE);
    sbReturn.append("Http Content Type     >");
    sbReturn.append(this.httpContentType);
    sbReturn.append(NEWLINE);
    sbReturn.append("Http Content Length   >");
    sbReturn.append(this.httpContentLength);
    sbReturn.append(NEWLINE);
    sbReturn.append("Http Content Encoding >");
    sbReturn.append(this.httpContentEncoding);
    sbReturn.append(NEWLINE);

    sbReturn.append("Start Time   >");
    sbReturn.append(this.startTime);
    sbReturn.append(NEWLINE);
    sbReturn.append("Finish Time  >");
    sbReturn.append(this.finishTime);
    sbReturn.append(NEWLINE);
    sbReturn.append("Error Messages generated during the download >");
    sbReturn.append(NEWLINE);
    sbReturn.append(this.errorMessage);
    sbReturn.append(NEWLINE);

    sbReturn.append("");
    sbReturn.append("");
    sbReturn.append("");

    return sbReturn.toString();
  }
  
  //--------------------------------------------
  /** this returns null if the download failed  */
  public AudioClip getSoundObject()
  {
    return this.soundObject;
  }

  //--------------------------------------------
  /** gets the data record  */
  public Record getRecord()
  {
    return this.record;
  }

  //--------------------------------------------
  /** return the number of milliseconds taken to download the file. */
  public long getDownloadDuration()
  {
    
    if (this.finishTime == null) { return 0; }
    if (this.startTime == null) { return 0; }
    return this.finishTime.getTime() - this.startTime.getTime();
  } 


  //--------------------------------------------
  /** how is this different from toString */
  public String print()
  { 
    return this.toString();
  }

  //--------------------------------------------
  /** a reasonably concise representation of the object */
  public String toString()
  {
    StringBuffer sbReturn = new StringBuffer("");

    sbReturn.append("Sound Download (url=");
    sbReturn.append(this.record.getSoundUrl());
    sbReturn.append(")");

    //--
    if (this.startTime == null)
    {
      sbReturn.append(" -not started- ");
      return sbReturn.toString();
    }

    if (this.wasSuccessful)
    {
      sbReturn.append(" ");
      sbReturn.append("successful ");
    }
    else
    {
      sbReturn.append(" ");
      sbReturn.append("failed ");
    }

    sbReturn.append("-at- ");
    sbReturn.append(this.startTime);
    sbReturn.append(" ");

    if (this.wasSuccessful)
    {
      sbReturn.append("[size: ");
      sbReturn.append(this.httpContentLength);
      sbReturn.append("]");
    }
    else
    {
      sbReturn.append("[");
      sbReturn.append(this.failureCause);
      sbReturn.append("]");
    }

    sbReturn.append("");
    //sbReturn.append(NEWLINE);

    return sbReturn.toString();
  }  //-- method: toString

  //--------------------------------------------
  public static void main(String[] args) throws Exception
  {
    String sUsageMessage = "usage: java SoundDownload [url]";
    String sTestUrl;

    if (args.length == 0)
    {	    
      System.out.println(sUsageMessage);
      System.exit(-1);
    }


    if (args[0].equals("."))
    {
      //-- a test wav file
      sTestUrl =
       "http://java.sun.com/docs/books/tutorial/sound/example-1dot2/bottle-open.wav";
    }
    else
    {
      sTestUrl = args[0];
    }
    
    Record rTest = new Record();
    rTest.setSoundUrl(sTestUrl);

    SoundDownload sdTest = new SoundDownload(rTest);
    sdTest.download();
    AudioClip acTest;
    acTest = sdTest.getSoundObject();

    if (acTest != null)
    {
      System.out.println("trying to play sound ...");
      System.out.println("");
      acTest.play();
    }
    else
    {
      //System.out.println("no sound to play");
    }

    System.out.println(sdTest.printStatistics());
    System.out.println(sdTest.toString());
    sdTest = null;
    acTest = null;

  } //-- main()
} //-- SoundDownload class
