/**
 *
 * @author Copyright (c) 2000 by BEA Systems, Inc. All Rights Reserved.
 */
package examples.applets.ping;


import java.awt.*;
import java.io.*;
import java.applet.*;
import java.util.*;

public class Statistics extends Canvas implements Runnable {

    void p(String s) {
	System.out.println("stats: " + s);
    }

    /* circular buffer of stat entries */
    float[] data;
    /* index of oldest entry */
    int ind = 0;
    float ymax;
    /* # data elements */
    int datalen;
    int nTicks;
    private String xlabel, ylabel;
    private int xlx, xly, ylx, yly;
    boolean enabled = false;
    Font font, tiny;
    FontMetrics fm, fmt;
    char[][] xls, yls, ticks;
    int XOFF, YOFF, AXW, TOPOFF, TICKOFF;
    
    public Statistics(int datalen, String xlabel, String ylabel,
		      float ymax, int ticks) {
	this.datalen = datalen;
	data = new float[datalen];
	this.xlabel = xlabel;
	this.ylabel = ylabel;
	this.ymax = ymax;
	nTicks = ticks;
	font = new Font("TimesRoman", Font.PLAIN, 12);
	tiny = new Font("TimesRoman", Font.PLAIN, 9);
	fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
	fmt = Toolkit.getDefaultToolkit().getFontMetrics(tiny);
	TOPOFF = fmt.getHeight() << 1;
    }
    public synchronized void resize(Dimension d) {
	super.resize(d);
	calculateOffsets(d);
	Graphics g = getGraphics();
	//	g.clearRect(0, 0, d.width, d.height);
	paint(g);
	g.dispose();
	//p("resized to dimension: " + d);
    }
    public synchronized void setSize(Dimension d) {
    	resize(d);
    }
    protected void bestXoff(Dimension sz) {
	char[] xarr = xlabel.toCharArray();
	int targetXoff = sz.width / 5;
	XOFF = fm.charsWidth(xarr, 0, xarr.length) + fm.charWidth('A') << 1;
	if (XOFF <= targetXoff) {
	    // easy - one string
	    xls = new char[1][];
	    xls[0] = xarr;
	    xlx = 5;
	    xly = (sz.height + fm.getMaxAscent())/2;
	    return;
	}
	// look for splits
	int nsplits = Math.max(XOFF/targetXoff, 3);
	xls = new char[nsplits][];
	int splitSz = xarr.length/nsplits;
	int i = 0, sind, tmp;
	XOFF = 0;
	//	p("nsplits="+nsplits+", splitSz=" + splitSz+", width('A')="+fm.charWidth('A'));
outerloop:
	for (sind = 0; sind < nsplits;  sind++) {
	    while (i < xarr.length && xarr[i] == ' ') {
		i++;
	    }
	    if (i >= xarr.length) {
		break;
	    } else if ((i + splitSz) >= xarr.length) {
		if ((tmp = fm.charsWidth(xarr, i, xarr.length - i)) > XOFF) {
		    XOFF = tmp;
		}
		xls[sind] = new char[xarr.length - i];
		System.arraycopy(xarr, i, xls[sind], 0, xls[sind].length);
		break;
	    }
	    int j;
	    /* look backwards, up to 2 chars */
	    for (j = i + splitSz; j >= i && j > i + splitSz - 2; j--) {
		if (xarr[j] == '/' || xarr[j] == ' ') {
		    if (xarr[j] == '/') { j++;}
		    if ((tmp = fm.charsWidth(xarr, i, j - i)) > XOFF) {
			XOFF = tmp;
		    }
		    xls[sind] = new char[j-i];
		    System.arraycopy(xarr, i, xls[sind], 0, xls[sind].length);
		    i = j;
		    continue outerloop;
		}
	    }
	    /* nothing backwards, look forwards, until split */
	    for (j = i + splitSz; j < xarr.length; j++) {
		if (xarr[j] == '/' || xarr[j] == ' ') {
		    break;
		}
	    }
	    if (j < xarr.length && xarr[j] == '/') {
		j++;
	    }
	    if ((tmp = fm.charsWidth(xarr, i, j - i)) > XOFF) {
		XOFF = tmp;
	    }
	    xls[sind] = new char[j-i];
	    System.arraycopy(xarr, i, xls[sind], 0, xls[sind].length);
	    i = j;
	}

	xlx = 5;
	XOFF += fm.charWidth('A') << 1; // space on either side
	xly = sz.height/2 - ((sind+1) * fm.getHeight())/2;
	//	p("got xlabels:");
	//	for (int k = 0; k < xls.length && xls[k] != null; k++) {
	//	    System.out.println("\t\""+xls[k]+"\"");
	//	}
	/* calculate space for ticks */
	int ch = fmt.getHeight();
	if (2*nTicks*ch > size().height - YOFF - TOPOFF - AXW) {
	    nTicks = (size().height - YOFF - TOPOFF - AXW)/(ch*2);
	}
	TICKOFF = 0;
	ticks = new char[nTicks+1][];
	for (int k = nTicks-1 ; k >= 0; k--) {
	    float f = (float)k/(float)nTicks;
	    ticks[k] = Float.toString(ymax * f ).toCharArray();
	    int mx = fmt.charsWidth(ticks[k], 0, ticks[k].length);
	    if (mx > TICKOFF) {
		TICKOFF = mx;
	    }
	}
	ticks[nTicks] = Float.toString(ymax).toCharArray();
	int mx = fmt.charsWidth(ticks[nTicks], 0, ticks[nTicks].length);
	if (mx > TICKOFF) {
	    TICKOFF = mx;
	}
	TICKOFF += 2*AXW;
	TICKOFF += fmt.charWidth('A');
	XOFF += fmt.charWidth('A') << 1;
	XOFF += TICKOFF;
    }

    protected void bestYoff(Dimension sz) {
	int targetYoff = sz.height / 5;
	char[] yarr = ylabel.toCharArray();
	YOFF = fm.getHeight() * 3;
	yly = sz.height - YOFF + fm.getHeight() + 5;
	yls = new char[1][];
	yls[0] = yarr;
    }

    protected void calculateOffsets(Dimension sz) {
	setFont(font);
	bestYoff(sz);
	bestXoff(sz);
	int graphWidth = sz.width - XOFF;
	ylx = XOFF + (graphWidth - fm.charsWidth(yls[0], 0, yls[0].length))/2;

	AXW = Math.min(sz.width / 100, 3);
	AXW = Math.max(AXW, 3);
    }

    void dumpSizes() {
	/*
	p("XOFF="+XOFF+", YOFF="+YOFF+", xlx="+xlx+
	  "\nxly="+xly+", ylx="+ylx+", yly="+yly+
	  "\nheight="+size().height+", width="+size().width+
	  " TOPOFF="+TOPOFF+", AXW="+AXW);
	  */
    }

    public synchronized void quit() {
	enabled = false;
	notify();
    }

    public synchronized void run() {
	calculateOffsets(size());
	//	p("run: dump sizes:");
	dumpSizes();
	enabled = true;
	while (enabled) {
	    try {
		wait();
	    } catch (InterruptedException e) {
		e.printStackTrace();
		return;
	    }
	    update();
	}
	//	p("updater thread exiting..");
    }

    public synchronized void addData(float p) {
	data[ind] = (p < ymax ? p: ymax);
	if (!enabled) {
	    ind++;
	    ind %= 10;
	} else {
	    notify();
	}
    }

    protected synchronized void update() {
	Graphics g = getGraphics();
	clipAndXlate(g);
	//	paintData(g);
	updateData(g);
	g.dispose();
	++ind;
	ind %= 10;
    }

    public synchronized void paint(Graphics g) {
	paintAxes(g);
	clipAndXlate(g);
	paintData(g);
    }

    private void clipAndXlate(Graphics g) {
	int w = size().width - XOFF;
	w = w - (w % datalen); // want w a multiple of datalen
	g.clipRect(XOFF + AXW, TOPOFF, w , size().height - TOPOFF - YOFF);
	g.translate(XOFF + AXW, TOPOFF);
    }

    protected void paintAxes(Graphics g) {
	if (!enabled) {
	    return;
	}
	int charHeight = fm.getHeight();
	int h = size().height;
	int w = size().width;
	g.setColor(Color.black);
	/* X-axis */
	g.fillRect(XOFF, h - YOFF, w - (w % datalen), AXW);
	/* left stuff */
	for (int i = 0; i < xls.length && xls[i] != null; i++) {
	    g.drawChars(xls[i], 0, xls[i].length, xlx, xly + i*charHeight);
	}
	/* bottom stuff */
	for (int i = 0; i < yls.length && yls[i] != null; i++) {
	    g.drawChars(yls[i], 0, yls[i].length, ylx, yly + charHeight*i);
	}
	/* Y-axis */
	g.fillRect(XOFF, TOPOFF, AXW, h + AXW - YOFF - TOPOFF);
	/* Y-axis ticks */
	g.setFont(tiny);
	charHeight = fmt.getHeight();
	int div = h - AXW - YOFF - TOPOFF;
	for (int i = 0; i < nTicks; i++) {
	    int level = h - YOFF -  i*div/nTicks;
	    g.drawLine(XOFF - 2*AXW, level, XOFF, level);
	    g.drawChars(ticks[i], 0, ticks[i].length, 
			XOFF - TICKOFF, level + (charHeight >>> 2));
	}
	int level = TOPOFF;
	g.drawLine(XOFF - 2*AXW, level, XOFF, level);
	g.drawChars(ticks[nTicks], 0, ticks[nTicks].length, 
		    XOFF - TICKOFF, level + (charHeight >>> 2));
	g.setFont(font);
    }
    
    protected void updateData(Graphics g) {
	if (!enabled) {
	    return;
	}
	Rectangle rect = g.getClipRect();
	int h = rect.height;
	int w = rect.width;
	int barWidth = w/datalen;
	//	p("updateData: barWidth="+barWidth+", w="+w);
	g.setColor(Color.blue);
	g.clearRect(0, 0, barWidth, h);
	g.copyArea(barWidth, 0, w - barWidth, h, -barWidth, 0);
	g.clearRect(w - barWidth, 0, barWidth, h);
	int barHeight = (int)((data[ind]/ymax) * h);
	g.fillRect(w - barWidth, h - barHeight, barWidth, barHeight);
    }

    protected void paintData(Graphics g) {
	if (!enabled) {
	    return;
	}

	Rectangle rect = g.getClipRect();
	int h = rect.height;
	int w = rect.width;
	int barWidth = w/datalen;
	g.clearRect(0, 0, w, h);
	g.setColor(Color.blue);
	//	p("paintData: barWidth="+barWidth+", w="+w);
	for (int i = ind + 1; i < datalen; i++) {
	    int barHeight = (int) ((data[i]/ymax) * h);
	    g.fillRect((i - ind - 1)*barWidth, h - barHeight,
			barWidth, barHeight);
	}
	for (int i = 0; i <= ind; i++) {
	    int barHeight = (int) ((data[i]/ymax) * h);
	    g.fillRect((datalen - ind - 1 + i)*barWidth, h - barHeight,
			barWidth, barHeight);
	}
    }
}


