import java.net.URL;
import java.awt.Font;
import java.net.URLConnection;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Vector;
import java.io.*;



/**
 * This class is used to load the text data for TextScroll in an
 * asynchronous way.  It will also apply the automatic line wrapping
 * to the data.  It provides a <CODE>refresh()</CODE> method which
 * can be used to force this <CODE>DataLoader</CODE> to reload
 * the data from the URL it was given when it was constructed.
 *
 * @version 1.3, 04 Jun 1998
 * @author Kevin Swan, 013639s@dragon.acadiau.ca
 */
public class DataLoader implements Runnable {

  /**
   * The current version of this class.
   */
  public static final String VERSION = "1.3";

  /**
   * This is how long the thread should sleep when waiting
   * for a <CODE>refresh()</CODE> request or a <CODE>stop()</CODE>
   * request.  Default is half a second.
   */
  private static final int DELAY = 500;



  /** The thread of execution.  This allows it to run asynchronously. */
  private Thread thread = null;

  /** The URL to load the data from. */
  private URL url = null;

  /** The array to store the data in. */
  private String[] textData = null;

  /** Variable used to flag if the data is ready or not. */
  private boolean dataReady = false;

  /** Variable used to flag if an error occurred. */
  private boolean errorOccurred = false;

  /**
   * Variable used to indicate that this <CODE>DataLoader</CODE>
   * should reload the data from the URL it was given in the
   * constructor.
   */
  private boolean shouldRefresh = true;

  /**
   * Variable used to indicate that this <CODE>DataLoader</CODE>
   * should apply line wrapping to the text after it is loaded
   * before delivering it to the caller.
   */
  private boolean shouldWrap;

  /**
   * Variable used to flag when the DataLoader should terminate
   * its thread.
   */
  private boolean isAlive = false;

  /** The starting font to use when applying line wrapping. */
  private Font startingFont;

  /** The width to assume when applying line wrapping. */
  private int width;



  /**
   * Constructor.  It expects the URL of the data file to load from.
   * The constructed <CODE>DataLoader</CODE> will apply line
   * wrapping to the retrieved data.
   *
   * @param url The URL to load the data from.
   * @param startingFont The starting Font, used for line wrapping.
   * @param width The width of the starting font, use for line
   *                wrapping.
   */
  public DataLoader (URL url, Font startingFont, int width) {
    this (url, startingFont, width, true);
  }



  /**
   * Constructor.  It expects the URL of the data file to load from.
   * It returns immediately.
   *
   * @param url The URL to load the data from.
   * @param startingFont The starting Font, used for line wrapping.
   * @param width The width of the starting font, use for line
   *                wrapping.
   * @param shouldWrap A boolean indicating whether or not this
   *                <CODE>DataLoader</CODE> should apply line
   *                wrapping or not.
   */
  public DataLoader (URL url, Font startingFont, int width, boolean shouldWrap) {
    this.url = url;
    this.dataReady = false;
    this.shouldRefresh = true;
    this.shouldWrap = shouldWrap;
    this.errorOccurred = false;
    this.startingFont = startingFont;
    this.width = width;
    this.isAlive = true;
    this.thread = new Thread (this);
    this.thread.start ();
  }



  /**
   * This method is called to reload the data from the URL.  It returns
   * immediately.  It performs automatic line wrapping based on the
   * width and <CODE>Font</CODE> given in the constructor.
   */
  public void refresh () {
    this.shouldRefresh = true;
  }



  /**
   * This method actually loads the data.  It sets the internal flags
   * appropriately, such that calls to <CODE>dataReady()</CODE> and
   * <CODE>errorOccurred</CODE> will always return correct information.
   */
  public void run () {

    outer:
    while (isAlive) {

      while (!shouldRefresh) {
        try {
          this.thread.sleep (DataLoader.DELAY);
          if (!isAlive) {
            break outer;
          }
        } catch (InterruptedException ie) {
          isAlive = false;
          break outer;
        }
      }

      URLConnection dataConnection = null;
      BufferedReader dis = null;

      try {
        dataConnection = this.url.openConnection ();
      } catch (IOException ioe) {
        this.errorOccurred = true;
        return;
      }

      try {
		dis =new BufferedReader(new InputStreamReader(dataConnection.getInputStream ()));
        //dis = new DataInputStream (dataConnection.getInputStream ());
      } catch (IOException ioe) {
        this.errorOccurred = true;
        return;
      }

      String line = null;
      Vector data = new Vector ();
      int index;

      while (true) {
        try {
          line = dis.readLine ();
        } catch (IOException ioe) {
          this.errorOccurred = true;
          return;
        }

        if (line == null)
          break;

        data.addElement (line + "\n");
      } /* while */

      this.textData = new String[data.size ()];
      for (int i = 0 ; i < data.size () ; i++)
        this.textData[i] = new String ((String) data.elementAt (i));

      try {
        dis.close ();
      } catch (IOException ioe) {
        System.err.println ("IOException closing DataInputStream on text data.");
        /* At this point, we can keep going.  Consider this non-fatal. */
      }

      /* Apply line wrapping, if we should. */
      if (this.shouldWrap) {
        LineWrapManager wrapManager = new LineWrapManager (this.textData,
                                                       this.startingFont);
        String[] tmpArr = wrapManager.wrapForWidth (this.width);
        this.textData = new String [tmpArr.length];
        System.arraycopy (tmpArr, 0, this.textData, 0, tmpArr.length);
      }

      /* Finally, get rid of any newlines that made it past the line wrapper. */
      for (int i = 0 ; i < this.textData.length ; i++)
        this.textData [i] = this.textData [i].trim ();

      this.dataReady = true;

      this.shouldRefresh = false;

    }

  } 
  public void stop () {
    this.isAlive = false;
  }
  public void start () {
    if (this.isAlive)
      return;
    this.isAlive = true;
    this.thread = new Thread (this);
    this.thread.start ();
  }
  public boolean dataReady () {
    return this.dataReady;
  }
  public boolean errorOccurred () {
    return this.errorOccurred;
  }
  public String[] getData () {
    if (!this.dataReady () || this.errorOccurred ())
      return null;
    else
      return textData;
  }

}
