using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Web.UI.DataVisualization.Charting;
using System.Collections;

namespace System.Web.UI.DataVisualization.Charting.Utilities
{
	/// <summary>
	/// .
	/// </summary>
	public class ChartDataTableHelper
	{
		#region Members
		protected System.Web.UI.DataVisualization.Charting.Chart ChartObj = null;
		protected ArrayList ChartAreas = null;
		protected bool AddTableTotals = false;
		protected System.Drawing.Color tableColor = Color.White;
		protected System.Drawing.Color borderColor = Color.Black;
		protected bool enabled = true;
		protected bool Initialized = false;

		#endregion

		#region Properties

		/// <summary>
		/// Enables or Disables the painting of the Data Table.
		/// </summary>
		public bool Enabled
		{
			get
			{
				return Enabled;
			}
			set
			{
				enabled = value;
			}
		}

		/// <summary>
		/// Sets or gets the Chart object.
		/// </summary>
		public System.Web.UI.DataVisualization.Charting.Chart Chart
		{
			get
			{
				return ChartObj;
			}
			set
			{
				ChartObj = value;
			}
		}


		/// <summary>
		/// Sets or gets the Table Color that will be painted.
		/// </summary>
		public System.Drawing.Color TableColor
		{
			get
			{
				return tableColor;
			}
			set
			{
				tableColor = value;
			}
		}

		/// <summary>
		/// Sets or gets the Table Border Color that will be painted.
		/// </summary>
		public System.Drawing.Color BorderColor
		{
			get
			{
				return borderColor;
			}
			set
			{
				borderColor = value;
			}
		}

		#endregion

		#region Constructors
		/// <summary>
		/// Construct a ChartDataTableHelper instance.
		/// </summary>
		public ChartDataTableHelper()
		{
			ChartObj = null;
			ChartAreas = new ArrayList();
		}

		/// <summary>
		/// Construct a ChartDataTableHelper instance and Initialize all ChartAreas with a table.
		/// </summary>
		public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj);
		}


		/// <summary>
		/// Construct a ChartDataTableHelper instance and Initialize the specified ChartArea with a table.
		/// </summary>
		public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj, chartAreaName);

		}

		/// <summary>
		/// Construct a ChartDataTableHelper instance, Initialize the specified ChartArea with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public ChartDataTableHelper(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
		{
			ChartAreas = new ArrayList();
			Initialize(chartObj, chartAreaName, addTableTotals);

		}

		#endregion 

		#region Initialization Methods
		/// <summary>
		/// Initialize all ChartAreas with a table.
		/// </summary>
		public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj)
		{
			ChartObj = chartObj;

			foreach(ChartArea area in ChartObj.ChartAreas)
			{
				AddDataTable(area.Name);
			}

			if(!Initialized)
				ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
		
			Initialized = true;
		}



		/// <summary>
		/// Initialize all ChartAreas with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, bool addTableTotals)
		{
			AddTableTotals = addTableTotals;
			Initialize(chartObj);
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table.
		/// </summary>
		public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName)
		{
			ChartObj = chartObj;
			
			AddDataTable(chartAreaName);

			if(!Initialized)
				ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint);
		
			Initialized = true;
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table and
		/// set a boolean to show or hide total columns.
		/// </summary>
		public void Initialize(System.Web.UI.DataVisualization.Charting.Chart chartObj, string chartAreaName, bool addTableTotals)
		{
			ChartObj = chartObj;
			AddTableTotals = addTableTotals;
			
			AddDataTable(chartAreaName);

			if(!Initialized)
				ChartObj.PostPaint +=new EventHandler<ChartPaintEventArgs>(this.Chart_PostPaint); 
		
			Initialized = true;
		}

		/// <summary>
		/// Initialize the specified ChartArea with a table.
		/// </summary>
		public void AddDataTable(string chartAreaName)
		{
			if(ChartObj == null || ChartAreas.IndexOf(chartAreaName) >= 0)
				return;

			// add this chart area to the list of chart areas that need to 
			// have a data table attached
			ChartAreas.Add(chartAreaName);

			int Row = 0;


			if(AddTableTotals)
			{
				// create a dummy series that will not be shown but is used for 
				// showing the totals in the data table
				ChartObj.Series.Add("DUMMY");
				ChartObj.Series["DUMMY"].ChartArea = chartAreaName;
				ChartObj.Series["DUMMY"].Enabled = false;
				ChartObj.Series["DUMMY"].Color = Color.Gainsboro;
			}


			// for each of the series that are attached to this 
			// named chart area, create a custom axis label.
			// All tables lines will be drawn on a paint event.
			// ****************************************************
			// NOTE: ALL SERIES MUST HAVE THE SAME NUMBER OF POINTS!
			// ****************************************************
			foreach(Series ser in ChartObj.Series)
			{
				if(chartAreaName == ser.ChartArea)
				{
					if(AddTableTotals)
					{
						// shadows must be turned off otherwise
						// they will still show up for the transparent points
						ser.ShadowOffset = 0;

						// adjust the series values to ensure they are not
						// indexed and they are sorted... plus adding each point
						// to make the dummy series data 
						AdjustXValues(ser);
					}


					Row++;
					double From = 0.0;
					double To = 0.0;
					bool firstPoint = true;

					double YValueTotal = 0;

					foreach(DataPoint dp in ser.Points)
					{
						if(AddTableTotals)
							YValueTotal += dp.YValues[0];

						if(firstPoint && dp.XValue == 0)
							From = 0.5;
						else if(firstPoint)
							From = dp.XValue - 0.5;

						if(firstPoint)
						{
							ChartObj.ChartAreas[chartAreaName].AxisX.Minimum = From;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.Interval = 1;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorGrid.IntervalOffset = 0.5;
							ChartObj.ChartAreas[chartAreaName].AxisX.MajorTickMark.IntervalOffset = 0.5;
							ChartObj.ChartAreas[chartAreaName].AxisX.LabelStyle.IntervalOffset = 0.5;
						}

						To = From + 1;

						ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Add(
							From, To, 
							" ",     // space used as a placeholder
                            Row, LabelMarkStyle.None, GridTickTypes.None
							);

						firstPoint = false;
						From += 1;
					}

					if(AddTableTotals)
						ser.Points[ser.Points.Count-1].YValues[0] = YValueTotal;
					
					ChartObj.ChartAreas[chartAreaName].AxisX.Maximum = To;

				}
			}

			if(AddTableTotals)
				AdjustYMaximum(chartAreaName);

		}

		/// <summary>
		/// With the addition of Totals, the chart will try to set the maximum
		/// values according to these totals.  This will cause some series to be
		/// barely visible.  Since the points are transparent this is a poor behavior.
		/// This method will find and explicitly set the YAxis maximum to something
		/// a little more with the user expectations.
		/// </summary>
		private void AdjustYMaximum(string chartAreaName)
		{
			double MaxYValue = 0;

			// find the max YValue from all points in all series
			foreach(Series ser in ChartObj.Series)
			{
				if(chartAreaName == ser.ChartArea && ser.Enabled)
				{
					// check agains all points except the last point
					// which is the totals column
					for(int index = 0; index < ser.Points.Count-1; index++)
					{
						DataPoint pt = ser.Points[index];
						if(pt.YValues[0] > MaxYValue)
							MaxYValue = pt.YValues[0];
					}		
				}
			}

			double LogValue = (int)(Math.Log10(MaxYValue)) + 1;
			double NewMaxYValue = Math.Pow(10, LogValue);
			double ratio = MaxYValue / NewMaxYValue;
			double divisor = 1;

			if(ratio <= 0.1)
				divisor = 10;
			else if(ratio < 0.2)
				divisor = 5;
			else if(ratio < 0.25)
				divisor = 4;
			else if(ratio < 0.4)
				divisor = 2.5;
			else if(ratio < 0.5)
				divisor = 2;
			else if(ratio < 0.8)
				divisor = 1.25;

			ChartObj.ChartAreas[chartAreaName].AxisY.Maximum = NewMaxYValue / divisor;
			ChartObj.ChartAreas[chartAreaName].AxisY.RoundAxisValues();

		}


		/// <summary>
		/// A cleanup method that ensures the XValues are sorted accordingly and set explicitly.  
		/// It will also create the totals for the DUMMY series.
		/// </summary>
		private void AdjustXValues(System.Web.UI.DataVisualization.Charting.Series series)
		{
			bool AddDummyPoints = true;

			if(series.Name == "DUMMY")
				return;
			else if(ChartObj.Series["DUMMY"].Points.Count > 0)
				AddDummyPoints = false;

			// sort the series
			series.Sort(PointSortOrder.Ascending, "X");

			bool IsIndexed = false;

			if(series.IsXValueIndexed)
				IsIndexed = true;
			else
			{
				bool IsFirstPoint = true;
				bool IsLastPointZero = false;

				// the series X values must be set and greater than zero
				foreach(DataPoint pt in series.Points)
				{
					if(pt.XValue == 0 && !IsFirstPoint && IsLastPointZero)
					{
						IsIndexed = true;
						break;
					}
					else if (pt.XValue == 0 && IsFirstPoint)
						IsLastPointZero = true;

					IsFirstPoint = false;
				}
			}

			if(IsIndexed)
			{
				series.IsXValueIndexed = false;
				int XValue = 0;
				
				foreach(DataPoint pt in series.Points)
					pt.XValue = ++XValue;
			}

			series.Points.AddXY(series.Points[series.Points.Count-1].XValue + 1, 0);
			series.Points[series.Points.Count-1].AxisLabel = "Total";
			series.Points[series.Points.Count-1].Color = Color.Transparent;
			series.Points[series.Points.Count-1].BorderColor = Color.Transparent;

			int index = 0;
			foreach(DataPoint pt in series.Points)
			{
				if(AddDummyPoints)
				{
					ChartObj.Series["DUMMY"].Points.AddXY(pt.XValue, pt.YValues[0]);
				}
				else
				{
					ChartObj.Series["DUMMY"].Points[index].YValues[0] += pt.YValues[0];
				}

				index++;
			}
		}


		#endregion 

		#region Remove Table

		public void RemoveDataTable(string chartAreaName)
		{
            if (ChartObj.ChartAreas.IndexOf(chartAreaName) >= 0)
				ChartObj.ChartAreas[chartAreaName].AxisX.CustomLabels.Clear();

			if(ChartAreas.IndexOf(chartAreaName) >= 0)
			{
				ChartAreas.RemoveAt(ChartAreas.IndexOf(chartAreaName));
			}
		}

		#endregion

		#region Paint Event Handling

		/// <summary>
		/// Chart Paint event handler.
		/// </summary>
		private void Chart_PostPaint(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
		{
			if( e.ChartElement is ChartArea )
			{
                ChartArea area = (ChartArea)e.ChartElement;
				// call the paint method.
				if(ChartAreas.IndexOf(area.Name) >= 0 && enabled)
				{
					PaintDataTable(sender, e);
				}
			}
		
		}	

		/// <summary>
		/// This method does all the work for the painting of the data table.
		/// </summary>
		private void PaintDataTable(object sender, System.Web.UI.DataVisualization.Charting.ChartPaintEventArgs e)
		{
            ChartArea area = (ChartArea)e.ChartElement;

			// get the rect of the chart area
			RectangleF rect = e.ChartGraphics.GetAbsoluteRectangle( area.Position.ToRectangleF() );

			// get the inner plot position
			ElementPosition elemPos = area.InnerPlotPosition;
			
			// find the coordinates of the inner plot position
			float x = rect.X + (rect.Width / 100 * elemPos.X);
			float y = rect.Y + (rect.Height / 100 * elemPos.Y);
			float ChartAreaBottomY = rect.Y + rect.Height;
			
			float width = (rect.Width / 100 * elemPos.Width);
			float height = (rect.Height / 100 * elemPos.Height);

			// find the height of the font that will be used
			Font axisFont = area.AxisX.LabelStyle.Font;
			string testString = "ForFontHeight";
			SizeF axisFontSize = e.ChartGraphics.Graphics.MeasureString(testString, axisFont);

			// find the height of the font that will be used
			Font titleFont = area.AxisX.TitleFont;
			testString = area.AxisX.Title;
            SizeF titleFontSize = e.ChartGraphics.Graphics.MeasureString(testString, titleFont);

			int seriesCount = 0;

			// for each series that is attached to the chart area,
			// draw some boxes around the labels in the color provided
			for(int i = e.Chart.Series.Count-1; i >= 0; i--)
			{
				if(area.Name == e.Chart.Series[i].ChartArea)
				{
					seriesCount++;
				}
			}

			// now, if a box was actually drawn, then draw 
			// the verticle lines to separate the columns of the table.
			if(seriesCount > 0)
			{
				for(int i = 0; i < e.Chart.Series.Count; i++)
				{
					if(area.Name == e.Chart.Series[i].ChartArea)
					{
						double min = area.AxisX.Minimum;
						double max = area.AxisX.Maximum;

						// modify the min value for the current axis view
						if(area.AxisX.ScaleView.Position-1 > min)
							min = area.AxisX.ScaleView.Position-1;

						// modify the max value for the currect axis view
						if( (area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5) < max)
							max = area.AxisX.ScaleView.Position + area.AxisX.ScaleView.Size + 0.5;


						// find the starting point that will be display.
						// this is dependent on the current axis view.
						// this sample assumes the same number of points in each
						// series so always take from the zeroth series
						int pointIndex = 0;
						foreach(DataPoint pt in ChartObj.Series[0].Points)
						{
							if(pt.XValue > min)
								break;

							pointIndex++;
						}

						bool TableLegendDrawn = false;

						for(double AxisValue = min; AxisValue < max; AxisValue++)
						{
                            float pixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue);
                            float nextPixelX = (float)e.ChartGraphics.GetPositionFromAxis(area.Name, AxisName.X, AxisValue + 1);
							float pixelY = ChartAreaBottomY - titleFontSize.Height - (seriesCount * axisFontSize.Height);

							PointF point1 = PointF.Empty;
							PointF point2 = PointF.Empty;

							// Set Maximum and minimum points
							point1.X = pixelX;
							point1.Y = 0;

							// Convert relative coordinates to absolute coordinates.
							point1 = e.ChartGraphics.GetAbsolutePoint(point1);
							point2.X = point1.X;
							point2.Y = ChartAreaBottomY - titleFontSize.Height;
							point1.Y = pixelY;

							// Draw connection line
							e.ChartGraphics.Graphics.DrawLine(new Pen(borderColor), point1,point2);


							point2.X = nextPixelX;
							point2.Y = 0;
							point2 = e.ChartGraphics.GetAbsolutePoint(point2);

							StringFormat format = new StringFormat();
							format.Alignment = StringAlignment.Center;
							format.LineAlignment = StringAlignment.Center;

							// for each series draw one value in the column
							int row = 0;
							foreach(Series ser in ChartObj.Series)
							{
								if(area.Name == ser.ChartArea)
								{
									if(!TableLegendDrawn)
									{
										// draw the series color box 
										e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(ser.Color), 
											x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);

										e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor), 
											x-10, row*(axisFont.Height)+(point1.Y), 10, axisFontSize.Height);

										e.ChartGraphics.Graphics.FillRectangle(new SolidBrush(tableColor),
											x, 
											row*(axisFont.Height)+(point1.Y),
											width, 
											axisFontSize.Height); 

										e.ChartGraphics.Graphics.DrawRectangle(new Pen(borderColor), 
											x, 
											row*(axisFont.Height)+(point1.Y),
											width, 
											axisFontSize.Height); 

									}
									
									if(pointIndex < ser.Points.Count)
									{
										string label = ser.Points[pointIndex].YValues[0].ToString();
										RectangleF textRect = new RectangleF(point1.X, row*(axisFont.Height)+(point1.Y+1), point2.X-point1.X, axisFont.Height);
										e.ChartGraphics.Graphics.DrawString(label, axisFont, new SolidBrush(area.AxisX.LabelStyle.ForeColor), textRect, format);
									}

									row++;

								}
							}

							TableLegendDrawn = true;
						
							pointIndex++;
						}

						// do this only once so break!
						break;
					}
				}
			}
		}


		#endregion


	}
}