/*
 * StatsGraph.java
 *	  A simple statistics graphing program
 *	  Usage:  <applet code="StatsGraph.class" width=100 height=100>
 *		  </applet>
 *
 * Ken Shirriff	 ken.shirriff@eng.sun.com
 *
 * Copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 * Please refer to the file "license.txt"
 * for further important copyright and licensing information.
 */

import java.awt.*;
import java.util.StringTokenizer;
import java.awt.image.*;
import java.net.*;
import java.io.*;
import java.applet.Applet;

public class StatsGraph extends Applet {
	String file;
	int width, height;
	final int YEAR=0;
	final int M=1,IV=2,MIV=3,H=4,HS=5,SIV=6,SB=7,BII=8,SH=9,SO=10,B=11,O=12, SUM=13;
	// Data is arrayed as follows: 0: x label, 1..maxcat: male data,
	// maxcat+1..2*maxcat: female data
	// nfields==2*maxcat+1
	int maxcat;
	int nfields;
	String labelstr[]=null;
	int row;
	Color colors[] = null;
	int numColors = 6;

	Graph graph = null;
	Panel p1 = new Panel();
	Checkbox csex[] = new Checkbox[2];
	Checkbox cm[] = null;
	ColorBox colorcode[][] = null;
	Label label[] = null;
	Label mf = new Label("");
	Checkbox cumul = null;
	Choice presets = null;
	String presetTab[] = null;
	int npresets = 0;
	Checkbox log = null;
	Checkbox change = null;
	Checkbox bounds = null;
	Checkbox stack = null;
	Checkbox frac = null;
	TextField boundsMin=null, boundsMax=null;
	Font font = null;

	public void init() {
		int fontsize=9;
		if (getParameter("width") != null) {
			width = Integer.parseInt(getParameter("width"));
		}
		if (getParameter("height") != null) {
			height = Integer.parseInt(getParameter("height"));
		}
		if (getParameter("fontsize") != null) {
			fontsize = Integer.parseInt(getParameter("fontsize"));
		}
		file = getParameter("file");
		if (file==null) {
			System.out.println("Need to specify file");
			showStatus("Need to specify file");
			return;
		}

		font = new Font("helvetica",Font.PLAIN, fontsize);

		readFile(file);
		initColors();
		setupLayout();
		updateData();
		setupPresets();
	}

	void initColors() {
		colors = new Color[28];
		colors[0] = Color.black;
		colors[2] = new Color(255,140,0); //M
		colors[4] = new Color(0,140,140); //IV
		colors[6] = new Color(0,255,140); //MIV
		colors[8] = new Color(90,130,0); //H
		colors[10] = new Color(140,0,140); //HS
		colors[12] = new Color(60,120,120); //SIV
		colors[14] = new Color(0,60,120); //SB
		colors[16] = new Color(120,110,0); //BII
		colors[18] = new Color(200,100,60); //SH
		colors[20] = new Color(0,0,140); //SO
		colors[22] = new Color(140,0,0); //B
		colors[24] = new Color(0,140,0); //O
		colors[26] = new Color(240,240,180); //SUM
		numColors=28;
		for (int i=0;i<numColors;i+=2) {
			colors[i+1] = colors[i].brighter().brighter();
		}
		// gray out categories that only exist for one sex
		colors[2*1+1] = Color.lightGray;
		colors[2*MIV+1] = Color.lightGray;
		colors[2*SB] = Color.lightGray;
	}



	// Read the presets from the params.
	public void setupPresets() {
		presetTab = new String[100];
		for (npresets=1;npresets<100;npresets++) {
			String p = getParameter("preset"+npresets);
			if (p == null) {
				npresets--;
				break;
			}
			presets.addItem(""+npresets);
			presets.setFont(font);
			presetTab[npresets] = p;
		}
		if (npresets>0) {
			doPreset(presetTab[1]);
		}
	}


	public void constrain(GridBagLayout gridbag, GridBagConstraints gc, Container parent, Component c, int w) {
		gc.gridwidth = w;
		gridbag.setConstraints(c,gc);
		parent.add(c);
	}

	public void setupLayout() {
		setFont(font);
		GridBagLayout topGridbag = new GridBagLayout();
		GridBagConstraints topGc = new GridBagConstraints();
		topGc.fill = GridBagConstraints.BOTH;
		topGc.weighty=1;
		topGc.weightx=1;
		topGc.gridheight = 3;
		setLayout(topGridbag);
		graph = new Graph();
		//graph.resize(new Dimension(size().width,300));
		graph.setFont(font);
		constrain(topGridbag, topGc, this, graph,1);
		topGc.weighty=20;
		topGc.gridheight = 1;

		Panel buttonPanel = new Panel();
		buttonPanel.setBackground(Color.lightGray);
		GridBagLayout buttonGridbag = new GridBagLayout();
		GridBagConstraints buttonGc = new GridBagConstraints();
		buttonPanel.setLayout(buttonGridbag);
		buttonGc.fill = GridBagConstraints.HORIZONTAL;
		buttonGc.weightx=0;
		buttonGc.weighty=1;


		Label m = new Label("M"), f = new Label("F"), t = new Label("");
		constrain(buttonGridbag,buttonGc,buttonPanel, m, 1);
		constrain(buttonGridbag,buttonGc,buttonPanel, f, 1);
		constrain(buttonGridbag,buttonGc,buttonPanel,t, GridBagConstraints.REMAINDER);
		csex[0] = new Checkbox();
		csex[0].setState(false);
		constrain(buttonGridbag,buttonGc,buttonPanel,csex[0], 1);
		csex[1] = new Checkbox();
		csex[1].setState(false);
		constrain(buttonGridbag,buttonGc,buttonPanel,csex[1], 1);
		constrain(buttonGridbag,buttonGc,buttonPanel,mf, GridBagConstraints.REMAINDER);
		//constrain(buttonGridbag,buttonGc,buttonPanel,new Label("M"), 1);
		//constrain(buttonGridbag,buttonGc,buttonPanel,new Label("F"), GridBagConstraints.REMAINDER);


		for (int i=1;i<=maxcat;i++) {
			cm[i] = new Checkbox();
			cm[i].setState(true);
			//buttonGc.ipadx=20;
			buttonGc.weightx=0;
			colorcode[0][i] = new ColorBox();
			colorcode[0][i].setBackground(colors[i%numColors]);
			colorcode[1][i] = new ColorBox();
			colorcode[1][i].setBackground(colors[i%numColors]);
			label[i] = new Label(labelstr[i]);
			constrain(buttonGridbag,buttonGc,buttonPanel,colorcode[0][i], 1);
			constrain(buttonGridbag,buttonGc,buttonPanel,colorcode[1][i], 1);
			//constrain(buttonGridbag,buttonGc,buttonPanel,new Panel(), 2);
			buttonGc.ipadx=0;
			buttonGc.weightx=0;
			constrain(buttonGridbag,buttonGc,buttonPanel,cm[i], 1);
			constrain(buttonGridbag,buttonGc,buttonPanel,label[i], ((i%2)==0)||(i==i)?GridBagConstraints.REMAINDER:1);
		}
		for (int i=SIV;i<=SO;i++) {
			cm[i].setState(false);
		}
		cm[SUM].setState(true);

		topGc.weighty=.5;
		topGc.weightx=0;
		constrain(topGridbag, topGc, this, buttonPanel,GridBagConstraints.REMAINDER);

		Panel controls = new Panel();
		controls.setBackground(Color.lightGray);
		GridBagLayout controlsGridbag = new GridBagLayout();
		GridBagConstraints controlsGc = new GridBagConstraints();
		controlsGc.weighty=1;
		controlsGc.anchor=GridBagConstraints.WEST;
		controls.setLayout(controlsGridbag);

		presets = new Choice();
		presets.setFont(font);
		presets.addItem("Presets");
		constrain(controlsGridbag,controlsGc,controls,presets,GridBagConstraints.REMAINDER);
		cumul = new Checkbox("Cumul.");
		cumul.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,cumul,1);
		log = new Checkbox("Log");
		log.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,log,1);
		change = new Checkbox("% chng");
		constrain(controlsGridbag,controlsGc,controls,change,GridBagConstraints.REMAINDER);
		change.setFont(font);
		frac = new Checkbox("Frac");
		frac.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,frac,1);
		stack = new Checkbox("Stack");
		stack.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,stack,1);
		bounds = new Checkbox("Bounds");
		bounds.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,bounds,GridBagConstraints.REMAINDER);
		boundsMin = new TextField("",6);
		boundsMin.setFont(font);
		boundsMax = new TextField("",6);
		boundsMax.setFont(font);
		constrain(controlsGridbag,controlsGc,controls,boundsMin,1);
		constrain(controlsGridbag,controlsGc,controls,boundsMax,GridBagConstraints.REMAINDER);

		topGc.gridy = 2;
		topGc.fill = GridBagConstraints.VERTICAL;

		constrain(topGridbag, topGc, this, controls,GridBagConstraints.REMAINDER);
		super.validate();
	}

        int toInt(String s) {
                try {
                        return Integer.valueOf(s).intValue();
                } catch (java.lang.NumberFormatException e) {
                        System.out.println("Number exception on "+s);
                        return -1;
                }
        }

        double toDouble(String s) {
                try {
                        return Double.valueOf(s).doubleValue();
                } catch (java.lang.NumberFormatException e) {
                        System.out.println("Number exception on "+s);
                        return -1;
                }
        }

	double data[][];

	public void paint(Graphics g) {
		g.setFont(font);
		super.paint(g);
	}

	public void updateData() {
		int datacnt = 0;
		int s0 = 0;
		int s1 = 0;
		int mode;
		double plotdata[][] = new double[row][2*nfields];
		Color gColors[] = new Color[2*nfields];
		if (csex[0].getState() && csex[1].getState()) {
			mf.setText("Male, Female");
			// Plot both
			mode = 0;
			s1 = 1;
		} else if (csex[0].getState()==false && csex[1].getState()==false) {
			// Plot sum
			mode = 1;
			mf.setText("Combined");
		} else if (csex[0].getState()) {
			// Plot male
			mf.setText("Male");
			mode = 2;
		} else {
			mf.setText("Female");
			// Plot female
			s0 = 1;
			s1 = 1;
			mode = 3;
		}
		for (int c=1;c<=maxcat;c++) {
			if (cm[c].getState()==false) {
				colorcode[0][c].setBackground(Color.lightGray);
				colorcode[1][c].setBackground(Color.lightGray);
			} else {
				if (mode==0) {
					// both
					colorcode[0][c].setBackground(colors[(c)*2]);
					colorcode[1][c].setBackground(colors[(c)*2+1]);
					gColors[datacnt]=colors[(c)*2];
					gColors[datacnt+1]=colors[(c)*2+1];
				} else if (mode==1) {
					// sum
					colorcode[0][c].setBackground(colors[(c)*2]);
					colorcode[1][c].setBackground(colors[(c)*2]);
					gColors[datacnt]=colors[(c)*2];
					if (c==SB) {
						// Special case
						gColors[datacnt]=colors[(c)*2+1];
						colorcode[0][c].setBackground(colors[(c)*2+1]);
						colorcode[1][c].setBackground(colors[(c)*2+1]);
					}
				} else if (mode==2) {
					//male
					colorcode[0][c].setBackground(colors[(c)*2]);
					colorcode[1][c].setBackground(Color.lightGray);
					gColors[datacnt]=colors[(c)*2];
				} else if (mode==3) {
					//female
					colorcode[0][c].setBackground(Color.lightGray);
					colorcode[1][c].setBackground(colors[(c)*2+1]);
					gColors[datacnt]=colors[(c)*2+1];
				}
				for (int s=s0;s<=s1;s++) {
					double lastd=0, thisd, tot=0, sumcat, totsum=0;
					for (int j=0;j<row;j++) {
						thisd=0;
						if (mode==1) {
							thisd = data[j][c]+data[j][c+maxcat];
						} else {
							thisd = data[j][c+maxcat*s];
						}
						if (mode==1||mode==0) {
							sumcat = data[j][SUM]+data[j][SUM+maxcat];
						} else {
							sumcat = data[j][SUM+maxcat*s];
						}
						if (cumul.getState()) {
							tot += thisd;
							thisd = tot;
							totsum += sumcat;
							sumcat = totsum;
						}
						if (frac.getState()) {
							thisd = thisd/sumcat*100;
						}
						if (change.getState()) {
							if (lastd != 0) {
								plotdata[j][datacnt] = (thisd-lastd)/lastd*100;
							} else if (thisd==0) {
								plotdata[j][datacnt] = 0;
							} else {
								plotdata[j][datacnt] = 100; // Arbitrary
							}
						} else {
							plotdata[j][datacnt] = thisd;
						}
						lastd = thisd;
					}
					datacnt++;
				}
			}
		}
		graph.setColors(gColors,datacnt);
		graph.setData(plotdata,(int)data[0][0],row,datacnt);
		graph.setStack(stack.getState());
		graph.setLog(log.getState());
	}

	public void readFile(String file) {
		try {
			URL url = new URL(getDocumentBase(), file);
			InputStream is = url.openStream();
			DataInputStream dis = new DataInputStream(is);
			String s;
			StringTokenizer st;
			s = dis.readLine();
			st = new StringTokenizer(s,"\t");
			row = toInt(st.nextToken());
			nfields = toInt(st.nextToken());
			maxcat = (nfields-1)/2;
			if (maxcat != SUM) {
				System.out.println("Number of fields mismatch "+maxcat+" "+nfields);
			}
			data = new double[row][nfields];
			labelstr = new String[nfields+1];

			colorcode = new ColorBox[2][nfields+1];
			label = new Label[nfields+1];
			cm = new Checkbox[nfields+1];
			s = dis.readLine();
			st = new StringTokenizer(s,"\t");
			for (int j=0;j<nfields;j++) {
				labelstr[j] = st.nextToken();
			}
	// Data is arrayed as follows: 0: x label, 1..maxcat: male data,
	// maxcat+1..2*maxcat: female data
	// nfields==2*maxcat+1
			for (int i=0;i<row;i++) {
				s = dis.readLine();
				st = new StringTokenizer(s,"\t");
				for (int j=0;j<nfields;j++) {
					data[i][j] = toDouble(st.nextToken());
				}
				if (data[i][0] != data[0][0]+i) {
					System.out.println("Bad x data "+data[i][0]+" on "+i);
				}
			}
		} catch (Exception e) {
			System.out.println("File exception on "+file);
			showStatus("File exception on "+file);
		}
	}

	// Perform a preset string.  Format is string of 0,1's:
	// cm checkboxes, 2 csex checkboxes, cumulative checkbox
	public void doPreset(String s) {
		try {
			for (int i=1;i<=maxcat;i++) {
				cm[i].setState(s.charAt(i-1)!='0');
			}
			csex[0].setState(s.charAt(maxcat-1+1)!='0');
			csex[1].setState(s.charAt(maxcat-1+2)!='0');
			cumul.setState(s.charAt(maxcat-1+3)!='0');
			log.setState(s.charAt(maxcat-1+4)!='0');
			graph.setLog(log.getState());
			bounds.setState(s.charAt(maxcat-1+5)!='0');
			change.setState(s.charAt(maxcat-1+6)!='0');
			stack.setState(s.charAt(maxcat-1+7)!='0');
			frac.setState(s.charAt(maxcat-1+8)!='0');
			if (bounds.getState()) {
				StringTokenizer st;
				st = new StringTokenizer(s,":");
				st.nextToken();
				boundsMin.setText(st.nextToken());
				boundsMax.setText(st.nextToken());
			}
			updateBounds();
		} catch (Exception e) {
			System.out.println("String exception "+s);
		}
	}

	public void printPreset() {
			for (int i=1;i<=maxcat;i++) {
				System.out.print(cm[i].getState()?'1':'0');
			}
			System.out.print(csex[0].getState()?'1':'0');
			System.out.print(csex[1].getState()?'1':'0');
			System.out.print(cumul.getState()?'1':'0');
			System.out.print(log.getState()?'1':'0');
			System.out.print(bounds.getState()?'1':'0');
			System.out.print(change.getState()?'1':'0');
			System.out.print(stack.getState()?'1':'0');
			System.out.print(frac.getState()?'1':'0');
			if (bounds.getState()) {
				System.out.print(":"+boundsMin.getText());
				System.out.print(":"+boundsMax.getText());
			} else {
				System.out.print("::");
			}
			System.out.println("");
	}
	public boolean action(Event e, Object o) {

		if (e.target instanceof TextField) {
			bounds.setState(true);
			updateBounds();
			updateData();
		}
		return super.action(e,o);
	}

	public boolean handleEvent(Event e) {
		if (e.target instanceof Choice && (String)e.arg != null) {
			for (int i=1;i<=npresets;i++) {
				if (((String)e.arg).equals(""+i)) {
					doPreset(presetTab[i]);
					updateData();
				}
			}
		}
		if (e.target instanceof Checkbox) {
			if (e.target.equals(log)) {
				graph.setLog(log.getState());
				// log doesn't work with other things
				if (log.getState()) {
					bounds.setState(false);
					change.setState(false);
					stack.setState(false);
					updateBounds();
				}
			} else if (e.target.equals(change)) {
				log.setState(false);
				stack.setState(false);
			} else if (e.target.equals(bounds)) {
				updateBounds();
			} else if (e.target.equals(stack)) {
				log.setState(false);
				change.setState(false);
			}

			updateData();
			printPreset();
		}
		return super.handleEvent(e);
	}

	public void updateBounds() {
		if (bounds.getState()) {
			log.setState(false);
			double min,max;
			//boundsMin.enable();
			//boundsMax.enable();
			try {
				min = Double.valueOf(boundsMin.getText()).doubleValue();
				max = Double.valueOf(boundsMax.getText()).doubleValue();
				graph.setRange(min,max);
			} catch (Exception ex) {
				graph.unsetRange();
			}
		} else {
			//boundsMin.disable();
			//boundsMax.disable();
			boundsMin.setText("");
			boundsMax.setText("");
			graph.unsetRange();
		}
	}

	public String getAppletInfo() {
		return "StatsGraph by Ken Shirriff  shirriff@eng.sun.com";
	}
 
	public String[][] getParameterInfo() {
		String [][] info = {
			{"width	  ","int	", "image width"},
			{"height  ","int	", "image height"},
			{"file	  ","string	 ", "filename"}
		};
		return info;
	}
}

// Make a box of the specified color
class ColorBox extends Panel {
	Color bg = Color.black;
	public void setBackground(Color c) {
		bg = c;
		repaint();
	}
	public void update(Graphics g) {
		paint(g);
	}
	public void paint(Graphics g) {
		g.setColor(bg);
		g.fillRect(0,0,100,100);
	}
}
