using System;
using System.Data;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Data.SqlClient;
namespace edu.jhu.Cas.Plot{
	public class TickVal{
		public double x0, dx;
		public int n;
	}
	public class Plotter{
		public bool PRESERVE_PROPORTION = false;
		public const string 
			X_COL="xcol",
			Y_COL="ycol",
			X_MIN="xmin",
			X_MAX="xmax",
			Y_MIN="ymin",
			Y_MAX="ymax";
		public const int 
			WIDTH=800, 
			TOP_M=50,
			BOTTOM_M=70,
			LEFT_M=70,	
			RIGHT_M=20,
			MAJOR_TICKS=15,
			MINOR_TICKS=6,
			TICK_WIDTH=5;
		public int HEIGHT=800;
		
		private string cstring, table, xcol, ycol,qry;
		private SqlCommand com;
		private float ymin,xmin,ymax,xmax;
		public Plotter(string cstring_, string table_, string xcol_, string ycol_, string qry_){
			cstring=cstring_;table=table_;xcol=xcol_;ycol=ycol_;
			qry=qry_.Trim();
		}
		public Stream PlotTable(){
			initConnection();
			try{
				initBoundaries();
				Bitmap bMap = new Bitmap(WIDTH,HEIGHT);
				Graphics g = Graphics.FromImage(bMap);
				//g.SmoothingMode = SmoothingMode.HighQuality;
				g.SmoothingMode = SmoothingMode.AntiAlias;
				g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
				drawCanvas(g);
				drawPoints(g);
				return saveToStream(bMap);
			}finally{
				com.Connection.Close();
                com.Connection.Dispose();
			}
		}
		public void PlotAndSaveTable(string path2Save){
			Stream s = PlotTable();
			FileStream fs = File.OpenWrite(path2Save);
			try{
				int read = s.ReadByte();
				while(read != -1){
					fs.WriteByte((byte)read);
					read = s.ReadByte();
				}
			}finally{
                try { s.Close(); }catch { }
                try { fs.Close(); }catch { }
			}
		}
		private void DrawTicks(Graphics g){
			Pen pen = new Pen(new SolidBrush(Color.Black),1);
			g.DrawRectangle(pen,LEFT_M,TOP_M,WIDTH-LEFT_M-RIGHT_M,HEIGHT-TOP_M-BOTTOM_M);
			StringFormat drawFormat = new StringFormat();
			drawFormat.Alignment = StringAlignment.Center;
			Font labelFont = new Font("Arial",8);
			TickVal tv = getTickVal(ymin,ymax,MAJOR_TICKS);
            
			ymin = (float)tv.x0;
            ymax = (float)(tv.x0 + (tv.dx*tv.n));
			RectangleF src = new RectangleF(0,ymin,TICK_WIDTH,ymax-ymin);
			Rectangle dest = new Rectangle(LEFT_M+1,TOP_M,TICK_WIDTH,HEIGHT-TOP_M-BOTTOM_M);
			GraphicsContainer gc = g.BeginContainer(dest,src,GraphicsUnit.Pixel);
                        
			for(double i=tv.x0+tv.dx;i<ymax;i+=tv.dx)//left
				g.DrawLine(pen,0,(float)i,TICK_WIDTH,(float)i);
            
			for(double d=tv.x0+(tv.dx/MINOR_TICKS);d<ymax;d+=tv.dx/MINOR_TICKS)	
				g.DrawLine(pen,0,(float)d,TICK_WIDTH/2,(float)d);
			g.EndContainer(gc);
            
			double dx = (tv.dx*(HEIGHT-TOP_M-BOTTOM_M))/(ymax-ymin);
			RectangleF layout = new RectangleF(0,0,LEFT_M,(float)dx*2);
            
			for(int n=1;n<tv.n;n++){//left labels
				layout.Y = (float)(HEIGHT-BOTTOM_M-(n*dx)-5);
				g.DrawString((tv.x0+(tv.dx*n)).ToString(),labelFont,pen.Brush,layout,drawFormat);
			}
			dest = new Rectangle(WIDTH-RIGHT_M-TICK_WIDTH,TOP_M,TICK_WIDTH,HEIGHT-TOP_M-BOTTOM_M);
			gc = g.BeginContainer(dest,src,GraphicsUnit.Pixel);
			for(double i=tv.x0+tv.dx;i<ymax;i+=tv.dx)//right
				g.DrawLine(pen,0,(float)i,(float)TICK_WIDTH,(float)i);
			for(double d=tv.x0+(tv.dx/MINOR_TICKS);d<ymax;d+=tv.dx/MINOR_TICKS)	
				g.DrawLine(pen,TICK_WIDTH,(float)d,TICK_WIDTH/2,(float)d);
			g.EndContainer(gc);
			tv = getTickVal(xmin,xmax,MAJOR_TICKS);
			xmin = (float)tv.x0;
			xmax = (float)(tv.x0 + (tv.dx*tv.n));
			xmax = (float)(tv.dx*tv.n);
			double tmp = tv.x0 + (tv.dx*tv.n);
            			
			xmax = (float)tmp;
			src = new RectangleF(xmin,0,xmax-xmin,TICK_WIDTH);
			dest = new Rectangle(LEFT_M,HEIGHT-BOTTOM_M-TICK_WIDTH,WIDTH-LEFT_M-RIGHT_M,TICK_WIDTH);
			gc = g.BeginContainer(dest,src,GraphicsUnit.Pixel);
			for(double i=tv.x0+tv.dx;i<xmax;i+=tv.dx)//bottom
				g.DrawLine(pen,(float)i,0,(float)i,(float)TICK_WIDTH);
			for(double d=tv.x0+(tv.dx/MINOR_TICKS);d<xmax;d+=tv.dx/MINOR_TICKS)	
				g.DrawLine(pen,(float)d,TICK_WIDTH,(float)d,TICK_WIDTH/2);
			g.EndContainer(gc);
			dx = (tv.dx*(WIDTH-LEFT_M-RIGHT_M))/(xmax-xmin);
			layout = new RectangleF(0,HEIGHT-BOTTOM_M+2,(float)dx*2,BOTTOM_M);
			for(int n=1;n<tv.n;n++){//bottom labels
				layout.X = (float)(LEFT_M+((n-1)*dx)+1);
				g.DrawString((tv.x0+(tv.dx*n)).ToString(),new Font("Arial",8),pen.Brush,layout,drawFormat);
			}
			
			src = new RectangleF(xmin,0,xmax-xmin,TICK_WIDTH);
			dest = new Rectangle(LEFT_M,TOP_M+1,WIDTH-LEFT_M-RIGHT_M,TICK_WIDTH);
			gc = g.BeginContainer(dest,src,GraphicsUnit.Pixel);
			for(double i=tv.x0+tv.dx;i<xmax;i+=tv.dx)//top
				g.DrawLine(pen,(float)i,(float)0,(float)i,(float)TICK_WIDTH);
			for(double d=tv.x0+(tv.dx/MINOR_TICKS);d<xmax;d+=tv.dx/MINOR_TICKS)	
				g.DrawLine(pen,(float)d,0,(float)d,TICK_WIDTH/2);
			g.EndContainer(gc);
			//g.DrawString("WTF",new Font("Arial",8),pen.Brush,0,0);
		}
		private void drawPoints(Graphics g){
			//string dataQry = "select top 10 "+xcol+" as "+X_COL+","+ycol+" as "+Y_COL+" from "+table;
			string dataQry = qry == null?"select "+xcol+" as "+X_COL+","+ycol+" as "+Y_COL+" from "+table:qry;
			//dataQry = "select  max(cx) as xcol, min(cy) as ycol from galaxy";
			com.CommandText = dataQry;
			SqlDataReader dataReader = com.ExecuteReader();
			RectangleF src = new RectangleF(xmin,ymin,xmax-xmin,ymax-ymin);
			Rectangle dest = new Rectangle(LEFT_M,TOP_M,WIDTH-LEFT_M-RIGHT_M-1,HEIGHT-TOP_M-BOTTOM_M-1);
			GraphicsContainer container = g.BeginContainer(dest,src,GraphicsUnit.Pixel);
			long read = 0;
			RectangleF newPixel = point2Pixel(src,dest);
			try{
				float x=1, y=1;
				DataTable schemaTable = dataReader.GetSchemaTable();
				Pen pen = new Pen(new SolidBrush(Color.Black),newPixel.Width);
				for(;;){
					try{
						if(!dataReader.Read())break;
						for(int c=0;c<dataReader.FieldCount;c++){
							switch(dataReader.GetName(c)){
								case X_COL:x=float.Parse(dataReader.GetValue(c).ToString());break;
								case Y_COL:y=float.Parse(dataReader.GetValue(c).ToString());break;
								default: Console.WriteLine(dataReader.GetName(c));break;
							}
						}
						newPixel.X = (float)x; newPixel.Y = (float)ymin-y+ymax;//is this right?
						//newPixel.X = (float)x; newPixel.Y = (float)y;//is this right?
						g.DrawRectangle(pen,newPixel.X,newPixel.Y,newPixel.Width,newPixel.Height);
						++read;
					}catch(SqlException se){
						Console.WriteLine(se.Message);
						dataReader.Close();
						while(!dataReader.IsClosed)
							System.Threading.Thread.Sleep(1*1000);
						com.Connection.Close();
						System.Threading.Thread.Sleep(30*1000);
						com.Connection.Open();
						com.CommandText = dataQry+" where not exists ("
							+dataQry.Replace("select","select top "+read+" ")+")";
						dataReader = com.ExecuteReader();
						continue;
					}
				}
				g.EndContainer(container);
			}finally{
				dataReader.Close();
			}	
		}
		private RectangleF point2Pixel(RectangleF src, Rectangle dest){
			return new RectangleF(0,0,src.Width/dest.Width,src.Height/dest.Height);
		}
		private Stream saveToStream(Bitmap bMap){
			MemoryStream ms = new MemoryStream();
			bMap.Save(ms,ImageFormat.Png);
			ms.Position = 0;
			return ms;
		}
		private void initConnection(){
			com = new SqlCommand();
			com.Connection = new SqlConnection(cstring);
			com.CommandTimeout = 5*60;
			com.Connection.Open();
		}
		//queries must already be in lower case!
		private void initBoundaries(){
			string boundQry = "select min("+xcol+") as "+X_MIN+", max("+xcol+") as "+X_MAX+", min("+ycol+") as "+
				Y_MIN+", max("+ycol+") as "+Y_MAX+" ";
			int fromi = qry.IndexOf("from");
			int selecti = qry.IndexOf("select");
			boundQry = qry.Replace(qry.Substring(selecti,fromi-selecti-1),boundQry);
            Console.WriteLine(boundQry);
			Console.WriteLine("^^^^^^^^^^^");
            
			SqlDataAdapter da = new SqlDataAdapter(com);
			com.CommandText = boundQry;
			DataSet boundaries = new DataSet();
			da.Fill(boundaries);
			ymin = float.Parse(boundaries.Tables[0].Rows[0][Y_MIN].ToString());
			xmin = float.Parse(boundaries.Tables[0].Rows[0][X_MIN].ToString());
			ymax = float.Parse(boundaries.Tables[0].Rows[0][Y_MAX].ToString());
			xmax = float.Parse(boundaries.Tables[0].Rows[0][X_MAX].ToString());
			//set height to preserve data proportions
			if(PRESERVE_PROPORTION)HEIGHT = (int)((WIDTH * (ymax-ymin))/(xmax-xmin));
		}
		private void drawCanvas(Graphics g){
			g.FillRectangle(new SolidBrush(Color.White), 
				0,0,WIDTH, HEIGHT);
			DrawTicks(g);
			StringFormat drawFormat = new StringFormat();
			drawFormat.Alignment = StringAlignment.Center;
			Font titleFont = new Font("Arial",14,FontStyle.Bold);
			g.DrawString("'"+xcol+"' vs '"+ycol+"'",titleFont,new SolidBrush(Color.Black),new RectangleF(0,0,WIDTH,TOP_M),drawFormat);
		}
		public TickVal getTickVal(double min, double max, int ticks){
			double x0 = min, dx = -1;
			double interval = (max - min) / ticks;
			double f = interval;
			int exp10 = 0;
			int n = ticks;
			bool go = true;
            if (interval == 0) 
                throw new Exception("Cannot plot columns without a range of values");
			//1 < f < 10
			if(interval > 10){
				while(f > 10){
					f = f / 10;
					exp10++;
				}
			}else if(interval < 1){
				while(f < 1){
					f = f * 10;
					exp10--;
				}
			}else if (interval <= 0){
				go = false;
			}
			double tint;
			if(f < 1.5)
				tint = 1;
			else if(f < 3)
				tint = 2;
			else if(f < 5)
				tint = 5;
			else
				tint = 10;
			if(go){
				dx = tint * Math.Pow(10,exp10);
				tint = Math.Round((min / dx));
				x0 = dx * tint;
				n = int.Parse(Math.Round((max - x0) / dx).ToString());
			}
			if(x0 > min){//check bottom
				x0 = x0 - dx;
				n++;
			}
			if(x0 + (dx * n) < max)//check top
				n++; 
			TickVal val = new TickVal();
			val.x0 = x0;
			val.n = n;
			val.dx = dx;
			return val;
		}
	}
}