package utilities;

import java.awt.*;
import java.io.*;

public class DotPlot extends Canvas{
	protected final static int MAXNUMBINS = 200;
	protected final static int MAXDATAPOINTS = 1000;
	protected final static int MAXINBIN = 200;
	
	protected	int 	fBinHeight = 3;
	protected	double 	fBinSpacing = 2;
		
	protected final int kXOffset = 10;
	protected  int fXSize = 200;
	protected  int fYSize = 140;
	protected  int fAxisHeight = 140;
	protected  int fAxisY = fYSize - 15;
	
	protected String 	fMessage = "DotSelected";
	
	protected int		fTopSpacing = 15;
	
	protected Image	m_OffImage = null;
	
	protected int	fNumBins;
	
	protected	int[]		fBins = new int[MAXNUMBINS];
	protected int[][]		fBinIndices = new int[MAXNUMBINS][MAXINBIN];
	protected	int[]		fOldBins = new int[MAXNUMBINS];
	
	protected int			fSelectedBin,fSelectedDot;
	protected int			fNumPoints;
	protected int			fNumOld;
	protected double[]	fData = new double[MAXDATAPOINTS];
	protected int[]		fIndices = new int[MAXDATAPOINTS];
	
	protected double[]	fOldData = new double[MAXDATAPOINTS];
	protected double		fpHat, fpHatOld; 

	protected double			fBinWidth;
	protected double			fTickSpacing;
		
	protected double			fMin,fMax;
	protected double			fPixelsPerX=0;
	protected double			fPixelsPerY=1;

	protected double			fMean,fStdDev;

	protected String			fTitle;

	protected Font			fTickFont = new Font("Helvetica", Font.PLAIN, 9);
	protected FontMetrics	fTickMetrics = getFontMetrics(fTickFont);
	protected Font			fTitleFont = new Font("Helvetica",Font.PLAIN,10);
	protected FontMetrics	fTitleMetrics = getFontMetrics(fTitleFont);

	protected boolean fArrowDefined = false;
	protected double	fArrowLoc;
	protected int		fSampleSize;
	
	protected MessageReceiver m_Receiver;
	
	public DotPlot(MessageReceiver rec, String title, double min, double max) {
		fMin = min;
		fMax = max;
		fTitle = title;
		fTickSpacing = utils.getTickSpacingEstimate(min,max);
		fSelectedDot = -1;
		fSelectedBin = -1;
		m_Receiver = rec;
	}
	
	/**
	 * Adjusts the minimum and maximum values of the x-axis according
	 * to the minimum and maximum values of the data set
	 */
	public void autoAdjustXAxis()
	{
		double mn = 0;
		double mx = 0;
		if (fNumPoints == 0) {
			mn = 0;
			mx = 1;
		}
		else {
			mn = fData[0];
			mx = fData[0];
			for (int i=0; i<fNumPoints; i++) {
				if (fData[i]<mn) mn = fData[i];
				if (fData[i]>mx) mx = fData[i];
			}
			double diff = mx-mn;
			mn -= diff * 0.1;
			mx += diff * 0.1;
		}
		if (mn == mx) mx+=1;
		fMin = mn;
		fMax = mx;
		fTickSpacing = utils.getTickSpacingEstimate(mn,mx);
	}

	protected void createBuffer() {
		m_OffImage = createImage(size().width, size().height);
	}

	protected void clearGraphics(Graphics g) {
		Color c= g.getColor();
		g.setColor(getBackground());
		g.fillRect(0,0,size().width,size().height);
		g.setColor(c);
	}

	public void reshape(int x, int y, int w, int h) {
		fXSize = w;
		fYSize = h;
		fAxisY = fYSize - 30;
		fAxisHeight = fAxisY - fTopSpacing;
		super.reshape(x,y,w,h);
	}

	public void addPoint(double val, int ind) {
		if (fNumPoints == MAXDATAPOINTS) return;
		fData[fNumPoints] = val;
		fIndices[fNumPoints] = ind;
		fNumPoints++;
	}
	
	public void addPoint(double val) {
		addPoint(val,0);
	}

	public void doStatCalc() {
		double EX=0,EX2=0;

		for (int i=0; i<fNumPoints; i++) {
			EX += fData[i]/(double)(fNumPoints);
			EX2 += fData[i]*fData[i]/(double)(fNumPoints);
		}

		fMean = EX;
		fStdDev = Math.sqrt(EX2 - EX*EX);
	}

	public double mean() {return fMean;}
	public double dev() {return fStdDev;}

	public void addNotify() {
		super.addNotify();
		setUpAxis();
	}

	public Dimension preferredSize() {return new Dimension(fXSize,fYSize);}
	public Dimension minimumSize() {return preferredSize();}

	public void setUpAxis() {
		fPixelsPerX = (fXSize - 2.0 * kXOffset)/(double)(fMax-fMin);
		if (fNumBins>MAXNUMBINS) fNumBins=MAXNUMBINS;
	}

	public void binWidth(double val) {
		fBinWidth = val;
		fNumBins = (int)((fMax-fMin)/fBinWidth+1);
	}
	
	public void arrowLoc(double val) {
		fArrowDefined = true;
		fArrowLoc = val;
	}
	
	public void sampleSize(int val) {
		fSampleSize = val;
	}

	public void clearCurrent() {
		fNumPoints = 0;
		fSelectedBin = -1;
		fArrowDefined = false;
	}
	
	public void clearOld() {
		fNumOld = 0;
	}

	public void copyToOld() {
		fNumOld = fNumPoints;
		for (int i=0; i<fNumOld; i++) 
			fOldData[i] = fData[i];
	}

	public void setMin(double val) {fMin = val;fTickSpacing = utils.getTickSpacingEstimate(fMin,fMax);}
	public void setMax(double val) {fMax = val;fTickSpacing = utils.getTickSpacingEstimate(fMin,fMax);}
	public void setTickSpacing(double val) {fTickSpacing = val;}

	public int x2pxl(double x) {return (int)((x-fMin)*fPixelsPerX+kXOffset);}
	public double pxl2x(int xi) {return (xi-kXOffset)/fPixelsPerX + fMin;}

	public int y2pxl(double y) {return (int)(fAxisY-y*fPixelsPerY);}
	public double pxl2y(int yi) {return (fAxisY - yi)/fPixelsPerY;}
	
	public void drawTitle(Graphics g) {
		if (fTitle == null) return;
		g.setColor(Color.black);
		g.setFont(fTitleFont);
		g.drawString(fTitle,size().width/2 - fTitleMetrics.stringWidth(fTitle)/2, 12);
	}
	
	public void setBinHeights() {
		int binspacing = 2;
		int binheight = 8;
		int y = 0;
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binspacing--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binheight--;
				break;
			}
		}
		
		for (int i=0;i<fNumBins;i++) {
			y = (int)(fBins[i]*(binspacing+binheight));
			if (fAxisY-y<=fTopSpacing) {
				binspacing--;
				break;
			}
		}
		
		fPixelsPerY = binspacing+binheight;
		
		fBinHeight = binheight;
		fBinSpacing = binspacing;
	}

	public void drawAxis(Graphics g) {
		String label;

		g.setColor(Color.black);
		g.setFont(fTickFont);
		g.drawLine(kXOffset, fAxisY, fXSize - 2*kXOffset, fAxisY);
		g.drawRect(kXOffset-3, fAxisY-fAxisHeight, fXSize-2*kXOffset+6, fAxisHeight);
		int x;
		double min;
		
		min = utils.findMin(fMin,fTickSpacing);
		for (double d = min; d<=fMax; d+=fTickSpacing) {
			x = x2pxl(d);
			g.drawLine(x,fAxisY-4,x,fAxisY);
			label = utils.num2str(d,2);
			g.drawString(label,x-(int)(fTickMetrics.stringWidth(label)/2.0), (int)(fAxisY+15));
		}
	}

	public void drawStats(Graphics g) {
		String label;

		g.setColor(Color.black);
		g.setFont(fTitleFont);
		label = "Mean = " + utils.num2str(fMean);
		g.drawString(label,20, fAxisY+35);
		label = "Std Dev = " + utils.num2str(fStdDev);
		g.drawString(label,100, fAxisY+35);
	}

	public void drawBins(Graphics g) {
		int binpos;
		
		int y;
		
		g.setColor(Color.black);
		
		Color c;
		
		int binwidth = (x2pxl(fBinWidth) - x2pxl(0))-2;
		if (binwidth < 1) binwidth = 1;
		
		for (int i=0; i<fNumBins; i++) {
			binpos = x2pxl(fMin+fBinWidth*i);
			g.setColor(new Color(128,128,150));
			g.drawLine(binpos,fAxisY,binpos,fTopSpacing);
			g.setColor(new Color(128,180,128));
			for (int j=0; j<fOldBins[i]; j++) {
				y = (int)((j+1)*(fBinSpacing+fBinHeight));
				if (fAxisY - y <= fTopSpacing) {
					g.drawString("+",binpos-2,fTopSpacing+2);
					break;
				}
				g.fillRect(binpos-binwidth*3/4,fAxisY - y-1, binwidth, fBinHeight);
			}
		}
		
		for (int i=0; i<fNumBins; i++) {
			binpos = x2pxl(fMin+fBinWidth*i);
		
			g.setColor(Color.black);
				
			for (int j=0; j<fBins[i]; j++) {
				y = (int)((j+1)*(fBinSpacing+fBinHeight));
				if (fAxisY - y <= fTopSpacing) {
					g.drawString("+",binpos-2,fTopSpacing + 2);
					break;
				}
				
				c = g.getColor();
				if (i == fSelectedBin && j==fSelectedDot) 
					g.setColor(Color.yellow);
				g.fillRect(binpos-binwidth/2,fAxisY - y, binwidth, fBinHeight);
				
				if (i == fSelectedBin && j==fSelectedDot) {
					g.setColor(Color.black);				
					g.drawRect(binpos-binwidth/2,fAxisY - y, binwidth, fBinHeight);
				}
				
				g.setColor(c);
			}
		}
	}
	
	public void setUpBins() {
		double bin;

		setUpAxis();
		
		for (int i=0; i<fNumBins; i++) fBins[i] = 0;
		for (int i=0; i<fNumPoints; i++) {
			bin = (fData[i]-fMin+fBinWidth/2.0)/fBinWidth;
			bin = (int)bin;
			if (bin<0) bin = 0;
			if (bin>=fNumBins) bin = fNumBins-1;
			fBinIndices[(int)bin][fBins[(int)bin]] = fIndices[i];
			fBins[(int)bin]++;
		}

		for (int i=0; i<fNumBins; i++) fOldBins[i] = 0;
		for (int i=0; i<fNumOld; i++) {
			bin = (fOldData[i]-fMin+fBinWidth/2.0)/fBinWidth;
			if (bin<0) continue;
			if (bin>=fNumBins) continue;
			fOldBins[(int)bin]++;
		}
		setBinHeights();
	}

	public void drawArrow(Graphics g) {
		if (!fArrowDefined) return;
		int x = x2pxl(fArrowLoc);
		g.setColor(Color.red);

		g.fillRect(x-2,fAxisY+4,5,10);
		for (int i=0; i<6; i++)
			g.drawLine(x-i,fAxisY+1+i,x+i,fAxisY+1+i);

		//utils.drawSpecialString(g,"T",x-3,fAxisY+25,getFontMetrics(g.getFont()));
		
		String L = new String(utils.num2str(fMean));
		
		x = x - getFontMetrics(g.getFont()).stringWidth(L)/2;

		g.drawString(L,x,fAxisY+25);
		
		g.setColor(Color.black);
	}
			
	public void update(Graphics g) {
		paint(g);
	}
			
	public void paint(Graphics g) {		
		if (m_OffImage == null) {
			createBuffer();
		}
		Graphics offG = m_OffImage.getGraphics();
		clearGraphics(offG);	

		if (fPixelsPerX==0) setUpAxis();
		drawTitle(offG);
		offG.setClip(kXOffset-3, fAxisY-fAxisHeight, fXSize-2*kXOffset+6, fAxisHeight);
		drawBins(offG);
		offG.setClip(0,0,size().width,size().height);
		drawAxis(offG);
		g.drawImage(m_OffImage,0,0,null);
		offG.dispose();
	}
	
	public void messageString(String s) {fMessage = s;}
	
	public boolean mouseDown(Event e, int x, int y) {
		
		fSelectedBin = fSelectedDot = -1;

		double bin;
		
		double xf = pxl2x(x);
		bin = (xf-fMin+fBinWidth/2.0)/fBinWidth;
		bin = (int)bin;
		if (bin<0) bin = 0;
		if (bin>=fNumBins) return false;//bin = fNumBins-1;
		
		double yf = pxl2y(y);
	
		if ((int)(yf)>=fBins[(int)bin])
			return false;//yf = fBins[(int)bin] - 1;
			
		if (yf>=0) {
			fSelectedBin = (int)bin;
			fSelectedDot = (int)(yf);
			repaint();
			m_Receiver.message(fMessage,(double)fBinIndices[fSelectedBin][fSelectedDot]);
		}
		return super.mouseDown(e,x,y);
	}
	
	public double min() {return fMin;}
	public double max() {return fMax;}
		
	public String string() {
		String s="";
		for (int i=0; i<fNumPoints; i++) {
			s += utils.num2str(fData[i]);
			if (i<fNumPoints-1)
			s += "\n";
		}
		return s;
	}
	
	public void loadDataFromStream(InputStream  is) {
		try {
			StreamTokenizer fTknzr = new StreamTokenizer(is);
		
			fNumPoints = 0;
		
			int val = 0;
			
			while (val != StreamTokenizer.TT_EOF) {
				val = fTknzr.nextToken();
				if (val == StreamTokenizer.TT_NUMBER) {
					if (fNumPoints>=MAXDATAPOINTS) {
						break;
					}
					addPoint(fTknzr.nval,fNumPoints);
				}
			}
		
			autoAdjustXAxis();
			binWidth((max()-min())/15.0);
			setUpBins();
			doStatCalc();
			repaint();
		}
		catch (Exception e) {
		}
	}

	public void setDataTo(String s) {
		try {
			InputStream is = new StringBufferInputStream(s);
			loadDataFromStream(is);
			is.close();
		}
		catch (Exception e) {
		}
	}
}