// (c) MIT 2003.  All rights reserved.

// -*-Mode: Java -*- 
// TinyBrowserPanel.java -- 
// Author: mark a. foltz <mfoltz@ai.mit.edu>
// Maintainer: mark a. foltz <mfoltz@ai.mit.edu>
// Copyright: Copyright (C) 2000 by mark a. foltz
// Created: <Thu Jan  6 07:49:08 2000> 
// Keywords: 

/**
 * 
 * @author mark a. foltz <mfoltz@ai.mit.edu>
 * @version $Id: TinyBrowserPanel.java,v 1.2 2003/03/06 00:40:41 moltmans Exp $
 * */

package rec.tinybrowser;

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class TinyBrowserPanel extends Panel implements MouseListener {

  private Color default_fg;
  private Font default_font;
  private int default_align;
  private Color hyperlink_color;

  private int current_align;
  private int current_indent;

  private String[] bullets = { "*", "\u00B7", "\u00BB" };

  private Content content = null;
  private Vector links = new Vector();
  private ScrollPane pane;

  private ActionListener listener = null; 

  /** The offscreen image. */
  public Image offscreen = null;
  /** The offscreen Graphics object associated with _buf. */
  public Graphics offscreenG = null;

  // the current draw position
  private int x, y;

  // the width & height of this panel
  private int _width, _height;

  // flag indicating repaint needed
  private boolean needsPaint = true;

  public TinyBrowserPanel(Color default_fg, Color default_bg, Color hyperlink,
			  Font default_font, int default_align, ScrollPane pane) {
    super();

    setBackground(default_bg);
    this.default_fg = default_fg;
    this.hyperlink_color = hyperlink;
    this.default_font = default_font;
    this.default_align = default_align;
    this.pane = pane;
    setSize(pane.getViewportSize());
    addMouseListener(this);
  }

  void display(Content content) {
    this.content = content;

    Object obj;
    links.removeAllElements();
    for (Enumeration e = content.elements(); e.hasMoreElements();) {
      obj = e.nextElement();
      if (obj instanceof Hyperlink) links.addElement(obj);
    }

    needsPaint = true;
    repaint();
  }

  private void checkOffscreenImage() {
//      System.err.println("checkOffscreenImage");
    // create the offscreen image if necessary
    if (offscreen == null || offscreenG == null) {
      offscreen = createImage(_width,_height); 
      offscreenG = offscreen.getGraphics();
      needsPaint = true;
    }
  }

  private int internalPaint(Graphics g) {

//      System.err.println("TinyBrowserPanel.internalPaint:  size="+getSize());

    g.clearRect(0,0,_width,_height);

    if (content == null) return _height;

    g.setFont(default_font);
    g.setColor(default_fg);

    current_align = default_align;
    current_indent = 0;

    Font cur_font;
    int bullet_type = -1;
    int list_level = -1;

    // 1 = unordered, 2 = ordered
    // support lists three deep
    int list_types[] = new int[3];
    int list_counts[] = new int[3];

    x = TinyBrowserPane.DEFAULT_INSETS;
    y = TinyBrowserPane.DEFAULT_INSETS;

    Enumeration e = content.elements();
    Object el;
    int value;

    while (e.hasMoreElements()) {
      el = e.nextElement();
      if (el instanceof Font) {
	g.setFont((Font) el);
      } else if (el instanceof Color) {
	g.setColor((Color) el);
      } else if (el instanceof Integer) {
	value = ((Integer) el).intValue();
	switch (value) {
	case Content.RESTORE_COLOR:
	  g.setColor(default_fg);
	  break;
	case Content.START_ITALIC:
	  cur_font = g.getFont();
	  g.setFont(new Font(cur_font.getName(), Font.ITALIC, cur_font.getSize()));
	  break;
	case Content.END_ITALIC:
	  cur_font = g.getFont();
	  g.setFont(new Font(cur_font.getName(), Font.PLAIN, cur_font.getSize()));
	  break;
	case Content.START_BOLD:
	  cur_font = g.getFont();
	  g.setFont(new Font(cur_font.getName(), Font.BOLD, cur_font.getSize()));
	  break;
	case Content.END_BOLD:
	  cur_font = g.getFont();
	  g.setFont(new Font(cur_font.getName(), Font.PLAIN, cur_font.getSize()));
	  break;
	case Content.RESTORE_FONT:
	  g.setFont(default_font);
	  break;
	case Content.BREAK:
	  x = TinyBrowserPane.DEFAULT_INSETS + current_indent;
	  y += g.getFontMetrics().getHeight();
	  break;
	case Content.PARAGRAPH:
	  if (x == TinyBrowserPane.DEFAULT_INSETS+current_indent) 
	    y += g.getFontMetrics().getHeight();
	  else {
	    x = TinyBrowserPane.DEFAULT_INSETS + current_indent;
	    y += 2*g.getFontMetrics().getHeight();
	  }
	  break;
	case Content.ALIGN_LEFT:
	  current_align = value;
	  break;
	case Content.ALIGN_RIGHT:
	  current_align = value;
	  break;
	case Content.ALIGN_CENTER:
	  current_align = value;
	  break;
	case Content.INDENT:
	  current_indent += TinyBrowserPane.INDENT_INCREMENT;
	  break;
	case Content.UNINDENT:
	  current_indent -= TinyBrowserPane.INDENT_INCREMENT;
	  break;
	case Content.START_UL:
	  if (list_level < 2) {
	    list_level++;
	    list_types[list_level] = 1;
	    current_indent += TinyBrowserPane.INDENT_INCREMENT+TinyBrowserPane.BULLET_INDENT;
	    bullet_type = (bullet_type + 1) % bullets.length;
	  }
	  break;
	case Content.END_UL:
	  current_indent -= TinyBrowserPane.INDENT_INCREMENT+TinyBrowserPane.BULLET_INDENT;
	  list_level--;
	  bullet_type = (bullet_type - 1) % bullets.length;
	  break;
	case Content.START_OL:
	  if (list_level < 2) {
	    list_level++;
	    list_types[list_level] = 2;
	    current_indent += TinyBrowserPane.INDENT_INCREMENT+TinyBrowserPane.BULLET_INDENT;
	    list_counts[list_level] = 1;
	  }
	  break;
	case Content.END_OL:
	  current_indent -= TinyBrowserPane.INDENT_INCREMENT+TinyBrowserPane.BULLET_INDENT;
	  list_level--;
	  break;
	case Content.LIST_ITEM:
	  // break from previous line
	  if (x != TinyBrowserPane.DEFAULT_INSETS+current_indent-TinyBrowserPane.BULLET_INDENT) {
	    x = TinyBrowserPane.DEFAULT_INSETS+current_indent-TinyBrowserPane.BULLET_INDENT;
	    y += g.getFontMetrics().getHeight();
	  }
	  // put bullet or number
	  if (list_types[list_level] == 1) {
	    drawString(g, Content.ALIGN_LEFT, bullets[bullet_type], false);
	  } else if (list_types[list_level] == 2) {
	    drawString(g, Content.ALIGN_LEFT, list_counts[list_level]+" ", false);
	    list_counts[list_level]++;
	  }
	  // set x and continue
	  x = TinyBrowserPane.DEFAULT_INSETS+current_indent;
	  break;
	default:
	  throw new Error("Unknown Content type: "+value);
	}
      } else if (el instanceof Hyperlink) {
	drawLink(g, current_align, (Hyperlink) el);
      } else if (el instanceof String) {
	drawString(g, current_align, (String) el, false);
      } else {
	throw new Error("Unknown object type in Content: "+el);
      }
    }

//      System.err.println("TinyBrowserPanel.internalPaint ending");

    return y;

  }
  
  public void paint(Graphics g) {

    checkOffscreenImage();

    if (!needsPaint) {
      g.drawImage(offscreen, 0, 0, this);
      return;
    }

    int layoutHeight = internalPaint(offscreenG);

    if (layoutHeight  > _height || 
	(layoutHeight  < _height-20 && layoutHeight > pane.getViewportSize().height)) {
      setSize(_width,layoutHeight+20);
    } else {
      g.drawImage(offscreen, 0, 0, this);
      needsPaint = false;
    }

  }

  private final Rectangle drawString(Graphics g, int align, String s, boolean underline) {

    FontMetrics fm = g.getFontMetrics();
    int w = _width - (2*TinyBrowserPane.DEFAULT_INSETS) - current_indent;

    int font_height = fm.getHeight();

    Rectangle bbox = new Rectangle(0, y, 0, font_height);

    String next = "";
    StringBuffer line = new StringBuffer();
    int string_width, line_width = 0;
    boolean more_tokens = true, eol = false;

    StringTokenizer st = new StringTokenizer(s," \t\n\r");
    if (st.hasMoreTokens()) {
      next = st.nextToken();
      next = next + " ";
    }
    string_width = fm.stringWidth(next);

    while (more_tokens) {

      line.setLength(0);
      line_width = 0;
      eol = false;

      while ((x+line_width <= w-string_width || 
	      (line_width == 0 && string_width > w))
	     && more_tokens && !eol) {
	line.append(next);
	//	if (next.endsWith("\n ")) eol = true;
	line_width += string_width;
	more_tokens = st.hasMoreTokens();
	if (more_tokens) {
	  next = st.nextToken();
	  next = next + " ";
	  string_width = fm.stringWidth(next);
	}
      }

      if (align == Content.ALIGN_CENTER) x = (w-line_width)/2;
      else if (align == Content.ALIGN_RIGHT) x = w-line_width;

      if (bbox.x == 0) bbox.x = x;

      // System.err.println("("+x+","+y+"), width="+line_width+": "+line);
      g.drawString(line.toString(),x,y+font_height);
      if (underline) g.drawLine(x, y+font_height+1, x+line_width, y+font_height+1);

      if (more_tokens || eol) {
	if (bbox.width == 0) bbox.width = line_width;
	x = TinyBrowserPane.DEFAULT_INSETS+current_indent;
	y += font_height;
      } else x = x + line_width;

    }

    if (bbox.width == 0) bbox.width = line_width;
    
    return bbox;

  }

  private final void drawLink(Graphics g, int align, Hyperlink link) {
    Color current_color = g.getColor();
    g.setColor(hyperlink_color);
    Rectangle bbox = drawString(g, current_align, link.getAnchor(), true);
    g.setColor(current_color);
    link.setRegion(bbox);
  }

  public void addActionListener(ActionListener l) {
    listener = AWTEventMulticaster.add(listener, l);
  }

  public void removeActionListener(ActionListener l) {
    listener = AWTEventMulticaster.remove(listener, l);
  }

  public void mouseClicked(MouseEvent e) {
//      System.err.println("TBPanel got "+e);
    if (listener != null && 
	e.getClickCount() == 1 && 
	((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
      Hyperlink link;
      for (int i = 0; i < links.size(); i++) {
	link = (Hyperlink) links.elementAt(i);
//  	System.err.println("TBPanel checking "+link);
	if (link.contains(e.getX(), e.getY())) {
	  listener.actionPerformed(new ActionEvent(link.getReference(),
						   ActionEvent.ACTION_PERFORMED,
						   link.getCommand()));
	}
      }
    }
  }

  public void mousePressed(MouseEvent e) { }

  public void mouseReleased(MouseEvent e) { }

  public void mouseEntered(MouseEvent e) { }

  public void mouseExited(MouseEvent e) { }

  public Dimension getPreferredSize() { return new Dimension(_width, _height); }

  public void setSize(Dimension d) {
    super.setSize(d);
    internalSetSize(d.width,d.height);
  }
  
  public void setSize(int w, int h) {
    super.setSize(w, h);
    internalSetSize(w,h);
  }

  public void setBounds(int x, int y, int width, int height) {
    super.setBounds(x,y,width,height);
    internalSetSize(width, height);
  }
    
  public void setBounds(Rectangle r) {
    super.setBounds(r);
    internalSetSize(r.width, r.height);
  }

  private void internalSetSize(int w, int h) {

    _width = w;
    _height = h;

    if (offscreen != null && 
	offscreen.getWidth(this) == _width && 
	offscreen.getHeight(this) == _height) return;

//      System.err.println("TinyBrowserPanel.internalSetSize("+w+","+h+")");

    if (offscreenG != null) offscreenG.dispose();

    offscreen = null;
    offscreenG = null;
    
    needsPaint = true;

    pane.doLayout();

  }

}
    

