using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace pcd_g1
{
	public enum CollideDirection
	{
		Up = 1, Down = 2, Left = 4, Right = 8,
		UpLeft = 5, UpRight = 9, DownLeft = 6, DownRight = 10,
		NoCollided = 0
	}

	public enum StageEventType
	{
		Clear = 1, Scroll = 2
	}

	abstract class Stage
	{
		protected List<IStageObject> stageObjects;

		public class collidedRecord
		{
			/// <summary>衝突したオブジェクト</summary>
			public IStageObject From;

			/// <summary>衝突されたオブジェクト</summary>
			public IStageObject To;

			/// <summary>衝突した方向</summary>
			public CollideDirection Direction;

			/// <summary>ぶつかった相手がSolidObjectでない</summary>
			public bool IsMounted;
		}

		public bool IsLake{get;protected set;}

		public Stage()
		{
			stageObjects = new List<IStageObject>();
			Command = new StageCommand();
			Command.OnClearStageCommandCalled += new ClearStageEventHandler(StageCommand_Clear);
			IsLake = false;
		}

		public CharacterStageObject MainCharacter { get; protected set; }

		List<IStageObject> addList = new List<IStageObject>();
		public void Add(IStageObject item)
		{
			if (removeList.Contains(item))
			{
				removeList.Remove(item);
			}else
			{
				addList.Add(item);
			}
		}

		public bool Contains(IStageObject item)
		{
			return stageObjects.Contains(item);
		}

		public int Count
		{
			get
			{
				return stageObjects.Count;
			}
		}

		List<IStageObject> removeList = new List<IStageObject>();
		public void Remove(IStageObject item)
		{
			removeList.Add(item);
		}

		public IStageObject[] Items
		{
			get
			{
				return stageObjects.ToArray();
			}
		}

		public IStageObject Item(int index)
		{
			return stageObjects[index];
		}

		public virtual void Update(int keyStatus)
		{
			Update(keyStatus, null);
		}

		public virtual void Update(int keyStatus, List<collidedRecord> recs, int deathLine = 3000)
		{
			var records = new List<collidedRecord>();

			if (recs == null)
			{
				//全項目のUpdate
				int vx, vy;
				foreach (var item in this.stageObjects)
				{
					item.Update(keyStatus, out vx, out vy);
					records.AddRange(
						move(item, vx, vy));
					if (item.Y > deathLine)
					{
						item.SignalProc(SignalType.SIGKILL, 0, 0);
					}
				}
			}
			else
			{
				records.AddRange(recs);
			}

			//イベントの通知
			foreach (var item in records)
			{
				bool c = item.Direction != CollideDirection.NoCollided;
				if (item.From.EventProc(c ? EventType.Collided : EventType.Touched, item.To, item.Direction))
				{
					item.To.EventProc(c ? EventType.BeCollided : EventType.BeTouched, item.From, item.Direction);
				}
			}

			foreach(var item in addList.ToArray())
			{
				if (stageObjects.Count > 0 &&
					stageObjects[stageObjects.Count - 1] is MainCharacterStageObject &&
					!(item is MainCharacterStageObject))
				{
					stageObjects.Insert(stageObjects.Count - 1, item);
				}
				else
				{
					stageObjects.Add(item);
				}
			}
			removeList.ForEach((j) => stageObjects.Remove(j));
			addList.Clear();
			removeList.Clear();
		}

		public virtual void Draw(Graphics g, int width, int height)
		{
			foreach (IStageObject item in Items)
			{
				g.ResetTransform();
				g.TranslateTransform((float)item.X, (float)item.Y);
				item.Draw(g, item.Width, item.Height);
			}
		}

		bool cleared = false;
		public void StageCommand_Clear()
		{
			if (!cleared)
			{
				StageManager.RunStage(typeof(startStage));
				cleared = true;
			}
		}

		public StageCommand Command { get; private set; }

		public bool IsOn(IStageObject obj, CollideDirection d)
		{
			IEnumerable<collidedRecord> items;
			int x = obj.X, y = obj.Y;
			if ((d & CollideDirection.Up) != 0) y--;
			if ((d & CollideDirection.Down) != 0) y++;
			if ((d & CollideDirection.Left) != 0) x--;
			if ((d & CollideDirection.Right) != 0) x++;
			return isCollided(obj, x, y, out items);
		}

		public bool IsOnObject(IStageObject obj, CollideDirection d, IStageObject block)
		{
			IEnumerable<collidedRecord> items;
			int x = obj.X, y = obj.Y;
			if ((d & CollideDirection.Up) != 0) y--;
			if ((d & CollideDirection.Down) != 0) y++;
			if ((d & CollideDirection.Left) != 0) x--;
			if ((d & CollideDirection.Right) != 0) x++;
			items = getCollidedObjects(obj, x, y);
			var collided = items.Where((p) => !p.IsMounted);
			foreach (var xx in collided)
			{
				if (xx.To == block) return true;
			}
			return false;

		}

		protected IEnumerable<collidedRecord> move(IStageObject obj, int vx, int vy)
		{
			CollideDirection d = CollideDirection.NoCollided;
			IEnumerable<collidedRecord> tmpRecLst = null, tmpRecLst2 = null, tmpRecLst3=null;

			var records = new List<collidedRecord>();
			int i, n, avx = Math.Abs(vx), avy = Math.Abs(vy);
			double ix, iy, sx, sy;
			bool xReached = false, yReached = false;

			if (vx == 0 && vy == 0) return new collidedRecord[] { };

			n = Math.Max(avx, avy);
			ix = (double)vx / (double)n; iy = (double)vy / (double)n;
			sx = obj.X; sy = obj.Y;

			double slopeUp = (int) Math.Abs(ix) * 1 + 2;

			for (i = 0; i < n && !(xReached && yReached); i++)
			{
				if (!xReached && ix != 0.0)
				{
					sx += ix;
					if (isCollided(obj, sx, sy, out tmpRecLst))
					{
						if (obj is MainCharacterStageObject)
						{
							sy -= slopeUp;
							if (isCollided(obj, sx, sy, out tmpRecLst2))
							{
                                sy -= slopeUp;
                                if (isCollided(obj, sx, sy, out tmpRecLst3))
                                {
                                    sy += slopeUp * 2;
                                    xReached = true;
                                    sx = Math.Ceiling(sx - ix);
                                }
                                tmpRecLst2 = tmpRecLst3;
							}
							tmpRecLst = tmpRecLst2;
						}
						else
						{
							xReached = true;
							sx = Math.Ceiling(sx - ix);
						}
						d |= ix > 0 ? CollideDirection.Right : CollideDirection.Left;
					}
					records.AddRange(tmpRecLst);
				}

				if (!yReached && iy != 0.0)
				{
					sy += iy;
					if (isCollided(obj, sx, sy, out tmpRecLst))
					{
						sy = Math.Ceiling(sy - iy);
						yReached = true;
						d |= iy > 0 ? CollideDirection.Down :  CollideDirection.Up;
					}
					records.AddRange(tmpRecLst);
				}
			}

			records.ForEach((r) => { r.From = obj; r.Direction = d; });

			obj.X = (int)Math.Floor(sx);
			obj.Y = (int)Math.Floor(sy);

			return records.ToArray();
		}

		private bool isCollided(IStageObject obj, double x, double y, out IEnumerable<collidedRecord> items)
		{
			items = getCollidedObjects(obj, x, y);
			var collided = items.Where((p) => !p.IsMounted);
			return collided.Count() > 0;
		}

		private bool isSolidObject(IStageObject item)
		{
			return item.CanCrash;
		}

		//CollidedRecordについてはToのみがセットされる。
		private IEnumerable<collidedRecord> getCollidedObjects(IStageObject obj, double x, double y)
		{
			foreach (IStageObject item in this.stageObjects)
			{
				if (item != obj)
				{
					if (obj.Range.InCollideField(x, y, item.Range))
					{
						if (obj.Range.Judge(x, y, item.Range))
						{
							yield return new collidedRecord()
							{
								To = item,
								IsMounted = !isSolidObject(item)
							};
						}
					}
				}
			}
			yield break;
		}

		public IEnumerable<IStageObject> GetCollidedStageObjects(IStageObject obj)
		{
			foreach (var item in getCollidedObjects(obj, obj.X, obj.Y))
			{
				yield return item.To;
			}
			yield break;
		}
	}
}
