///Current version
///ID:          $Id: Canvas.cs,v 1.1.1.1 2006/11/14 02:56:41 gupchup Exp $
///Revision:    $Revision: 1.1.1.1 $
///Date:        $Date: 2006/11/14 02:56:41 $

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using Spherical;

namespace Spherical.Visualizer
{
    public static class RegionImage
    {
        public static void SaveGif(string filename, Region region, double ra, double dec, double ppd)
        {
            RegionCanvas c = new RegionCanvas(ra, dec, "STR", 512, 512, ppd, false);
            region.Intersect(new Halfspace(new Cartesian(ra, dec), -0.99));
            region.Simplify(true, Const.Tolerance, Const.MaximumIteration, false);
            c.DrawRegion(region, Color.Yellow, 1);
            try { c.drawGrid(); }
            catch { }
            c.Image.Save(filename, ImageFormat.Gif);
        }
    }
    
    /// <summary>
	/// GraphicsEnv defines our own Graphic Enviroment for SDSS
	/// </summary>
	public class RegionCanvas
	{	
		private bool	debug=false;		
		private Int32	width, height;					// Image size
		private double	ppm;							// image resolution in pixels per arcminute
		private double  pixelScale;						// Pixel scale in arcsec/pixel units
		
		private  IProjection proj;						// the abstract Projection object		
		private TANProjection tproj;					// the TAN projection object
		private STRProjection sproj;					// the STR projection object

		private Graphics  gc;
		private Bitmap	  img;

		private Pen photoPen	= new Pen(Color.DodgerBlue, 1);			
		private Pen specPen		= new Pen(Color.Red, 1);					
		private Pen targetPen	= new Pen(Color.GreenYellow, 1);			
		private Pen bboxPen		= new Pen(Color.FromArgb(255,0,255),0.25F);
		private Pen outlinePen	= new Pen(Color.FromArgb(0,255,0),0.25F);			
		private Pen fieldPen	= new Pen(Color.Gray,0.25F);						
		private Pen maskPen		= new Pen(Color.Tomato,1);			
		private Pen platePen	= new Pen(Color.HotPink);
		private Pen testPen		= new Pen(Color.DarkTurquoise);
		private Pen gridPen		= new Pen(Color.LightGreen,1);
		private Pen rulerPen	= new Pen(Color.LightGreen,2);
		private Pen regionPen	= new Pen(Color.Orange,2.0F);

		//---------------
		// label formats
		//---------------
		private SolidBrush drawBrush	= new SolidBrush(Color.White);
		private SolidBrush fillBrush	= new SolidBrush(Color.FromArgb(17,59,227));
		private StringFormat drawFormat = new StringFormat();
		private Font font				= new Font("Arial", 9);

		//------------------------------------
		// define where the label is printed. 
		// yLabel changes as we write lines
		//------------------------------------
		private int xLabel		= 20;
		private int yLabel		= 15;
		private StringBuilder debugMessage = new StringBuilder();		

		/// <summary>
		/// SDSSGraphicsEnv constructor
		/// </summary>
		public RegionCanvas ( double ra, double dec, string projtype, Int32 w_, Int32 h_, double ppd, bool dbg_)
		{			
			debug		= dbg_;
			width		= w_;
			height		= h_;
			ppm			= ppd /60.0;
			pixelScale  = 3600.0/ppd;

			// Extend line ends to fill the whole pixel 
			outlinePen.StartCap = LineCap.Square;
			outlinePen.EndCap	= LineCap.Square;								

			//----------------------------------------------------------------------
			// Allocate the drawing canvas 
			PixelFormat pf	= PixelFormat.Format32bppRgb;	// use colors
			img			= new Bitmap(width, height, pf);	// Make our new image to user's adjusted specs
			gc			= Graphics.FromImage( img);			// Make an graphic obj we can draw on				
			gc.SmoothingMode = SmoothingMode.AntiAlias;		// set as default SmoothingMode
            this.InitializeProjection(ra, dec, ppd, projtype);
		}

		private void InitializeProjection(double ra_, double dec_, double ppd_, string ptype_)
		{
			//-------------------------------------
			// call the Projection constructor, then
			// cast onto the abstract Interface
			//-------------------------------------
			if (ptype_=="TAN")
			{
				tproj	= new TANProjection( ra_, dec_, ppd_, width, height);
				proj	= (IProjection)tproj;
			}
			if (ptype_=="STR") 
			{
				sproj	= new STRProjection( ra_, dec_, ppd_, width, height);
				proj	= (IProjection)sproj;
			}
		}

		//=============================================================
		// Image related functions
		//=============================================================

		/// <summary>
		/// get image property
		/// </summary>	
		public Bitmap Image   // 
		{
			get 
			{
				return this.img;
			}
		}

		/// <summary>
		/// getBuffer. Returns the bytes of the image buffer as a JPEG.
		/// </summary>
		/// <returns>Byte[] of the JPEG image</returns>
		public Byte[] getBuffer()
		{
				
			MemoryStream theJpeg = new MemoryStream();  // place to store the Jpeg
			img.Save(theJpeg, ImageFormat.Jpeg);		// make the Jpeg				
			Byte[] imgdata = theJpeg.ToArray();			// the image to be returned as a Jpeg byte stream	

			//#########################################################################
			// we will still need to write the WCS and copyright info into the header
			//#########################################################################

			return(imgdata);							// return image
		}

		/// <summary>
		/// Invert. It will invert each pixel of the image.
		/// </summary>
		public void Invert() 
		{
			//-------------------------------------------
			// GDI+ still lies to us - the return format   
			//-------------------------------------------
			BitmapData bmData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), 
				ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 
			int stride = bmData.Stride; 
			System.IntPtr Scan0 = bmData.Scan0; 
			unsafe 
			{ 
				byte * p = (byte *)(void *)Scan0;
				int nOffset = stride - img.Width*3; 
				int nWidth = img.Width * 3;
				for(int y=0;y < img.Height;++y) 
				{
					for(int x=0; x < nWidth; ++x ) 
					{
						p[0] = (byte)(255 - p[0]);
						++p;
					}
					p += nOffset;
				}
			}
			img.UnlockBits(bmData);
		} 
	
		/// <summary>
		/// GetViewPort. It will compute the ViewPort from the projection.
		/// </summary>
		public string GetViewPort()
		{
			//------------------------
			// construct the viewport
			//------------------------
			PointEq nw = proj.ScreenToEq(new PointF(0.0F,0.0F));
			PointEq ne = proj.ScreenToEq(new PointF((float)(width),0.0F));
			PointEq sw = proj.ScreenToEq(new PointF(0.0F,(float)(height)));
			PointEq se = proj.ScreenToEq(new PointF((float)(width),(float)(height)));

			//----------------------------
			// convert the vertices to V3
			//----------------------------
			double[] nw3 = V3.Normal(nw);
			double[] ne3 = V3.Normal(ne);
			double[] sw3 = V3.Normal(sw);
			double[] se3 = V3.Normal(se);

			//-----------------------------------------
			// compute the normal vectors 
			// corresponding to the great circle edges
			//-----------------------------------------
			double[] n3  = V3.Normalize(V3.Wedge(nw3,ne3));
			double[] s3  = V3.Normalize(V3.Wedge(se3,sw3));
			double[] w3  = V3.Normalize(V3.Wedge(sw3,nw3));
			double[] e3  = V3.Normalize(V3.Wedge(ne3,se3));

			string vs = "REGION CONVEX ";
			vs += n3[0].ToString()+" "+n3[1].ToString()+" "+n3[2].ToString()+" 0.0 ";
			vs += s3[0].ToString()+" "+s3[1].ToString()+" "+s3[2].ToString()+" 0.0 ";
			vs += e3[0].ToString()+" "+e3[1].ToString()+" "+e3[2].ToString()+" 0.0 ";
			vs += w3[0].ToString()+" "+w3[1].ToString()+" "+w3[2].ToString()+" 0.0 ";

			//addDebugMessage("ViewPort: "+vs+"\n");
			return vs;
		}

		//=============================================================
		// High level drawing routines
		//=============================================================

        public void DrawRegion(Spherical.Region region, Color color, int fill)
        {
            HTMRegion h = new HTMRegion(0, 0, "SECTOR");
            List<HTMArc> arcs = new List<HTMArc>(4*region.NumberOfPatches());

            for (int ic = 0; ic < region.ConvexList.Count; ic++)
            {
                Spherical.Convex convex = region.ConvexList[ic];
                ReadOnlyCollection<Spherical.Patch> pl = convex.PatchList;
                for (int ip = 0; ip < pl.Count; ip++)
                {
                    for (int ia = 0; ia < pl[ip].ArcList.Count; ia++)
                    {
                        Spherical.Arc ta = pl[ip].ArcList[ia];
                        HTMArc a = new HTMArc(ic, ic * 10 + ip, 1,
                            ta.Point1.RA, ta.Point1.Dec,
                            ta.Point2.RA, ta.Point2.Dec,
                            ta.Circle.Vector.X, ta.Circle.Vector.Y, ta.Circle.Vector.Z,
                            ta.Circle.Cos0,
                            ic * 100 + ip * 10 + ia);
                        arcs.Add(a);
                    }
                }
            }
            //------------------------------------
            // copy the ArrayList into the array
            //------------------------------------
            h.arc = arcs.ToArray();
            h.count = h.arc.Length;

            this.drawRegion(h, color, fill);
        }

		/// <summary>
		/// drawRegion. Draw the outlines of a region.
		///  </summary>
		/// <param name="hr">The struct containing an HTMRegion</param>
		private void drawRegion(HTMRegion hr, Color color, int fill)	
		{		
			if (hr.arc.Length==0)
			{
				debugMessage.Append("NULL Region "+hr.rid.ToString()+"  "+fill.ToString()+"\n");        
				return;
			}

			debugMessage.Append("DrawRegion "+hr.rid.ToString()+" " + fill.ToString()+"\n");
      
			//----------------------------
			// do an adaptive resolution
			//----------------------------
			int nstep = 100 -3 * ((int)(pixelScale/10.0));
			if (nstep<30) nstep=30;
			double delta = 2*Math.PI/(nstep-1);

			//-------------
			// create path
			//-------------
			GraphicsPath path = new GraphicsPath();

			//----------------------------------------
			// allocate enough storage for the points
			//----------------------------------------
			int npoints = nstep*hr.arc.Length;
			PointF[] pts = new PointF[npoints];

			int np = 0;
			try
			{
				Int64 convex = hr.arc[0].cvx;
				Int32 patch  = hr.arc[0].patch;
				for (int k=0;k<hr.arc.Length;k++) 
				{
					//---------------------------------------------------
					// if a new patch or a new convex, draw it right now
					//---------------------------------------------------
					if (hr.arc[k].cvx != convex | hr.arc[k].patch != patch) 
					{
						//drawPatch(pts, np, cl, hr.fill);
						if (np!=0) addPatch(pts, np, path);
						convex = hr.arc[k].cvx;
						patch  = hr.arc[k].patch;
						np = 0;
					}

					//-------------------------------------------
					// if it is not drawable, go to the next arc
					//-------------------------------------------
					if (hr.arc[k].draw==0) continue;

					double[] r1	= V3.Normal(hr.arc[k].ra1,hr.arc[k].dec1);
					double[] r2	= V3.Normal(hr.arc[k].ra2,hr.arc[k].dec2);

					//----------------------------------------------------------
					// get the equation of the plane, and the circle of the arc
					//----------------------------------------------------------
					double cth	= hr.arc[k].c;
					double sth	= Math.Abs(Math.Sin(Math.Acos(cth)));
					double[] n  = {hr.arc[k].x,hr.arc[k].y, hr.arc[k].z};
					PointEq  g  = V3.ToEq(n);
					double[] w  = V3.West(g.ra,g.dec);
					double[] u  = V3.North(g.ra,g.dec);

					for (int m=0;m<3;m++)
					{
						n[m]  *= cth;
						r1[m] -= n[m];
						r2[m] -= n[m];
						w[m]  *= sth;
						u[m]  *= sth;
					}

					//----------------------------------------------------------
					// compute the two angles defining the two roots on the arc
					//----------------------------------------------------------
					double phi1 = Math.Atan2(V3.Dot(r1,u),V3.Dot(r1,w));
					double phi2 = Math.Atan2(V3.Dot(r2,u),V3.Dot(r2,w));
					if (phi2<=phi1) phi2+=Math.PI*2.0;

					//---------------------------------
					// compute precise number of steps
					//---------------------------------
					int steps = (int) Math.Floor((phi2-phi1)/delta);
					if (steps<15) steps=15;

					//----------------------
					// do the loop over phi
					//----------------------
					double[] r  = new double[3];
					double phi = phi1;
					double inc = (phi2-phi1)/(steps-1);
					for (int i = 0; i < steps; i++) 
					{ 
						for (int m=0;m<3;m++)
						{
							r[m] = n[m] + Math.Cos(phi)*w[m] + Math.Sin(phi)*u[m];
						}
						pts[np++] = proj.EqToScreen(V3.ToEq(r),0.0F);
						phi += inc;
					}				
				}

				if (np!=0) addPatch(pts, np, path);
                Pen rpen = new Pen(color,0.25F);
				SolidBrush oBrush = new SolidBrush(Color.FromArgb(64,color));
				if(fill>0) gc.FillPath(oBrush,path);

				//--------------------------
				// now draw the path itself
				//--------------------------
				gc.DrawPath(rpen, path);		
			}
			catch {displayMessage("exception in drawRegion "+hr.rid.ToString()+"\n");}			
		}


		/// <summary>
		/// addPatch. Insert the outline of a patch given by a polyline into the path.
		///  </summary>
		/// <param name="pts">The array containing the points in screen coordinates</param>
		/// <param name="np">The number of points to be drawn</param>
		/// <param name="path">The GraphicsPath to be appended to</param>

		public void addPatch (PointF[] pts, int np, GraphicsPath path)	
		{
			//-----------------------------------------------
			// copy the points so that we can use a polyline
			//-----------------------------------------------
			PointF[] pp = new PointF[np];
			for (int i=0;i<np;i++) pp[i] = pts[i]; 

			path.CloseFigure();
			path.AddLines(pp);
		}


		/// <summary>
		/// drawLabel. Draws a label at the next line.
		/// </summary>
		/// <param name="label">The text of the label.</param>
		public void drawLabel(string label)
		{
			gc.ResetTransform();
			gc.DrawString(label, font, drawBrush, xLabel, yLabel, drawFormat);
				
			string[] lines = label.Split(new char[] {'\n'});
			yLabel +=  (int)font.SizeInPoints * (lines.Length+1); 
		}
	

		/// <summary>
		/// drawGrid. Draws the axes and tickmarks on the canvas.
		/// </summary>
		public void drawGrid()
		{
			//-------------------------------------------------------------------
			// write the grid with tickmarks 
			gc.ResetTransform();

			// first draw X, Y grid and the N, S, E, W labels on the grid

			int cFudge	= 8;
			float inner	= 0.05F*Math.Max(width,height);
			float outer	= 0.125F;
			float xc	= width/2;
			float yc	= height/2;

			gc.DrawLine(gridPen, xc, yc-inner, xc, height*outer);				// x axis
			gc.DrawLine(gridPen, xc, yc+inner, xc, height*(1.0F -outer) );	// x axis
			gc.DrawLine(gridPen, xc-inner, yc, width*outer, yc);				// y axis
			gc.DrawLine(gridPen, xc+inner, yc, width*(1.0F-outer), yc);		// y axis

			gc.DrawString("N", font, drawBrush, xc-cFudge, 2*cFudge, drawFormat);
			gc.DrawString("S", font, drawBrush, xc-cFudge, height-4*cFudge, drawFormat);				
			gc.DrawString("E", font, drawBrush, 2*cFudge, yc-cFudge, drawFormat);		
			gc.DrawString("W", font, drawBrush, width-4*cFudge, yc-cFudge, drawFormat);

			//----------------------------------
			// now draw decide on the tick size
			//----------------------------------
			scaleItem[] ruler = new scaleItem []
			{
				new scaleItem("0.5''", 1.0/120.0),
				new scaleItem("1''", 1.0/60.0),
				new scaleItem("2''", 2.0/60.0),
				new scaleItem("5''", 5.0/60.0),
				new scaleItem("10''", 1.0/6.0),
				new scaleItem("20''", 2.0/6.0),
				new scaleItem("1'", 1.0),
				new scaleItem("2'", 2.0),
				new scaleItem("5'", 5.0),
				new scaleItem("10'", 10.0),
				new scaleItem("20'", 20.0),
				new scaleItem("30'", 30.0),
				new scaleItem("1deg", 60.0)
			};
				
			double extent = Math.Min(width,height);
			int ticks = 10;

			int i;
			for (i=0; i < ruler.Length; i++) 
			{
				ticks = (int)Math.Floor(extent / (ruler[i].size*ppm) );
				if ((ticks >= 4) && (ticks < 12) ) 	break;
			}
			string label	= ruler[i].label;
			double tickSize = ruler[i].size * ppm;
				
			//--------------------
			// draw the tickmarks
			//--------------------
			int pos =  0;
			for (i = -ticks; i <= ticks; i++)
			{
				pos = (int) Math.Floor(i*tickSize);
				gc.DrawLine(gridPen, 0, yc + pos,	cFudge, yc + pos);
				gc.DrawLine(gridPen, width, yc + pos, width-cFudge, yc + pos);
				gc.DrawLine(gridPen, xc + pos, 0,	xc + pos,	cFudge);
				gc.DrawLine(gridPen, xc + pos, height, xc + pos, height-cFudge);
			}								

			//-------------------------------------------------
			// draw the tick and write the label at the center
			//-------------------------------------------------
			int x1	= xLabel;		
			int x2	= xLabel + (int)tickSize;
			int len	= 4;
			int yy  = yLabel+20;
			gc.DrawLine(rulerPen, x1, yy      , x2, yy      );
			gc.DrawLine(rulerPen, x1, yy - len, x1, yy + len);
			gc.DrawLine(rulerPen, x2, yy - len, x2, yy + len);
			
			xLabel += (int)(0.5*tickSize) - cFudge;
			this.drawLabel(label);
		}

		//=============================================================
		// Debug and printing routines
		//=============================================================
		
		/// <summary>
		/// addDebugMessage. Add a line to the current debug message string.
		/// </summary>
		/// <param name="s">string to print</param>
		public void addDebugMessage(string s)
		{
			debugMessage.Append(s);
		}

		/// <summary>
		/// drawDebugMessage. Draws the debugmessage at the current location.
		/// </summary>
		public void drawDebugMessage()
		{
			drawLabel(debugMessage.ToString());
		}


		/// <summary>
		/// displayMessage. Draw the message on the screen.
		/// </summary>
		/// <param name="error">String to display</param>
		public void displayMessage(string error)
		{	
			gc.ResetTransform();
			gc.FillRectangle(new SolidBrush(Color.Black),0,0,width,height);
			gc.DrawString(error,new Font("Arial", 10),new SolidBrush(Color.White), 
				10  ,10 ,new StringFormat());
		}

		//=============================================================
		// Support routines
		//=============================================================

		private class scaleItem
		{
			public string label;
			public double size;

			public scaleItem (string strLabel, double vArcminutes) 
			{
				label = strLabel;
				size  = vArcminutes;
			}
		}


		/// <summary>
		/// drawAxis. Debugging, draws the N direction in red, the E in green.
		/// </summary>
		/// <param name="ra_">Right ascension of center</param>
		/// <param name="dec_">Declination of center</param>
		public void drawAxis(double ra_, double dec_)
		{
			PointF p = proj.EqToScreen(ra_,dec_,0.0F);
			PointF n = proj.EqToScreen(ra_,dec_+0.1F,0.0F);
			PointF w = proj.EqToScreen(ra_+0.1F,dec_,0.0F);
			gc.DrawLine(specPen,p,n);
			gc.DrawLine(outlinePen,p,w);

		}

		/// <summary>
		/// regionColor. Returns the drawing color of a given region type.
		/// </summary>
		/// <param name="type">Region type as a string in upper case</param>
		/// <param name="isMask">0 if not a mask, 1 if mask</param>
		/// returns Color.
		public Color regionColor(string type, int isMask)
		{
			//---------------------------
			// set the appropriate color
			//---------------------------
			Color cl;
			switch(type)
			{
				case "CAMCOL":
					cl = Color.White;
					break;
				case "CHUNK":
					cl = Color.Orange;
					break;
				case "HOLE":
					cl = Color.FromArgb(0,255,255);
					break;
				case "TIHOLE":
					cl = Color.FromArgb(128,128,255);
					break;
				case "PLATE":
					cl = Color.White;
					break;
				case "PRIMARY":
					cl = Color.Green;
					break;
				case "RUN":
					cl = Color.Blue;
					break;
				case "SECTOR":
					cl = Color.Yellow;
					break;
				case "SECTORLET":
					cl = Color.Yellow;
					break;
				case "SEGMENT":
					cl = Color.FromArgb(128,255,128);
					break;
				case "SKYBOX":
					cl = Color.FromArgb(128,255,128);
					break;
				case "STAVE":
					cl = Color.FromArgb(0,255,255);
					break;
				case "STRIPE":
					cl = Color.FromArgb(0,255,0);
					break;
				case "TIGEOM":
					cl = Color.FromArgb(255,128,128);
					break;
				case "TILE":
					cl = Color.FromArgb(0,255,255);
					break;
				case "TIPRIMARY":
					cl = Color.FromArgb(155,200,100);
					break;
				case "TILEBOX":
					cl = Color.FromArgb(255,255,0);
					break;
				case "WEDGE":
					cl = Color.FromArgb(255,255,0);
					break;
				default:
					cl = Color.White;
					break;
			}
			if (isMask>0) cl = Color.FromArgb(255,0,0);
			return cl;
		}
	}
}
/* Revision History

        $Log: Canvas.cs,v $
        Revision 1.1.1.1  2006/11/14 02:56:41  gupchup
        no message

        Revision 1.5  2006/03/01 20:39:34  budavari
        Using List<> for HTMArc collection

        Revision 1.4  2006/02/28 16:44:00  budavari
        new lib

        Revision 1.3  2006/02/13 22:55:03  budavari
        Less linefeed

        Revision 1.2  2006/02/13 17:07:19  budavari
        Canvas is public to draw more regions

        Revision 1.1  2006/02/03 21:16:42  budavari
        Added module for visualization - based on Geom2

        Revision 1.6  2003/04/10 21:54:32  szalay
         projection interface added

        Revision 1.5  2003/04/08 17:12:00  nieto
        Trying to include projection.cs class in project

        Revision 1.3  2003/03/25 23:18:33  nieto
        testing draw fields with their own coord
		
*/
