/******************************************************************************
 * BreastSketchImage.cs 
 * 
 * <Copyright and License> 
 * 
 * This file is part of GODS, a package for annotating medical images. 
 * 
 * Copyright (C) 2004 
 * Xuedong Chen 
 * Heri Mariwa 
 * Kimmy (Hsiang-ling) Lin 
 * Rui Yu 
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation; either version 2 
 * of the License, or (at your option) any later version. 
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 *  
 * For further questions about this file, please send email to 
 * gods@cs.umb.edu or visit us at http://gods.sourceforge.net.  
 ******************************************************************************/

using System;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;

namespace GODS
{
	using MarkData;

	/// <summary>
	/// Summary description for BreastSketchImage.
	/// </summary>
	public class BreastSketchImage : DiagnosticImage
	{
		public const string AXILLARY = "Ax";
		public const string BREAST = "breast";
		public const string SUPRACLAVICULAR = "SC";
		public const string NIPPLE = "nipple";
		
		Point rNipplePosition, lNipplePosition;
		int breastRadius, nippleRadius;

		/// <summary>
		/// Default Constructor
		/// </summary>
		public BreastSketchImage(Bitmap image) : base(image)
		{
			// nipples' coordinates of the given breast image. 
			rNipplePosition = new Point(126, 141);
			lNipplePosition = new Point(227, 141);

			// distance from the nipple to a breast boundary point.
			// used to determine a valid drop target within a breast.
			// drop location is valid if the distance of the dropped Mark to the
			// closest nipple is less than the radius.
			breastRadius = 48;
			//nippleRadius = 10;
			nippleRadius = 20;
		}

		/// <summary>
		/// Getter and Setter for left nipple position
		/// </summary>
		public Point LNipplePosition 
		{
			get 
			{
				return lNipplePosition;
			}

			set 
			{
				lNipplePosition = value;
			}
		}

		/// <summary>
		/// Getter and Setter for right nipple position
		/// </summary>
		public Point RNipplePosition
		{
			get
			{
				return rNipplePosition;
			}
			set
			{
				rNipplePosition = value;
			}
		}



		public override ArrayList buildSummary(ArrayList marks)
		{
			ArrayList summaries = new ArrayList();
			StringBuilder builder;
			StringBuilder anotherBuilder = new StringBuilder();

			foreach (Mark mark in marks)
			{
				builder = new StringBuilder();
				if(mark.HasImage)
				{
					Point clientPoint = new Point(
						(int) (mark.Location.X + (mark.Width/2)),
						(int) (mark.Location.Y + (mark.Height/2)));
					//means singleton mark, thus not need to indicate the position
					builder.Append(this.IsLeft(mark.Location)? "L" : "R");
				
					builder.Append(" ");
					builder.Append(this.getRegion(clientPoint));
					builder.Append(": ");
				}
				builder.Append(mark.Description);
				if (mark.HasPresetValues && mark.CurrentValueIndex != 0) 
				{
					builder.Append(" : ");
					builder.Append(mark.CurrentValue);
				}
				if (mark.HasImage) 
				{
					builder.Append(" ");
					builder.Append(" Estimated size: ");
					builder.Append(mark.MarkWidth);
					builder.Append("cm x ");
					builder.Append(mark.MarkHeight);
					builder.Append("cm");
					builder.Append(" at ");
					builder.Append(this.GetClock(mark.Location));
					builder.Append(" o'clock.");
				}
				builder.Append(" ");
				builder.Append(mark.MarkComment);
				summaries.Add(builder.ToString());
				anotherBuilder.Append(builder.ToString() + System.Environment.NewLine);
			}
			base.LatestSummary = anotherBuilder.ToString();
			return summaries;
		}

		/// <summary>
		/// check the position to see if it is in left breast or right breast
		/// </summary>
		/// <param name="clientPoint"></param>
		/// <returns></returns>
		private bool IsLeft(Point clientPoint)
		{
			double distToLNipple = PointsDistance(clientPoint, lNipplePosition);
			double distToRNipple = PointsDistance(clientPoint, rNipplePosition);
			return (distToLNipple < distToRNipple)? true : false;
		}

		/// <summary>
		/// get clock according to right or left nipple.
		/// </summary>
		/// <param name="clientPoint"></param>
		/// <returns></returns>
		public int GetClock(Point clientPoint)
		{
			int clock = 0;
			double tanH, tanV;
			if (IsLeft(clientPoint))
			{
				tanH = clientPoint.X - lNipplePosition.X;
				tanV = -(clientPoint.Y - lNipplePosition.Y);
			}
			else
			{
				tanH = clientPoint.X - rNipplePosition.X;
				tanV = -(clientPoint.Y - rNipplePosition.Y);
			}

			clock = GetClock(tanH, tanV);
			return clock;
		}

		/// <summary>
		/// private clock calculation 
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		private int GetClock(double x, double y)
		{
			int clock = 0;
			double result = Math.Atan((y/x)) * (180/Math.PI);
			if (y >= 0)
			{
				if (0<= result && result < 15.0)
					clock = 3;
				else if(15.0 <= result && result < 45.0)
					clock = 2;
				else if(45.0 <= result && result < 75.0)
					clock = 1;
				else if((75.0 <= result && result < 90.0) || (-90.0 <= result && result < -75.0))
					clock = 12;
				else if(-75.0 <= result && result < -45.0)
					clock = 11;
				else if(-45.0 <= result && result < -15.0)
					clock = 10;
				else if(-15.0 <= result && result < 0)
					clock =9;
			}
			else
			{
				if (0<= result && result < 15.0)
					clock = 9;
				else if(15.0 <= result && result < 45.0)
					clock = 8;
				else if(45.0 <= result && result < 75.0)
					clock = 7;
				else if((75.0 <= result && result < 90.0) || (-90.0 <= result && result < -75.0))
					clock = 6;
				else if(-75.0 <= result && result < -45.0)
					clock = 5;
				else if(-45.0 <= result && result < -15.0)
					clock = 4;
				else if(-15.0 <= result && result < 0)
					clock =3;
			}
			return clock;
		}
		
		/// <summary>
		/// helper for points distance calculation
		/// </summary>
		/// <param name="p1"></param>
		/// <param name="p2"></param>
		/// <returns></returns>
		private double PointsDistance(Point p1, Point p2) 
		{
			return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
		}

		/// <summary>
		/// get the region name according to mark position,
		/// return null if the region is invalid
		/// </summary>
		/// <param name="clientPoint"></param>
		/// <returns></returns>
		public string getRegion(Point clientPoint)
		{
			double distToLNipple = PointsDistance(clientPoint, LNipplePosition);
			double distToRNipple = PointsDistance(clientPoint, RNipplePosition);

			if (distToLNipple < this.nippleRadius || distToRNipple < this.nippleRadius)
			{
				return NIPPLE;
			}

			if ((distToLNipple < this.breastRadius || distToRNipple < this.breastRadius)
				|| ((clientPoint.X > 80 && clientPoint.X < 174) && (clientPoint.Y > 74 && clientPoint.Y < 141))
				|| ((clientPoint.X > 180 && clientPoint.X < 274) && (clientPoint.Y > 74 && clientPoint.Y < 141)))
			{
				return BREAST;
			}
			
			if (((clientPoint.X > 52 && clientPoint.X < 76) && (clientPoint.Y > 70 && clientPoint.Y < 135))
				|| ((clientPoint.X > 278 && clientPoint.X < 301) && (clientPoint.Y > 70 && clientPoint.Y < 135)))
			{
				return AXILLARY;
			}

			if (((clientPoint.X > 88 && clientPoint.X < 154) && (clientPoint.Y > 9 && clientPoint.Y < 49))
				|| ((clientPoint.X > 200 && clientPoint.X < 266) && (clientPoint.Y > 9 && clientPoint.Y < 49)))
			{
				return SUPRACLAVICULAR;
			}			
			return null;
		}

		public override void saveAnnoImage(string path, ArrayList marks)
		{
			Bitmap tempBMP = new Bitmap(this.RealImage);
			Graphics g = Graphics.FromImage(tempBMP);
			
			foreach (Mark mark in marks) 
			{
				if (mark.HasImage)
				{
					g.DrawImage(
						mark.Image,
						mark.Location.X,
						mark.Location.Y,
						mark.Width,
						mark.Height);
				}
			}
			
			try 
			{
				if (System.IO.File.Exists(path))
				{
					System.IO.File.Delete(path);
				}
				tempBMP.Save(path, System.Drawing.Imaging.ImageFormat.Bmp);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}
		}
	}
}
