/* dIntProg Browser. A webbrowser written in Java.
 * Copyright (C) 2001 Martin Geisler <gimpster@gimpster.com>
 *  
 * 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; if not, write to the
 * 
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import javax.swing.*;
import java.text.*;
import java.util.*;
import java.net.*;

/** A fragment of text. This is the basic building-block of all
 *  paragraphs. A <code>TextFragment</code> is meant to be used
 *  together with other fragments to form larger pieces of text. Each
 *  fragment has it's own content, font, and may be a link. */
public class TextFragment extends AbstractRigidBox {

    private String content;
    private Graphics pen;
    private Font font;
    private URL url;
    /* A very small font - used when switching to the real font. */
    private Font switch_font = new Font(null, Font.PLAIN, 4);

    /** Creates a new <code>TextFragment</code>.
     *  @param str the string contained within this fragment. The
     *  string should contain any white-space necessary to ensure
     *  correct spacing when this fragment is placed side-to-side with
     *  other fragments.
     *  @param f the font that will be used when rendering the
     *  fragment. The font is also used when the content is meassured.
     *  @param u the URL that this fragment links to. If the fragment
     *  doesn't link to anywhere <code>u</code> should be
     *  <code>null</code>. */
    public TextFragment(String str, Font f, URL u) {
        content = str;
        font = f;
        url = u;
    }
    
    /** Creates a new <code>TextFragment</code>. This is used when a
     *  fragment is split into two. The new fragments will have to be
     *  created by this constructor, as this is the only way to give
     *  the new fragments the needed graphics context.
     *  @param str the string contained within this fragment. The
     *  string should contain any white-space necessary to ensure
     *  correct spacing when this fragment is placed side-to-side with
     *  other fragments.
     *  @param f the font that will be used when rendering the
     *  fragment. The font is also used when the content is meassured.
     *  @param u the URL that this fragment links to. If the fragment
     *  doesn't link to anywhere <code>u</code> should be
     *  <code>null</code>. */
    private TextFragment(String str, Font f, URL u, Graphics g) {
        this(str, f, u);
        doLayout(g, null, 0);
    }
    
    public void doLayout(Graphics g, JComponent c, int w) {
        if (width == -1) {
            /* Argh! Java has a strange and stupid bug that means that
             * font changes doesn't apply until you change the /size/
             * of the font /and/ draw with it. */
            g.setFont(switch_font);
            g.drawString("", 0, 0);
            /* We can now use the real font. */
            g.setFont(font);
            width = g.getFontMetrics().stringWidth(content);
            height = g.getFontMetrics().getHeight();
            pen = g;
            Browser.debug("TextFragment.doLayout: " + this);
        }
    }

    /** Returns the width of it's argument.
     *  @param s the string to be meassured. */
    private int stringWidth(String s) {
        pen.setFont(font);
        return pen.getFontMetrics().stringWidth(s);
    }

    /** Returns the minimum width of this <code>TextFragment</code>.
     *  The result is calculated by meassuring every word in the
     *  string and is only calculated once for each fragment.
     *  @return the minimum width.*/
    public int getMinimumWidth() {
        /* We calculate the minimum width, if necessary */
        if (min_width == -1) {
            StringTokenizer st = new StringTokenizer(content, " ");
            while (st.hasMoreTokens()) {
                String s = st.nextToken();
                int w = stringWidth(s);
                if (w > min_width) {
                    min_width = w;
                }
            }
        }
        return min_width;
    }

    /** Reports if a split is possible at <code>w</code>. For a
     *  fragment to be split, it must contain more than a single word.
     *  If it does, the width of the initial word (plus any white
     *  space at the beginning) is meassured and compared with
     *  <code>w</code>.
     *  @param w the desired width of the head.
     *  @return <code>true</code> if <code>w</code> is greather than
     *  the length of the initial word. */
    public boolean splitIsPossible(int w) {
        int offset = content.indexOf(' ');
        if (offset == 0) {
            /* The string starts with a space. We look the for
             * next space. */
            offset = content.indexOf(' ', 1);
        }
                
        if (offset == -1 || offset == content.length()-1) {
            /* There is only a single word in the string, which
             * might end with a space. */
            return false;
        } else {
            /* We return the length of the first word (plus the
             * initial white space, if any) */
            String substr = content.substring(0, offset);
            return (w > stringWidth(substr));
        }
    }


    public void drawAt(int x, int y, DocumentView v) {
        if (url != null) {
            v.drawString(x, y, height, content, font,
                         new HyperLink(x, y, width, height, url));
        } else {
            v.drawString(x, y, height, content, font, null);
        }
        if (Browser.debugging) {
            v.drawRect(x, y, width, height, Color.orange);
        }
    }

    public Box splitHead(int w) {

        StringTokenizer st = new StringTokenizer(content, " ", true);
        String result = "";

        String s = st.nextToken();
        int x_offset = stringWidth(s);

        while (st.hasMoreTokens() && x_offset < w) {
            result = result + s;
            s = st.nextToken();
            x_offset = x_offset + stringWidth(s);
        }
        return new TextFragment(result, font, url, pen);
    }

    public Box splitTail(int w) {
        
        StringTokenizer st = new StringTokenizer(content, " ", true);
        
        String result = "";
        
        String s = st.nextToken();
        int x_offset = stringWidth(s);

        while (st.hasMoreTokens() && x_offset < w) {
            result = result + s;
            s = st.nextToken();
            x_offset = x_offset + stringWidth(s);
        }

        result = content.substring(result.length());
        /* The tail shouldn't start with a space. */
        if (result.charAt(0) == ' ') {
            result = result.substring(1);
        }
        return new TextFragment(result, font, url, pen);
    }

    public Box getSmallestHead() {
        
        int offset = content.indexOf(' ');
        if (offset == -1 || offset == content.length()-1) {
            /* There is only a single word in the string. */
            return null;
        } else {
            return new TextFragment(content.substring(0, offset),
                                    font, url, pen);
        }
    }

    public Box getLargestTail() {
        int offset = content.indexOf(' ');
        if (offset == -1 || offset == content.length()-1) {
            /* There is only a single word in the string. */
            return null;
        } else {
            return new TextFragment(content.substring(offset + 1),
                                    font, url, pen);
        }

    }
    
    
    public void trim(int edge) {
        Browser.debug("Trimming: " + this);
        if (content.length() > 0) {
            if (edge == RigidBox.LEFT &&
		content.charAt(0) == ' ') {
                content = content.substring(0);
	    } else if (edge == RigidBox.RIGHT &&
		       content.charAt(content.length()-1) == ' ') {
                content = content.substring(0, content.length()-1);
            } else if (edge == RigidBox.BOTH) {
                content = content.trim();
            }
            width = stringWidth(content);
        }
    }
    
    public String toString() {
        if (content.length() > 16) {
            return "'" + content.substring(0, 13) +
                "...': " + width + "x" + height;
        } else {
            return "'" + content + "': " +  width + "x" + height;
        }
    }

}