/*
 * LineWrapManager.java  1.2 15/12/98
 *
 * Copyright (c) 1997, 1998 by Kevin Swan.  All rights reserved.
 *
 * I reserve all rights to this software.  You may use it and
 * distribute it freely, provided you do not remove this header
 * and attribute partial credit to the author, Kevin Swan.
 *
 * 14 Apr 1998 Created.
 *             Released as version 1.0.
 * 16 Apr 1998 Corrected a bug in setFontSize which was causing lines to
 *               not wrap correctly.
 *             Released as version 1.1.
 * 15 Dec 1998 Modified because of changes to TextScroll.  Had
 *               to recognize the new methods.
 *             Released as version 1.2.
 */



/*
 * Commented out for unbundled distribution.
 *
 * package kevin.applets.textscroll;
 */



import java.util.StringTokenizer;
import java.util.Vector;
import java.awt.Font;
import java.awt.FontMetrics;




/**
 * This class does the line wrapping for our text.  Line wrapping is
 * complicated in an applet such as this, because the font face and size
 * are dynamic.  One cannot simply say, &quot;lines are 20 characters
 * wide.&quot; This class actually goes through and renders the entire
 * text presentation off-screen.  You would use it like this:
 *
 * <P><PRE><CODE>
 *   ... // text is our String[] of data, width is the width of the applet.
 *   LineWrapManager wrapManager = new LineWrapManager (text, face, size);
 *   text = wrapManager.wrapForWidth (width);
 * </CODE></PRE>
 *
 * <P>After that, <CODE>text</CODE> would still contain the same data,
 * but each element of the array might hold more or fewer words than it
 * did before, and it might contain more or fewer elements overall.
 *
 * <P>Wrapping lines that are too long is one thing, but it must be
 * decided whether to bring up the next line if there is room on the
 * previous line.  This class decides to do this.  This complicates
 * things slightly when you want a new paragraph.  There is one simple
 * rule to remember when composing your text file content:
 *
 * <P>
 * <BLOCKQUOTE>
 *   &quot;The first newline is always ignored.&quot;
 * </BLOCKQUOTE>
 *
 * <P>This means when you want to explicitely start a new line, you must
 * put two carriage returns directly next to each other.  Three newlines
 * in the text file will give you two newlines in the applet.
 *
 * <P>It is also important to note that directives always get their own
 * line in the data file.  When the applet encounters a directive, it
 * starts a new line, no matter what.
 *
 * <P>This class basically provides empty implementations for all the
 * methods a <CODE>DirectiveManager</CODE> might call.  It doesn't actually
 * print anything anywhere, it just uses configuration information to
 * measure lines of text, and wrap them appropriately.
 *
 * @version 1.2, 15 Dec 1998
 * @author Kevin Swan, 013639s@dragon.acadiau.ca
 */

public class LineWrapManager extends TextScroll {


  private StringTokenizer tokenizer;
  private int lineNum;
  private String[] data;
  private Font font;
  private int fontSize;
  private int fontStyle;
  private String fontFace;
  private DirectiveManager directiveManager;
  private int textInset;



  /**
   * Constructor.  Takes the data to be wrapped, the initial font face,
   * and the intial font size.
   *
   * @param text The <CODE>String</CODE> array to have line wrapping
   *             performed on it.
   * @param font The initial font to use.
   */
  public LineWrapManager (String[] text, Font font) {
    this.data = text;
    this.fontFace = font.getName ();
    this.fontSize = font.getSize ();
    this.font = font;
    this.lineNum = 0;
    this.tokenizer = new StringTokenizer (this.data[lineNum], " \t\n\r", true);
    this.directiveManager = new DirectiveManager (this);
  }



  /**
   * Doesn't need to do anything in this class.
   */
  public void setRightForegroundColor (String color) { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void setLeftForegroundColor (String color) { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void setRightBackgroundColor (String color) { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void setLeftBackgroundColor (String color) { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void setSpeed (String speed) { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void pause () { }



  /**
   * Doesn't need to do anything in this class.
   */
  public void pause (String delay) { }



  /** 
   * Ignore.  We don't care about the left pane.
   *
   * @param face The name of the font face to use
   *    in the left pane.
   */
  public void setLeftFontFace (String face) {
  }



  /** 
   * Note the new font face.
   *
   * @param face The name of the font face to use
   *    in the right pane.
   */
  public void setRightFontFace (String face) {
    this.font = new Font (face, this.fontStyle, this.fontSize);
  }



  /** 
   * Note the new font face.
   *
   * @param face The name of the font face to use
   *    in the right pane.
   */
  public void setFontFace (String face) {
    this.setRightFontFace (face);
  }



  /**
   * Ignore.  We don't care about the left pane.
   *
   * @param size The integer size to use for the font
   *    in the left pane.
   */
  public void setLeftFontSize (String size) {
  }



  /**
   * Note the new font size.
   *
   * @param size The integer size to use for the font
   *    in the right pane.
   */
  public void setRightFontSize (String size) {
    try {
      this.fontSize = Integer.parseInt (size);
    } catch (NumberFormatException nfe) {
      this.fontSize = 10;
    }
    this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
  }



  /**
   * Note the new font size.
   *
   * @param size The integer size to use for the font
   *    in the right pane.
   */
  public void setFontSize (String size) {
    this.setRightFontSize (size);
  }



  /**
   * Whether we should center the text in the left
   * pane or not.  Ignored.
   */
  public void setLeftCenter (String flag) {
  }



  /**
   * Whether we should center the text in the right
   * pane or not.  Ignored.
   */
  public void setRightCenter (String flag) {
  }



  /**
   * Whether we should center the text in the right
   * pane or not.  Ignored.
   */
  public void setCenter (String flag) {
  }



  /**
   * Sets the bold flag for the left pane based on
   * the argument.  Ignore.  We don't care about
   * the left pane.
   *
   * @param flag Should be either &quot;true&quot;
   *    or &quot;false.&quot;
   */
  public void setLeftBold (String flag) {
  }



  /**
   * Sets the bold flag for the right pane based on
   * the argument.
   *
   * @param flag Should be either &quot;true&quot;
   *    or &quot;false.&quot;
   */
  public void setRightBold (String flag) {
    if ((Boolean.valueOf (flag)).booleanValue ())
      this.fontStyle = this.fontStyle | Font.BOLD;
    else
      this.fontStyle = this.fontStyle & ~Font.BOLD;
    this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
  }



  /**
   * Sets the bold flag for the right pane based on
   * the argument.
   *
   * @param flag Should be either &quot;true&quot;
   *    or &quot;false.&quot;
   */
  public void setBold (String flag) {
    this.setRightBold (flag);
  }



  /**
   * Sets the italic flag for the left pane based on
   * the argument.  Ignore.  We don't care about the
   * left pane.
   *
   * @param flag Should be either &quot;true&quot;
   *    or &quot;false.&quot;
   */
  public void setLeftItalic (String flag) {
  }


  /**
   * Sets the italic flag for the right pane based on
   * the argument.
   *
   * @param flag Should be either &quot;true&quot;
   *    or &quot;false.&quot;
   */
  public void setRightItalic (String flag) {
    if ((Boolean.valueOf (flag)).booleanValue ())
      this.fontStyle = this.fontStyle | Font.ITALIC;
    else
      this.fontStyle = this.fontStyle & ~Font.ITALIC;
    this.font = new Font (this.fontFace, this.fontStyle, this.fontSize);
  }



  /**
   * Sets the italic flag for the right pane based on
   * the argument.
   *
   * @param flag Should be either &quot;true&quot;
   *     or &quot;false.&quot;
   */
  public void setItalic (String flag) {
    this.setRightItalic (flag);
  }



  /**
   * Sets the text inset.
   * 
   * @param size The integer size to use for the inset.
   */
  public void setInset (String insetStr) {
    try {
      this.textInset = Integer.parseInt (insetStr);
    } catch (NumberFormatException nfe) {
      return;
    }
  }



  /**
   * This method actually does all the wrapping work.  It takes
   * an integer argument and wraps the text in the <CODE>String</CODE>
   * array using that argument as a width.
   *
   * @param width The integer width of the display area to wrap to.
   *
   * @return A <CODE>String</CODE> array of text, of which every line
   *         will fit in the display area, except for lines consisting
   *         only of a single word, which itself is too wide for the
   *         display area.
   */
  public String[] wrapForWidth (int width) {

    Vector lines = new Vector ();

    String line = "";
    String tok;
    FontMetrics fm;
    boolean newlineFlag = false;


    fm = this.getToolkit ().getFontMetrics (this.font);

    /*
     * ALGORITHM: Read tokens by calling nextToken until it returns
     * null.
     */
    while (true) {

      /* Read a token. */
      tok = this.nextToken ();

      /*
       * If there are no more tokens, check to see if line contains
       * valid data.  If so, add it to the Vector.  Break out of this
       * loop.
       */
      if (tok == null) {
        if (line.length () > 0) {
          line = line.trim ();
          lines.addElement (line);
        }

        break; 
      }

      /*
       * If its a newline, check the flag.
       * If the flag is set,
       *    then the user wants an actual blank line.  Print the
       *    current line out, and leave the flag set.
       * If the flag is not set, set it and continue.
       */
      if (tok.equals ("\n"))
        if (newlineFlag) {
          line = line.trim ();
          lines.addElement (line);
          /*
           * If they had 2 in a row, they want a new paragraph.
           * Add a blank line.
           */
          lines.addElement ("");
          line = "";
          continue;
        } else {
          /* This is the first newline.  Set the flag, append a space. */
          newlineFlag = true;
          line = line.trim ();
          line += " ";
          continue;
        }

      /*
       * If we get this far, then we hit a non-newline character.
       * Un-set the newline flag.
       */
      newlineFlag = false;

      /*
       * If it is a directive, start a new line and handle
       * the directive.  Continue.
       */
      if (tok.startsWith ("^^")) {

        /*
         * We've hit a directive.  Write whatever we've got in the
         * line out now, and reset it.
         */
        if (line.length () > 0) {
          line = line.trim ();
          lines.addElement (line);
          line = "";
        }

        /* OK, now, get the rest of the line. */

        String directive = tok;

        while (true) {
          tok = nextToken ();
          if (tok == null)
            break;
          if (tok.equals ("\n"))
            break;

          directive += tok;
        }

        this.directiveManager.performDirective (directive);
        fm = this.getToolkit ().getFontMetrics (this.font);
        line = line.trim ();
        lines.addElement (directive);
        continue;
      }

      /*
       * If the current line plus this token is too long,
       *   If there is no other data in "line,"
       *     add it anyway.
       *  otherwise,
       *      add the line to the new Vector, start a
       *      new line, and add this token.
       */
      if (fm.stringWidth (line + tok) > (width - 2 - this.textInset)) {
        if (line.length () == 0) {
          lines.addElement (tok);
          continue;
        } else {
          line = line.trim ();
          lines.addElement (line);
          line = tok;
        }
      } else
        line = line.trim () + " " + tok.trim ();
    }

    String[] newData = new String [lines.size ()];
    for (int i = 0 ; i < lines.size () ; i++)
      newData [i] = (String) lines.elementAt (i);

    return newData;

  } /* wrapForWidth */



  /**
   * This method is used to tokenize the text in the array.  It lets
   * us treat the data in the array as a Stream.
   *
   * @return The next token of data.  This includes whitespace, so the
   *         caller can look for repeated newlines.
   */
  public String nextToken () {

    while (!this.tokenizer.hasMoreTokens ()) {
      if (lineNum == (this.data.length - 1))
        return null;
      this.tokenizer = new StringTokenizer (this.data [++lineNum], " \t\n\r", true);
    }

    return tokenizer.nextToken ();

  }
    


  /**
   * Lets us &quot;un-get&quot; a token.
   *
   * @param tok The token to put back onto the tokenizer.  After this,
   *        the next call to <CODE>nextToken()</CODE> should return
   *        <CODE>tok</CODE>.
   */
  public void unGet (String tok) {
    String tmp = "";

    while (this.tokenizer.hasMoreTokens ())
      tmp += this.tokenizer.nextToken ();

    tmp = tok + tmp;

    this.tokenizer = new StringTokenizer (tmp, " \t\n\r", true);
  }
}

