import java.awt.*;
import java.io.*;
import java.applet.*;
import java.util.*;
import java.net.*;

public class StaffRoll extends Applet implements Runnable {
	static final int MIN_FSIZE = 6;

	// relief style
	static final int NONE = 0;
	static final int PLAIN = 1;
	static final int SUNKEN = 2;
	static final int RAISED = 3;

	int max = 100;
	Vector texts;
	int margin = 20;
	int speed = 1;
	int interval = 20;
	Color color = Color.white;
	Color bgColor = Color.black;
	Color borderColor = Color.black;
	Color shadow;
	Color link = Color.magenta;
	Color alink = Color.red;
	int relief = NONE;
	int align = RollText.WRAP;
	int border = 1;
	Image bgImage;
	Font font;
	String fname = "SansSerif";
	int fsize = 12;
	int fstyle = Font.PLAIN;
	Image offs;
	Graphics offg;
	Thread thread;
	Dimension size;
	int height;
	boolean clicked;
	int viewx;
	int viewy;
	int mx = -1;
	int my = -1;
	AppletContext context;
	MediaTracker mt;
	BufferedReader reader;
	boolean pause;
	String target;

	public void init() {
		System.out.println(this);
		size = size();
		texts = new Vector();
		context = getAppletContext();
		font = new Font(fname, fstyle, fsize);
		offs = createImage(size.width, size.height);
		offg = offs.getGraphics();

		String s = getParameter("max");
		if (s != null) {
			max = Integer.parseInt(s);
			if (max < 1) max = 1;
		}
		s = getParameter("fname");
		if (s != null) {
			fname = s;
		}
		s = getParameter("fsize");
		if (s != null) {
			fsize = Integer.parseInt(s);
			if (fsize < MIN_FSIZE) fsize = MIN_FSIZE;
		}
		s = getParameter("fstyle");
		if (s != null) {
			fstyle = getFontStyle(s);
		}
		font = new Font(fname, fstyle, fsize);
		s = getParameter("color");
		if (s != null) {
			color = new Color(Integer.parseInt(s, 16));
		}
		s = getParameter("shadow");
		if (s != null) {
			shadow = new Color(Integer.parseInt(s, 16));
		}
		s = getParameter("bgColor");
		if (s != null) {
			bgColor = new Color(Integer.parseInt(s, 16));
		}
		s = getParameter("borderColor");
		if (s != null) {
			borderColor = new Color(Integer.parseInt(s, 16));
		} else {
			borderColor = bgColor;
		}
		s = getParameter("margin");
		if (s != null) {
			margin = Integer.parseInt(s);
		}
		if (margin * 2 >= size.width) {
			margin = size.width / 2 - 1;
		}
		s = getParameter("border");
		if (s != null) {
			border = Integer.parseInt(s);
			if (border < 0) {
				border = 0;
			}
		}
		s = getParameter("relief");
		if (s != null) {
			s = s.trim().toLowerCase();
			if (s.equals("sunken")) {
				relief = SUNKEN;
			} else if (s.equals("raised")) {
				relief = RAISED;
			} else if (s.equals("plain")) {
				relief = PLAIN;
			}
		}
		s = getParameter("link");
		if (s != null) {
			link = new Color(Integer.parseInt(s, 16));
		}
		s = getParameter("alink");
		if (s != null) {
			alink = new Color(Integer.parseInt(s, 16));
		}
		s = getParameter("align");
		if (s != null) {
			align = getTextAlign(s);
		}
		s = getParameter("speed");
		if (s != null) {
			speed = Integer.parseInt(s);
			if (speed < 1) speed = 1;
		}
		s = getParameter("interval");
		if (s != null) {
			interval = Integer.parseInt(s);
			if (interval < 20) interval = 20;
		}
		s = getParameter("file");
		if (s != null) {
			readFile(s);
		} else {
			readParam();
		}
		s = getParameter("bgImage");
		if (s != null) {
			bgImage = getImage(getDocumentBase(), s);
			mt = new MediaTracker(this);
			mt.addImage(bgImage, 0);
		}
		s = getParameter("target");
		if (s != null) {
			target = s;
		}
	}

	String changeTabToSpaces(String s) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			if (c == '\t') {
				sb.append("    ");
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}

	void readFile(String file) {
		String line;
		Font textFont = new Font(fname, fstyle, fsize);
		try {
			URL url = new URL(getDocumentBase(), file);
			reader = new BufferedReader(new InputStreamReader(url.openStream(), "JISAutoDetect"));
			while ((line = reader.readLine()) != null) {
				line = changeTabToSpaces(line);
				RollText rt = new RollText(line, textFont, height, align, this);
				rt.setColor(color);
				rt.setShadowColor(shadow);
				height += rt.getHeight();
				texts.addElement(rt);
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	void readParam() {
		for (int i = 1; i < max; i++) {
			String text = getParameter("text" + i);
			if (text == null) {
				continue;
			}
			while (text != null) {
				String token;
				int n;
				if ((n = text.indexOf('|')) >= 0) {
					token = text.substring(0, n);
					text = text.substring(n + 1);
				} else {
					token = text;
					text = null;
				}
				Font textFont = font;
				String s1 = getParameter("fname" + i);
				String s2 = getParameter("fsize" + i);
				String s3 = getParameter("fstyle" + i);
				if (s1 != null || s2 != null || s3 != null) {
					String fontName = fname;
					int fontSize = fsize;
					int fontStyle = fstyle;
					if (s1 != null) {
						fontName = s1;
					}
					if (s2 != null) {
						fontSize = Integer.parseInt(s2);
						if (fontSize < MIN_FSIZE) fontSize = MIN_FSIZE;
					}
					if (s3 != null) {
						fontStyle = getFontStyle(s3);
					}
					textFont = new Font(fontName, fontStyle, fontSize);
				}
				int textAlign = align;
				String s = getParameter("align" + i);
				if (s != null) {
					textAlign = getTextAlign(s);
				}
				RollText rt = new RollText(token, textFont, height, textAlign, this);
				s = getParameter("color" + i);
				if (s != null) {
					rt.setColor(new Color(Integer.parseInt(s, 16)));
				} else {
					rt.setColor(color);
				}
				s = getParameter("shadow" + i);
				if (s != null) {
					rt.setShadowColor(new Color(Integer.parseInt(s, 16)));
				} else if (shadow != null) {
					rt.setShadowColor(shadow);
				}
				s = getParameter("link" + i);
				if (s != null) {
					rt.setLinkColor(new Color(Integer.parseInt(s, 16)));
				} else {
					rt.setLinkColor(link);
				}
				s = getParameter("alink" + i);
				if (s != null) {
					rt.setVLinkColor(new Color(Integer.parseInt(s, 16)));
				} else {
					rt.setVLinkColor(alink);
				}
				s = getParameter("url" + i);
				if (s != null) {
					URL url = null;
					try {
						url = new URL(s);
					} catch (MalformedURLException e) {
						try {
							url = new URL(getDocumentBase(), s);
						} catch (MalformedURLException mue) {
							mue.printStackTrace();
						}
					}
					if (url != null) {
						rt.setURL(url);
					}
				}
				height += rt.getHeight();
				texts.addElement(rt);
			}
		}
	}

	int getFontStyle(String s) {
		int style = Font.PLAIN;
		s = s.toLowerCase().trim();
		if (s.equals("p")) {
			style = Font.PLAIN;
		} else if (s.equals("i")) {
			style = Font.ITALIC;
		} else if (s.equals("b")) {
			style = Font.BOLD;
		} else if (s.equals("ib") || s.equals("bi")) {
			style = Font.ITALIC + Font.BOLD;
		}
		return style;
	}

	int getTextAlign(String s) {
		int align = RollText.WRAP;
		s = s.toLowerCase().trim();
		if (s.equals("center")) {
			align = RollText.CENTER;
		} else if (s.equals("right")) {
			align = RollText.RIGHT;
		} else if (s.equals("left")) {
			align = RollText.LEFT;
		} else if (s.equals("wrap")) {
			align = RollText.WRAP;
		}
		return align;
	}

	public void start() {
		if (thread == null) {
			thread = new Thread(this);
			thread.start();
		}
	}

	public void stop() {
		if (thread != null) {
			thread.stop();
			thread = null;
		}
	}

	boolean isClicked() {
		return clicked;
	}

	public int getWidth() {
		return size.width;
	}

	public int getHeight() {
		return size.height;
	}

	int getMargin() {
		return margin;
	}

	public synchronized void paint(Graphics g) {
		g.drawImage(offs, 0, 0, null);
	}

	public void update(Graphics g) {
		paint(g);
	}

	void drawMessage(String msg) {
		offg.setColor(Color.black);
		offg.fillRect(0, 0, size.width, size.height);
		offg.setColor(Color.cyan);
		offg.drawString(msg, 10, 20);
		repaint();
	}

	public void pause() {
		pause = true;
	}

	public void run() {
		if (bgImage != null) {
			drawMessage("Loading image...");
			while (!mt.checkAll(true)) {
				try {
					thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (mt.isErrorAny()) {
				drawMessage("Error: Loading image...");
				return;
			}
		}
		viewx = 0;
		viewy = -size.height;
		for (;;) {
			pause = false;
			synchronized(this) {
				offg.setColor(bgColor);
				offg.fillRect(0, 0, size.width, size.height);
				if (bgImage != null) {
					offg.drawImage(bgImage, 0, 0, size.width, size.height, null);
				}
				for (Enumeration e = texts.elements(); e.hasMoreElements(); ) {
					RollText text = (RollText)e.nextElement();
					text.draw(offg, viewx, viewy, mx, my);
				}
				if (relief != NONE) {
					offg.setColor(borderColor);
					for (int i = 0; i < border; i++) {
						switch (relief) {
						case PLAIN:
							offg.drawRect(i, i, size.width - 1 - 2 * i, size.height - 1 - 2 * i);
							break;
						case SUNKEN:
							offg.draw3DRect(i, i, size.width - 1 - 2 * i, size.height - 1 - 2 * i, false);
							break;
						case RAISED:
							offg.draw3DRect(i, i, size.width - 1 - 2 * i, size.height - 1 - 2 * i, true);
							break;
						}
					}
				}
			}
			repaint();
			try {
				thread.sleep(interval);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (!pause) {
				viewy += speed;
				if (viewy > height) {
					viewy = -size.height;
				}
				setCursor(Cursor.getDefaultCursor());
				repaint();
			} else {
				setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
				repaint();
			}
		}
	}

	public boolean mouseEnter(Event evt, int x, int y) {
		return true;
	}

	public boolean mouseExit(Event evt, int x, int y) {
		mx = -1;
		my = -1;
		clicked = false;
		return true;
	}

	public boolean mouseMove(Event evt, int x, int y) {
		mx = x;
		my = y;
		return true;
	}

	public boolean mouseDrag(Event evt, int x, int y) {
		mx = x;
		my = y;
		return true;
	}

	public boolean mouseUp(Event evt, int x, int y) {
		clicked = false;
		for (Enumeration e = texts.elements(); e.hasMoreElements(); ) {
			RollText text = (RollText)e.nextElement();
			text.checkClicked(viewx, viewy, x, y);
		}
		return true;
	}

	public boolean mouseDown(Event evt, int x, int y) {
		clicked = true;
		return true;
	}

	void showDocument(URL url) {
		if (target == null) {
			context.showDocument(url);
		} else {
			context.showDocument(url, target);
		}
	}

	public String toString() {
		return "StaffRoll (C) Nakagawa Masami 1999";
	}
}

class RollText {
	public static final int CENTER = 0;
	public static final int RIGHT = 1;
	public static final int LEFT = 2;
	public static final int WRAP = 3;

	private int locy;
	private int locx;
	private int align;
	private String text;
	private Font font;
	private FontMetrics metrics;
	private Vector wraptext;
	private StaffRoll parent;
	private int height;
	private int width;
	private Color col = Color.white;
	private Color shadow;
	private Color link = Color.magenta;
	private Color alink = Color.red;
	private URL url;

	RollText(String text, Font font, int locy, int align, StaffRoll parent) {
		this.text = text;
		this.font = font;
		this.locy = locy;
		this.align = align;
		this.parent = parent;
		metrics = parent.getFontMetrics(font);
		height = metrics.getHeight();
		width = metrics.stringWidth(text);
		switch (align) {
		case CENTER:
			locx = (parent.getWidth() - width) / 2;
			break;
		case RIGHT:
			locx = parent.getWidth() - parent.getMargin() - width;
			break;
		case LEFT:
			locx = parent.getMargin();
			break;
		case WRAP:
			locx = parent.getMargin();
			wraptext = getWrapText(text);
			height *= wraptext.size();
			break;
		}
	}

	private Vector getWrapText(String text) {
		Vector vec = new Vector();
		int lim = parent.getWidth() - 2 * parent.getMargin();
		if (metrics.stringWidth(text) <= lim || text.length() <= 1) {
			vec.addElement(text);
		} else {
			char[] buf = text.toCharArray();
			int wid, off, len;
			for (wid = 0, off = 0, len = 1; off + len <= buf.length; len++) {
				if (metrics.charsWidth(buf, off, len) > lim) {
					if (len > 1) len--;
					vec.addElement(new String(buf, off, len));
					off += len;
					len = 1;
				}
			}
			if (--len > 0) {
				vec.addElement(new String(buf, off, len));
			}
		}
		return vec;
	}

	int getHeight() {
		return height;
	}

	void setColor(Color col) {
		this.col = col;
	}

	void setShadowColor(Color col) {
		shadow = col;
	}

	void setLinkColor(Color col) {
		link = col;
	}

	void setVLinkColor(Color col) {
		alink = col;
	}

	void setURL(URL url) {
		this.url = url;
	}

	boolean inside(int vx, int vy, int mx, int my) {
		int x1 = locx - vx;
		int y1 = locy - vy;
		int x2 = x1 + width;
		int y2 = y1 + height;
		if (mx > x1 && mx < x2 && my > y1 && my < y2) {
			return true;
		}
		return false;
	}

	void checkClicked(int vx, int vy, int mx, int my) {
		if (url != null) {
			if (inside(vx, vy, mx, my)) {
				parent.showDocument(url);
			}
		}
	}

	void draw(Graphics g, int vx, int vy, int mx, int my) {
		Color col = this.col;
		if (url != null && mx >= 0 && my >= 0) {
			if (inside(vx, vy, mx, my)) {
				if (parent.isClicked()) {
					col = alink;
				} else {
					col = link;
				}
				parent.pause();
			}
		}
		if (align == WRAP) {
			for (Enumeration e = wraptext.elements(); e.hasMoreElements(); ) {
				drawLine(g, (String)e.nextElement(), vx, vy, mx, my, col);
				vy -= metrics.getHeight();
			}
		} else {
			drawLine(g, text, vx, vy, mx, my, col);
		}
	}

	private void drawLine(Graphics g, String line, int vx, int vy, int mx, int my, Color col) {
		if ((locy < vy + parent.getHeight()) && (locy + metrics.getHeight() > vy)) {
			int x = locx - vx;
			int y = locy - vy + metrics.getHeight();
			g.setFont(font);
			if (shadow != null) {
				g.setColor(shadow);
				g.drawString(line, x + 1, y + 1);
			}
			g.setColor(col);
			g.drawString(line, x, y);
		}
	}
}

