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

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Data.SqlClient;
using System.Net;
using System.Data.SqlTypes;

namespace Geom
{
	/// <summary>
	/// Interface definition for different projections.
	/// </summary>
	public interface IProjection
	{
		PointF EqToScreen(double ra_, double dec_);
		PointF EqToScreen(PointEq e_);
		PointEq ScreenToEq(PointF p_);
	}


	/// <summary>
	/// CARTProjection
	/// Gives us the definition of the projection to the screen
	/// using a cartesian projection, centered on (ra=180, dec=0).
	/// </summary>
	public class CARTProjection: IProjection
	{
		public static string Revision
		{
			get 
			{
				return "$Revision: 1.1.1.1 $";
			}
		}
		private Int32  width, height;					// screen size
		private PointF cOffset;							// reference point offset
		private double scale;							// image scale
		private double[] n, w, u;						// normal, west, up vectors
		/// <summary>
		/// InitProjection. Initialize the transformation for the projection
		/// to be used on the screen.
		/// </summary>
		public CARTProjection(double ra_, double dec_, double ppd_, int width_, int height_)
		{
			ra_  = 180.0;
			dec_ = 0.0;
			n = V3.Normal(ra_,dec_);
			w = V3.West(ra_,dec_);
			u = V3.North(ra_,dec_);

			//scale		= ppd_/V3.D2R;
			scale = ppd_;
			width		= width_;
			height		= height_;
			cOffset		= new PointF((float)(width/2.0),(float)(height/2.0));
		}		

		//------------------------------
		// Coordinate transformations
		//------------------------------
		
		/// <summary>
		/// EqToScreen. Maps an (ra,dec) coordinate to the Screen.
		/// This is the basic coordinate transformation function
		///  </summary>
		/// <param name="ra_">Right ascension in degrees, double</param>
		/// <param name="dec_">Declination in degrees, double</param>
		/// <param name="size_">Symbol size in pixels, float</param>
		public PointF EqToScreen(double ra_, double dec_) 
		{
			double gn = Math.Cos(dec_ * V3.D2R);
			double gw;
			if(gn == 0)
			{
				gw = 0;
			}
			else
			{
				gw = (ra_ - 180.0);
			}
			PointF q = new PointF(cOffset.X - (float)(scale * gw), cOffset.Y - (float)(scale * dec_));
			return q;
		}

		public PointF EqToScreen(PointEq e) 
		{
			return EqToScreen(e.ra, e.dec);
		}


		/// <summary>
		/// ScreenToEq. Maps a screen coordinate to an (ra,dec).
		/// This is the inverse coordinate transformation function
		///  </summary>
		/// <param name="p">The screen coordinate of the point</param>
		public PointEq ScreenToEq(PointF p) 
		{
			double[] r = new Double[3];

			//----------------------------------------
			// first subtract the offset, and rescale
			//----------------------------------------
			PointF q = new PointF((float)((cOffset.X-p.X)/scale), (float)((cOffset.Y-p.Y)/scale));

			//------------------------------------------------
			// now these are the dot products with w and u
			// compute the dot product with the normal vector
			//------------------------------------------------
			double qdec = 90*q.Y;
			double qra  = 180 + 180* q.X * Math.Cos(qdec*V3.D2R);
			/*
						gn = Math.Sqrt(gn);
						for (int i=0;i<3;i++) 
						{
							r[i] = gn*n[i]+q.X*w[i]+q.Y*u[i];
						}
						PointEq g = V3.ToEq(r);
			*/
			PointEq g = new PointEq(qra,qdec);
			return g;
		}
	}

	/// <summary>
	/// TANProjection
	/// Gives us the definition of the projection to the screen
	/// using a tangent plane projection, centered on (ra,dec).
	/// </summary>
	public class TANProjection: IProjection
	{
		public static string Revision
		{
			get 
			{
				return "$Revision: 1.1.1.1 $";
			}
		}
		private Int32  width, height;					// screen size
		private PointF cOffset;							// reference point offset
		private double scale;							// image scale
		private double[] n, w, u;
		/// <summary>
		/// InitProjection. Initialize the transformation for the projection
		/// to be used on the screen.
		/// </summary>	
		public TANProjection(double ra_, double dec_, double ppd_, int width_, int height_)
		{
			n = V3.Normal(ra_,dec_);
			w = V3.West(ra_,dec_);
			u = V3.North(ra_,dec_);

			scale		= ppd_/V3.D2R;
			width		= width_;
			height		= height_;
			cOffset		= new PointF((float)(width/2.0),(float)(height/2.0));
		}		

		//------------------------------
		// Coordinate transformations
		//------------------------------
		
		/// <summary>
		/// EqToScreen. Maps an (ra,dec) coordinate to the Screen.
		/// This is the basic coordinate transformation function
		///  </summary>
		/// <param name="ra_">Right ascension in degrees, double</param>
		/// <param name="dec_">Declination in degrees, double</param>
		public PointF EqToScreen(double ra_, double dec_) 
		{
			double [] r = V3.Normal(ra_,dec_);
			double gn=V3.Dot(r,n);
			double gw=V3.Dot(r,w);
			double gu= V3.Dot(r,u);
			PointF q = new PointF(cOffset.X - (float)(scale*gw/gn),cOffset.Y - (float)(scale*gu/gn));
			return q;
		}

		public PointF EqToScreen(PointEq e) 
		{
			return EqToScreen(e.ra, e.dec);
		}


		/// <summary>
		/// ScreenToEq. Maps a screen coordinate to an (ra,dec).
		/// This is the inverse coordinate transformation function
		///  </summary>
		/// <param name="p">The screen coordinate of the point</param>
		public PointEq ScreenToEq(PointF p) 
		{
			double[] r = new Double[3];

			//----------------------------------------
			// first subtract the offset, and rescale
			//----------------------------------------
			PointF q = new PointF((float)((cOffset.X-p.X)/scale), (float)((cOffset.Y-p.Y)/scale));

			//------------------------------------------------
			// now these are the dot products with w and u
			// compute the dot product with the normal vector
			//------------------------------------------------
			double gn = 1.0-q.X*q.X-q.Y*q.Y;
			gn = Math.Sqrt(gn);
			for (int i=0;i<3;i++) 
			{
				r[i] = gn*n[i]+q.X*w[i]+q.Y*u[i];
			}
			PointEq g = V3.ToEq(r);
			return g;
		}
	}

	/// <summary>
	/// STRProjection
	/// Gives us the definition of the projection to the screen
	/// using a stereographic projection, centered on (ra,dec).
	/// </summary>
	public class STRProjection: IProjection
	{
		public static string Revision
		{
			get 
			{
				return "$Revision: 1.1.1.1 $";
			}
		}
		private Int32  width, height;					// screen size
		private PointF cOffset;							// reference point offset
		private double scale;							// image scale
		private double[] n, w, u;
		/// <summary>
		/// InitProjection. Initialize the transformation for the projection
		/// to be used on the screen.
		/// </summary>	
		public STRProjection(double ra_, double dec_, double ppd_, int width_, int height_)
		{
			n = V3.Normal(ra_,dec_);
			w = V3.West(ra_,dec_);
			u = V3.North(ra_,dec_);

			scale		= ppd_/V3.D2R;
			width		= width_;
			height		= height_;
			cOffset		= new PointF((float)(width/2.0),(float)(height/2.0));
		}		

		//-----------------------------
		// Coordinate transformations
		//-----------------------------
		
		/// <summary>
		/// EqToScreen. Maps an (ra,dec) coordinate to the Screen.
		/// This is the basic coordinate transformation function
		///  </summary>
		/// <param name="ra_">Right ascension in degrees, double</param>
		/// <param name="dec_">Declination in degrees, double</param>
		public PointF EqToScreen(double ra_, double dec_) 
		{
			double [] r = V3.Normal(ra_,dec_);
			double gn   = V3.Dot(r,n);
			double gw	= V3.Dot(r,w);
			double gu	= V3.Dot(r,u);
			double a	= 2.0/(1+gn);
			PointF q	= new PointF(cOffset.X-(float)(scale*a*gw),cOffset.Y-(float)(scale*a*gu));
			return q;
		}

		public PointF EqToScreen(PointEq e) 
		{
			return EqToScreen(e.ra, e.dec);
		}

		/// <summary>
		/// Screen to equatorial coordinates for STR projection
		/// </summary>
		/// <param name="p"></param>
		/// <returns></returns>
		public PointEq ScreenToEq(PointF p) 
		{
			double[] r = new Double[3];

			//----------------------------------------
			// first subtract the offset, and rescale
			//----------------------------------------
			PointF q = new PointF((float)((cOffset.X-p.X)/scale), (float)((cOffset.Y-p.Y)/scale));

			//------------------------------------------------
			// now these are the dot products with w and u
			// compute the dot product with the normal vector
			//------------------------------------------------
			double rho	 = 0.25*(q.X*q.X + q.Y*q.Y);
			for (int i=0;i<3;i++) 
			{
				r[i] = ((1.0-rho)*n[i]+q.X*w[i]+q.Y*u[i])/(1.0+rho);
			}
			PointEq g = V3.ToEq(r);
			return g;
		}
	}

	/// <summary>
	/// SDSSProjection
	/// Gives us the definition of the projection to the screen
	/// using the standard SDSS notation of projections.
	/// This was implemented to keep the Projection interface consistent.
	/// </summary>
	public class SDSSProjection: IProjection
	{
		public static string Revision
		{
			get 
			{
				return "$Revision: 1.1.1.1 $";
			}
		}

		private PointF cOffset;							// reference point offset
		private double rotation;						// rotation of reference frame
		private double cosR, sinR;						// sin, cos of rotation matrix
		private Coord  refCoord;						// the reference coordinate system
		private Int32  width, height;					// screen size
		private bool debug = false;
		private SqlDataReader reader	= null;
		private SqlConnection sqlConn = null;
		private int		zoom = 0;
		double ra_, dec_, imageScale_;
		private float size = 0.0F;
		
		/// <summary>
		/// InitProjection. Initialize the transformation for the projection
		/// to be used on the screen.
		/// </summary>	
		public SDSSProjection(double ra_, double dec_, double imageScale_, int width_, int height_)
		{
			this.ra_ = ra_;
			this.dec_ = dec_;
			this.imageScale_ = imageScale_;
			this.width = width_;
			this.height = height_;
		}		

		/// <summary>
		/// Set up the projection types.
		/// Set the properties
		/// </summary>
		public void setUp ()
		{
			// Get the coord object
			Coord fc_ = new Coord();	
			fc_ = getSDSSRefCoord(ra_, dec_);

			// rotation (degrees) to make north "up" -- compute from coordinates.
			rotation = fc_.rotateDegreesToNorth(ra_,dec_);

			cosR = Math.Cos(rotation*Coord.D2R);
			sinR = Math.Sin(rotation*Coord.D2R);

									
			refCoord = new Coord ();
			refCoord.copy (fc_);    
			refCoord.scale /= imageScale_;

			// and define the upper corner
			cOffset		= new PointF(0.0F,0.0F);
			PointF p	= refCoord.EqToFrame(ra_, dec_);
			cOffset		= new PointF(p.X, p.Y);

			///<comment>
			/// Use for debug purposes only. 
			///</comment>
			/*
			if (debug && false) 
			{
				PointF q = Projection.EqToScreen(ra,dec,0.0F);
				debugMessage.AppendFormat("Initializing projection\n\n");        
				debugMessage.AppendFormat("refCoord.scale: {0}, imageScale: {1}\n", refCoord.scale, imageScale);        
				debugMessage.AppendFormat("refCoord.info: {0}\n", refCoord.info);
				debugMessage.AppendFormat("Rotation: {0}\n", rotation);        
				debugMessage.AppendFormat("Astrom: {0}, {1}, {2}, {3}, {4}, {5}\n {6},{7}\n",
				refCoord.a, refCoord.b, refCoord.c, 
				refCoord.d, refCoord.e, refCoord.f,
				refCoord.node, refCoord.incl);
			}
			*/
				

		}

		/// <summary>
		/// Set the connection with the database to be used by
		/// getRefCoord function
		/// </summary>
		/// <param name="sqlConn">connection to the SkyServer</param>
		public void setConnection (SqlConnection sqlConn)
		{
			this.sqlConn = sqlConn;
		}

		/// <summary>
		/// Set the zoom value which is desired
		/// Used in getRefCoord function
		/// </summary>
		/// <param name="zoom"></param>
		public void setZoom (int zoom)
		{
			this.zoom = zoom;
		}

		/// <summary>
		/// size was originaly a part of EqToScreen but the interface was changed
		/// to contain no size. For marking objects using the query, the size
		/// is however required to be part of the EqToScreen and so its set here
		/// so that it can be used in the EqToScreen function. Under normal circumstance
		/// the size variable should be set to zero but in mark using query, the size
		/// is needed.
		/// </summary>
		/// <param name="_size"></param>
		public void setSize (float _size)
		{
			size = _size;
		}

		/// <summary>
		/// Return the refCoord. Used in calculating affineTransformation (Canvas.cs)
		/// </summary>
		/// <returns></returns>
		public Coord getRefCoord ()
		{
			return refCoord;
		}

		//=============================================================
		// Coordinate transformations
		//=============================================================
		
		/// <summary>
		/// EqToScreen. Maps an (ra,dec) coordinate to the Screen.
		/// This is the basic coordinate transformation function
		///  </summary>
		/// <param name="ra_">Right ascension in degrees, double</param>
		/// <param name="dec_">Declination in degrees, double</param>
		/// <param name="size_">Symbol size in pixels, float</param>
		/// <remarks>
		/// Under normal circumstances the size is always "0" and doesnt play
		/// a role but for mark-objects-using-query feature, size is required
		/// and hence is passed as an argument before calling this function
		/// so that size plays a role.
		/// </remarks>
		/// 
		public PointF EqToScreen(double ra_, double dec_)
		{
			PointF p = refCoord.EqToFrame(ra_, dec_);
				
			// back to the center
			p.X -= cOffset.X;	
			p.Y -= cOffset.Y;
	
			// now rotate and swap the X coordinate
			PointF q = new PointF(
				(float)( -cosR * p.X + sinR * p.Y), 
				(float)( sinR * p.X + cosR * p.Y)
				);

				
			q.X += (float) (width/2 - 0.5*size);
			q.Y += (float) (height/2 - 0.5*size);

			return q;
		}

		/// <summary>
		/// Equatorial to screen
		/// </summary>
		/// <param name="e"></param>
		/// <returns></returns>
		public PointF EqToScreen(PointEq e)
		{
			return EqToScreen(e.ra, e.dec);
		}

		/// <summary>
		/// Added just to fulfill the interface definition
		/// Required to draw the arcs in the grid.
		/// </summary>
		/// <param name="p_"></param>
		/// <returns></returns>
		/// <comment name="jayant">
		/// I reversed engineered the screenToEq to draw implement this but im not sure
		/// if this works !
		/// </comment>
		public PointEq ScreenToEq(PointF p_)
		{
			p_.X -= (float)(width/2 - 0.5*size);
			p_.Y -= (float) (height/2 - 0.5*size);
 
			PointF q = new PointF (
				(float) (-cosR * p_.X + sinR * p_.Y),
				(float) (sinR * p_.X + cosR * p_.Y));

			q.X += cOffset.X;
			q.Y += cOffset.Y;

			PointEq eq = refCoord.FrameToEq (q.X, q.Y);
			return eq;
		}
		
		/// <summary>
		/// Gets the reference frame(s) for the given (ra,dec) using the frameSQL logic as in ImgCutout
		/// In the earlier ImgCutout version, it was implemented in the main class ImgCutout.
		/// SDSS doesnt implement WCS therefore it needs to get the frames closest to a given (ra, dec)
		/// using a different logic mechanism. Once those frames are obtained, they are processed using a
		/// similar logic as the WCS (Generic) implementation. The implementation of the frame handling
		/// is implemented in Survey::SDSSSurveuy.getFrames ();
		/// For Generic implementation logic refer to Survey::NonSDSSSurvey.getFrames ();
		/// </summary>
		/// <param name="ra"></param>
		/// <param name="dec"></param>
		/// <returns></returns>
		public Coord getSDSSRefCoord (double ra, double dec)
		{
			//--------------------------------------------------------------------------------------------
			// astro transformations are done around the "center" frame
			// extract the center coordinate parameters from the frame closest to that spot.
			// sql query to get frame coordinates

			StringBuilder sQ= new StringBuilder();
			sQ.Append("SELECT dbo.fSDSS(f.fieldID), f.a, f.b, f.c, f.d, f.e, f.f, \n");
			sQ.Append("f.node, f.incl, f.stripe \n" );
			sQ.AppendFormat("FROM dbo.fGetNearestFrameEq({0},{1},0) as n, Frame f \n", ra, dec);
			sQ.Append("WHERE f.fieldId=n.fieldId and f.zoom=0 \n");

			// fetch the frame parameters and save them in "center" coordinate.
			Coord centerCoord = null;
			SqlCommand cmdCenter = new SqlCommand(sQ.ToString(), sqlConn);
			try  
			{
				reader = cmdCenter.ExecuteReader();
				if (reader.Read()) 
				{
					centerCoord = new Coord(
						Convert.ToDouble(reader[1]),		// a
						Convert.ToDouble(reader[2]),		// b
						Convert.ToDouble(reader[3]),		// c
						Convert.ToDouble(reader[4]),		// d
						Convert.ToDouble(reader[5]),		// e
						Convert.ToDouble(reader[6]),		// f
						Convert.ToDouble(reader[7]),		// node
						Convert.ToDouble(reader[8]), 		// incl
						Math.Pow(2,zoom),					// zoomScale
						Convert.ToString(reader[0]));		// info
					 
					reader.Close();	
				}				 				
				else 
				{					
					throw new Exception("Requested (ra, dec) is outside the SDSS footprint. \n");
				}
			}
			catch  (Exception e)
			{ 
				if (debug) 
				{
					showException("getRefCoord()", sQ.ToString(), e);
				}
				else 
					throw new Exception (e.Message);
			}
			return centerCoord;
		}

		/// <summary>
		/// Assemmble a generic message and throws the Exception
		/// </summary>
		public void showException(string sFunction, string sQuery, Exception e)
		{

			StringBuilder msg = new StringBuilder();
			msg.AppendFormat("{0} has failed:\n{1}\n", sFunction, sQuery);
			msg.AppendFormat("Exception Message:{0}", e.Message);
				
			throw new Exception(msg.ToString()); //Actual exception
		}

	}
}
