/*
 *  TLV - Trace Log Visualizer
 *
 *  Copyright (C) 2008-2010 by Nagoya Univ., JAPAN
 *
 *  上記著作権者は，以下の(1)〜(4)の条件を満たす場合に限り，本ソフトウェ
 *  ア（本ソフトウェアを改変したものを含む．以下同じ）を使用・複製・改
 *  変・再配布（以下，利用と呼ぶ）することを無償で許諾する．
 *  (1) 本ソフトウェアをソースコードの形で利用する場合には，上記の著作
 *      権表示，この利用条件および下記の無保証規定が，そのままの形でソー
 *      スコード中に含まれていること．
 *  (2) 本ソフトウェアを，ライブラリ形式など，他のソフトウェア開発に使
 *      用できる形で再配布する場合には，再配布に伴うドキュメント（利用
 *      者マニュアルなど）に，上記の著作権表示，この利用条件および下記
 *      の無保証規定を掲載すること．
 *  (3) 本ソフトウェアを，機器に組み込むなど，他のソフトウェア開発に使
 *      用できない形で再配布する場合には，次のいずれかの条件を満たすこ
 *      と．
 *    (a) 再配布に伴うドキュメント（利用者マニュアルなど）に，上記の著
 *        作権表示，この利用条件および下記の無保証規定を掲載すること．
 *    (b) 再配布の形態を，別に定める方法によって，TOPPERSプロジェクトに
 *        報告すること．
 *  (4) 本ソフトウェアの利用により直接的または間接的に生じるいかなる損
 *      害からも，上記著作権者およびTOPPERSプロジェクトを免責すること．
 *      また，本ソフトウェアのユーザまたはエンドユーザからのいかなる理
 *      由に基づく請求からも，上記著作権者およびTOPPERSプロジェクトを
 *      免責すること．
 *
 *  本ソフトウェアは，無保証で提供されているものである．上記著作権者お
 *  よびTOPPERSプロジェクトは，本ソフトウェアに関して，特定の使用目的
 *  に対する適合性も含めて，いかなる保証も行わない．また，本ソフトウェ
 *  アの利用により直接的または間接的に生じたいかなる損害に関しても，そ
 *  の責任を負わない．
 *
 *  @(#) $Id$
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Reflection;
using NU.OJL.MPRTOS.TLV.Base;
using System.Text.RegularExpressions;

namespace NU.OJL.MPRTOS.TLV.Core
{
	public class Shape : IHavingNullableProperty, ICloneable
	{
		private static Dictionary<string, Shape> _cache = new Dictionary<string, Shape>();

		private SolidBrush _brush = null;

		private Json _metaData;
		private List<string> _argPropList = new List<string>();
		private Color? _fill;

		public static Shape Default
		{
			get
			{
				Shape shape = new Shape();
				shape.Pen = new Pen();
				shape.Pen.Width = 1.0f;
				shape.Pen.Color = Color.Black;
				shape.Pen.DashCap = DashCap.Flat;
				shape.Pen.DashStyle = DashStyle.Solid;
				shape.Pen.Alpha = 255;
				shape.Pen.DashPattern = new float[] { 1.0f, 1.0f };
				shape.Font = new Font();
				shape.Font.Color = Color.Black;
				shape.Font.Family = FontFamily.GenericSansSerif;
				shape.Font.Size = 8f;
				shape.Font.Style = FontStyle.Regular;
				shape.Font.Align = ContentAlignment.MiddleCenter;
				shape.Type = ShapeType.Undefined;
				shape.Arc = new Arc(0.0f, 90.0f);
				shape.Area = new Area("0,0", "100%,100%");
				shape.Location = new Point("0,0");
				shape.Size = new Size("100%,100%");
				shape.Points = new PointList();
				shape.Points.Add(new Point("0,0"));
				shape.Points.Add(new Point("100%,0"));
				shape.Points.Add(new Point("100%,100%"));
				shape.Points.Add(new Point("0,100%"));
				shape.Fill = Color.White;
				shape.Offset = new Size("0,0");
				shape.Text = string.Empty;
				shape.Alpha = 255;
				return shape;
			}
		}

		private ShapeType? _Type;
		private string _Text;
		private PointList _Points;
		private Size _Offset;
		private Point _Location;
		private Size _Size;
		private Arc _Arc;
		private Pen _Pen;
		private Font _Font;
		private Area _Area;
		private int? _Alpha;

		public ShapeType? Type { get { return _Type; } set { _Type = value; } }
		public string Text { get { return _Text; } set { _Text = value; } }
		public PointList Points { get { return _Points; } set { _Points = value; } }
		public Size Offset { get { return _Offset; } set { _Offset = value; } }
		public Point Location { get { return _Location; } set { _Location = value; } }
		public Size Size { get { return _Size; } set { _Size = value; } }
		public Arc Arc { get { return _Arc; } set { _Arc = value; } }
		public Pen Pen { get { return _Pen; } set { _Pen = value; } }
		public Font Font { get { return _Font; } set { _Font = value; } }
		public Color? Fill
		{
			get { return _fill; }
			set
			{
				_fill = (Alpha.HasValue && value.HasValue && value.Value.A == 0) ? Color.FromArgb(Alpha.Value, value.Value) : value;
			}
		}
		public Area Area { get { return _Area; } set { _Area = value; } }
		public int? Alpha { get { return _Alpha; } set { _Alpha = value; } }

		public Json MetaData
		{
			get { return _metaData; }
			set
			{
				_metaData = value;

				if (_metaData == null)
					return;

				PropertyInfo[] pis = typeof(Shape).GetProperties();

				foreach (KeyValuePair<string, Json> kvp in _metaData.GetKeyValuePairEnumerator())
				{
					PropertyInfo pi;

					try
					{
						pi = Single<PropertyInfo>(pis, delegate(PropertyInfo p) { return p.Name == kvp.Key; });
					}
					catch
					{
						continue;
					}
					if (!JsonExtension.ToJsonString(kvp.Value).Contains("ARG"))
					{
						object obj;
						lock (ApplicationFactory.JsonSerializer)
						{
							obj = ApplicationFactory.JsonSerializer.Deserialize(JsonExtension.ToJsonString(kvp.Value), pi.PropertyType);
						}
						pi.SetValue(this, obj, null);
					}
					else
					{
						_argPropList.Add(kvp.Key);
					}
				}
			}
		}

		/// <summary>
		/// Shapeに引数の値を代入する。
		/// リフレクションを用いており低速なため、キャッシュを利用している
		/// </summary>
		/// <param name="args">引数</param>
		public void SetArgs(params string[] args)
		{
			string k = StringArrayExtension.ToCSVString(args) + JsonExtension.ToJsonString(_metaData);

			if (_cache.ContainsKey(k))
			{
				_metaData = _cache[k]._metaData;
				_argPropList = _cache[k]._argPropList;
				_brush = _cache[k]._brush;
				_fill = _cache[k]._fill;
				Type = _cache[k].Type;
				Text = _cache[k].Text;
				Points = _cache[k].Points;
				Offset = _cache[k].Offset;
				Location = _cache[k].Location;
				Size = _cache[k].Size;
				Arc = _cache[k].Arc;
				Pen = _cache[k].Pen;
				Font = _cache[k].Font;
				Fill = _cache[k].Fill;
				Area = _cache[k].Area;
				Alpha = _cache[k].Alpha;
				return;
			}
			else
			{
				foreach (string str in _argPropList)
				{
					PropertyInfo pi = Single<PropertyInfo>(typeof(Shape).GetProperties(), delegate(PropertyInfo p) { return p.Name == str; });
					string value = JsonExtension.ToJsonString(MetaData[str]);
					foreach (Match m in Regex.Matches(value, @"\${ARG(?<id>\d+)}"))
					{
						int i = int.Parse(m.Groups["id"].Value);
						if (args != null && args.Length > i)
							value = value.Replace(m.Value, args[i]);
					}
					lock (ApplicationFactory.JsonSerializer)
					{
						pi.SetValue(this, ApplicationFactory.JsonSerializer.Deserialize(value, pi.PropertyType), null);
					}
				}

				lock (_cache)
				{
					if (!_cache.ContainsKey(k))
						_cache.Add(k, this);
				}
			}
		}

		public Shape()
		{
		}

		public void Draw(Graphics graphics, RectangleF rect)
		{

			RectangleF area = (Area != null) ? Area.ToRectangleF(Offset, rect) : RectangleF.Empty;
			PointF[] points = (Points != null) ? Points.ToPointF(Offset, rect) : null;

			//Rectangle area = new Rectangle((int)areaF.X, (int)areaF.Y, (int)areaF.Width, (int)areaF.Height);

			if (_brush == null)
				setBrush();

			area.Intersect(new RectangleF(
				(graphics.ClipBounds.X - graphics.ClipBounds.Width),
				(graphics.ClipBounds.Y - graphics.ClipBounds.Height),
				(graphics.ClipBounds.Width * 3),
				(graphics.ClipBounds.Height * 3)
				));

			switch (Type)
			{
				case ShapeType.Line:
					graphics.DrawLine(Pen, points[0], points[1]);
					break;

				case ShapeType.Arrow:
					SmoothingMode s = graphics.SmoothingMode;
					graphics.SmoothingMode = SmoothingMode.AntiAlias;
					graphics.DrawLine(Pen, points[0], points[1]);
					graphics.SmoothingMode = s;
					break;

				case ShapeType.Rectangle:
					if (Fill.HasValue)
						graphics.FillRectangle(_brush, area);
					if (Pen != null)
						graphics.DrawRectangle(Pen, area.X, area.Y, area.Width, area.Height);
					break;

				case ShapeType.Ellipse:
					if (Fill.HasValue)
						graphics.FillEllipse(_brush, area);
					if (Pen != null)
						graphics.DrawEllipse(Pen, area);
					break;

				case ShapeType.Pie:
					if (Fill.HasValue)
						graphics.FillPie(_brush, area.X, area.Y, area.Width, area.Height, Arc.Start, Arc.Sweep);
					if (Pen != null)
						graphics.DrawPie(Pen, area, Arc.Start, Arc.Sweep);
					break;

				case ShapeType.Polygon:
					if (Fill.HasValue)
						graphics.FillPolygon(_brush, points);
					if (Pen != null)
						graphics.DrawPolygon(Pen, points);
					break;

				case ShapeType.Text:
					RectangleF a = new RectangleF(area.X + 1, area.Y + 1, area.Width - 2, area.Height - 2);
					SizeF size = graphics.MeasureString(Text, Font);

					float h = size.Height - a.Height;

					if (h > 0)
					{
						a = new RectangleF(a.X, a.Y - h, a.Width, a.Height + h);
					}

					graphics.DrawString(Text, Font, _brush, a, Font.GetStringFormat());
					break;
			}
		}

		public void ChackValidate()
		{
			switch (Type)
			{
				case ShapeType.Line:
					if (Pen == null)
						throw new Exception("Lineの描画にはPenの指定が必要です。");
					if (Points == null)
						throw new Exception("Lineの描画にはCoordinatesの指定が必要です。");
					break;
				case ShapeType.Arrow:
					if (Pen == null)
						throw new Exception("Arrowの描画にはPenの指定が必要です。");
					if (Points == null)
						throw new Exception("Arrowの描画にはCoordinatesの指定が必要です。");
					break;
				case ShapeType.Rectangle:
					if (Area == null && (Location == null && Size == null))
						throw new Exception("Rectangleの描画にはAreaまたはPointとSizeの指定が必要です。");
					if (Fill == null && Pen == null)
						throw new Exception("Rectangleの描画にはFillまたはPenの指定が必要です。");
					break;
				case ShapeType.Ellipse:
					if (Area == null && (Location == null && Size == null))
						throw new Exception("Ellipseの描画にはAreaまたはPointとSizeの指定が必要です。");
					if (Fill == null && Pen == null)
						throw new Exception("Ellipseの描画にはFillまたはPenの指定が必要です。");
					break;
				case ShapeType.Pie:
					if (Arc == null)
						throw new Exception("Pieの描画にはArcの指定が必要です。");
					if (Area == null && (Location == null && Size == null))
						throw new Exception("Pieの描画にはAreaまたはPointとSizeの指定が必要です。");
					if (Fill == null && Pen == null)
						throw new Exception("Pieの描画にはFillまたはPenの指定が必要です。");
					break;
				case ShapeType.Polygon:
					if (Area == null && (Location == null && Size == null))
						throw new Exception("Polygonの描画にはCoordinatesの指定が必要です。");
					if (Fill == null && Pen == null)
						throw new Exception("Polygonの描画にはFillまたはPenの指定が必要です。");
					break;
				case ShapeType.Text:
					if (Area == null && (Location == null && Size == null))
						throw new Exception("Textの描画にはAreaまたはPointとSizeの指定が必要です。");
					break;
				default:
					throw new Exception("未知の図形でず。");
			}
		}

		public void SetDefaultValue()
		{
			if (Area == null)
			{
				if (Location != null && Size != null)
					Area = new Area(Location, Size);
				else if (Location != null && Size == null)
					Area = new Area(Location, Default.Size);
				else if (Location == null && Size != null)
					Area = new Area(Default.Location, Size);
				else
					Area = Default.Area;
			}

			if (Points == null)
				Points = Default.Points;

			if (Offset == null)
				Offset = Default.Offset;

			if (Type == ShapeType.Arrow)
			{
				System.Drawing.Pen p = Pen.GetPen();
				lock (p)
				{
					p.EndCap = LineCap.Custom;
					p.CustomEndCap = new AdjustableArrowCap(p.Width == 1 ? 2 : p.Width, p.Width == 1 ? 2 : p.Width);
					Pen.SetPen(p);
				}
			}

			if (Type == ShapeType.Text)
			{
				if (Text == null)
					Text = Default.Text;

				if (Font == null)
					Font = Default.Font;

				if (!Font.Color.HasValue)
					Font.Color = Default.Font.Color.Value;
			}

			setBrush();
		}

		private void setBrush()
		{
			switch (Type)
			{
				case ShapeType.Ellipse:
				case ShapeType.Pie:
				case ShapeType.Polygon:
				case ShapeType.Rectangle:
					Color c = Fill.Value;
					if (Alpha.HasValue)
						c = Color.FromArgb(Alpha.Value, c);
					_brush = new SolidBrush(c);
					break;

				case ShapeType.Text:
					_brush = new SolidBrush(Font.Color.Value);
					break;
			}
		}

		public object Clone()
		{
			Shape sp = new Shape();

			sp._metaData = _metaData;
			sp._argPropList = _argPropList;
			sp._brush = _brush;
			sp._fill = _fill;

			sp.Type = Type;
			sp.Text = Text;
			sp.Points = Points;
			sp.Offset = Offset;
			sp.Location = Location;
			sp.Size = Size;
			sp.Arc = Arc;
			sp.Pen = Pen;
			sp.Font = Font;
			sp.Fill = Fill;
			sp.Area = Area;
			sp.Alpha = Alpha;

			return sp;
		}

		private static T Single<T>(IEnumerable<T> objs, Func<T, bool> p)
		{
			bool exsist = false;
			T result = default(T);
			foreach (T obj in objs)
			{
				if (p(obj))
				{
					if (exsist)
						throw new InvalidOperationException();
					exsist = true;
					result = obj;
				}
			}
			return result;
		}
	}
}
