/*
 * GNU.FREE 2002
 *
 * Copyright (c) 1999, 2000, 2001, 2002
 * The Free Software Foundation (www.fsf.org)
 *
 * GNU.FREE Co-ordinator: Jason Kitcat <jeep@free-project.org>
 *
 * GNU site: http://www.gnu.org/software/free/
 * 
 * FREE e-democracy site: http://www.free-project.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program (COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
 
 // statement below kept out of good form but this code was taken from the GPLed NFC Chat system.
 
/**
 * Copyright (c) 2000 Lyrisoft Solutions, Inc.
 * Used by permission
 */
package Free.awt;

import java.awt.*;
import java.util.Vector;
import java.util.Enumeration;

/**
 * TextRun.
 * A single run of text that has a string and a TextStyle.
 * Runs know how to draw themselves with word-wrapping.<br>
 * <br>
 * Originally from NFC Chat. Modified for use in GNU.FREE<br>
 * <br>
 *
 * @version 1.0 2 April 2001
 * @author Jason Kitcat
 * @since 1.6
 */
public class TextRun {
    private String _text;
    private TextStyle _style;
    private char[] _chars;

    private FontMetrics _fm;
    private int _fmMaxDescent;
    private int _fmMaxAscent;
    private int _fmLeading;
    private int _fmHeight;
    private int[] _fmWidths;

    private Vector _substrings;
    private boolean _newLineFixed;

    private Rectangle _boundingRect = new Rectangle(0, 0, 0, 0);

    public TextRun(String text, TextStyle style) {
        _text = text;
        _style = style;

        _fm = Toolkit.getDefaultToolkit().getFontMetrics(style.getFont());
        _fmMaxDescent = _fm.getMaxDescent();
        _fmMaxAscent = _fm.getMaxAscent();
        _fmLeading = _fm.getLeading();
        _fmHeight = _fm.getHeight();
        _fmWidths = _fm.getWidths();
        _chars = _text.toCharArray();

        _substrings = new Vector();
    }

    public TextStyle getStyle() {
        return _style;
    }

    public String getText() {
        return _text;
    }

    public void setFont(Font f) {
        _style.setFont(f);
    }

    /**
     * Return the smallest Rectangle that contains this whole TextRun.
     * Prepare must be called before this will return anything accurate.
     */
    public Rectangle getBoundingRect() {
        return _boundingRect;
    }

    /**
     * Find the index of last last character of the substring that can be drawn before
     * having to wrap.
     *
     * @param begin the offset of the first character
     * @param xOff the X position that the substring starts at
     * @param xMax the wrapping point
     * @return the index of the last character that can be drawn before wrapping
     */
    int findEnd(int begin, int xOff, int xMax) {
        int end = begin;
        int width = xOff;
        while (end != _chars.length) {
            if (width + _fmWidths[_chars[end]] <= xMax &&
                _chars[end] != '\n')
            {
                width += _fmWidths[_chars[end]];
                end++;
            } else {
                break;
            }
        }

        if (end == _chars.length) {
            return end;
        }

        if (_chars[end] == '\n') {
            _newLineFixed = true;
            return end;
        }

        // back up to the last space
        int oldEnd = end;
        while (end > begin) {
            if (_chars[end-1] == ' ') {
                return end; // here it is
            } else {
                end--;
            }
        }
        return xOff == 0 ? oldEnd : end;
    }

    /**
     * Prepares this run of text for word-wrapped stylized display,
     * breaking it down into discrete Substrings that can be drawn
     * easily.
     *
     * @param xMax the wrapping point (width of the view)
     * @param preferredSize is filled in based xMax
     * @param the Point contains the x, y offset that this TextRun starts at.
     *                  The values in p get replaced with the Point that the
     *                  next TextRun should begin at.
     */
    public void prepare(int xMax, Dimension preferredSize, Point p) {
        _substrings.removeAllElements();

        _boundingRect.x = 0;
        _boundingRect.y = p.y;

        int x = p.x;
        int y = p.y;
        if (xMax == 0) {
            xMax = 600; 
        }

        int yOff = y + _fmMaxAscent;
        int xOff = x;

        int begin = 0;
        int len = _chars.length;
        int end = 0;

        int width = 0;
        do {
            if (_chars[begin] == '\n') {
                begin++;
                if (!_newLineFixed) {
                    yOff += _fmHeight;
                    xOff = 0;
                } else {
                    _newLineFixed = false;
                }
                if (begin == _chars.length-1) {
                    break;
                }
            }
            end = findEnd(begin, xOff, xMax);
            width = xOff + _fm.charsWidth(_chars, begin, end-begin);

            _substrings.addElement(new Substring(begin, end-begin, xOff, yOff));

            xOff = 0;
            if (end != len) {
                yOff += _fmHeight;
                begin = end;
            }
        } while (end != len);

        preferredSize.width = xMax;
        preferredSize.height = yOff - y + _fmMaxDescent;
        p.x = width;
        p.y = yOff - _fmMaxAscent;

        _boundingRect.width = preferredSize.width;
        _boundingRect.height = preferredSize.height;
    }

    /**
     * Check if this TextRun contains the specified point.  This class assumes
     * that the y coordinate has already been translated
     */
    public boolean contains(int x, int y, int yTranslation) {
        if (_boundingRect.inside(x, y+yTranslation)) {
            Substring target = null;
            for (Enumeration e = _substrings.elements(); e.hasMoreElements(); ) {
                Substring sub = (Substring)e.nextElement();
                if (x >= sub.xOff && y+yTranslation >= sub.yOff - _fmHeight) {
                    target = sub;
                }
            }
            
            if (target != null &&
                x < (target.xOff + _fm.stringWidth(new String(_chars, target.begin, target.length))))
            {
                return true;
            }

        }
        return false;
    }


    /**
     * Draw this run of text on the screen, starting at the y Offset specified.
     * Prepare must have been called first, or else nothing will draw
     *
     * @param g the graphics context to draw on
     */
    public void draw(Graphics g, int yOff) {
        g.setColor(_style.getColor());
        g.setFont(_style.getFont());

        for (Enumeration e = _substrings.elements(); e.hasMoreElements(); ) {
            Substring substring = (Substring)e.nextElement();
            g.drawChars(_chars, substring.begin, substring.length, substring.xOff, substring.yOff + yOff);
        }
    }

    public String toString() {
        return "TextRun[\"" + _text + "\"]; Bounds = " + _boundingRect;
    }
}

/**
 * Encapsulates a substring that is ready to be drawn.
 * Has an x and y screen offset (xOff and yOff), an offset
 * into the chars array (begin) and length.
 */
class Substring {
    public int begin;
    public int length;
    public int xOff;
    public int yOff;

    /**
     * @param begin starting index into the chars array
     * @param length length of the substring
     * @param xOff xOffset to start drawing
     * @param yOff yOffset to start drawing
     */
    public Substring(int begin, int length, int xOff, int yOff) {
        this.begin = begin;
        this.length = length;
        this.xOff = xOff;
        this.yOff = yOff;
    }
}
