using System;
using System.Collections.Generic;
using System.Text;
using Box2DX.Collision;
using Box2DX.Common;
using Box2DX.Dynamics;

namespace Nlgp1.B2DX
{
	public abstract class RigidBody
	{
		//todo いくつかの作業に分けて動作させる
		//addShape(),setdef(fric,rest,dens),こうすることで後で属性を変更できる
		public string BodyKind = "";

		Body selfBody;
		public Body SelfBody
		{
			get
			{
				return selfBody;
			}
			private set
			{
				selfBody = value;
			}
		}
		float boxwidth;
		public float Boxwidth
		{
			get { return boxwidth; }
			internal set { boxwidth = value; }
		}
		float boxheight;
		public float Boxheight
		{
			get { return boxheight; }
			internal set { boxheight = value; }
		}
		public myUserData UserData;
		MassData massdata = new MassData();
		/// <summary>
		/// いまいち適当な数値が思い浮かばない
		/// </summary>
		protected const float inertia = 5;

		BodyDef Bodydef = new BodyDef();
		protected const float rest = 0.6f, dens = 0.1f, fric = 0.3f;
		protected const float angularDamping = 0.3f, lineardamping = 0.5f;
		Dictionary<ObjectDataKind, float> Objdata = new Dictionary<ObjectDataKind, float>();
		/// <summary>
		/// ボックスの再生成を許可するかのフラグ
		/// </summary>
		bool alregene;
		public bool Alregene
		{
			private get { return alregene; }
			set { alregene = value; }
		}
		List<ShapeDef> plist = new List<ShapeDef>();
		List<Shape> shapelist = new List<Shape>();
		public List<Shape> Shapelist
		{
			get { return shapelist; }
			private set { shapelist = value; }
		}
		/// <summary>
		/// ポリゴンのリストにpolygondefを加える
		/// </summary>
		/// <param name="pa"></param>
		public void Addtolist(params PolygonDef[] pa)
		{
			foreach (PolygonDef p in pa)
				plist.Add(p);
		}


		#region 新コンストラクタ
		//todo 新コンストラクタのクラス変数
		//public Box(Vec2 pos, float angle, World world, float boxwidth, float boxheight, bool noRotateFlag, Dictionary<UserDataKind, string> userdata,Dictionary<ObjectDataKind,float> data)
		public RigidBody(Vec2 pos, float angle)
		{
			Bodydef = new Bodydef2(pos, angle, lineardamping, angularDamping);
		}
		public RigidBody(Vec2 pos, float angle, float lind, float angd)
		{
			Bodydef = new Bodydef2(pos, angle, lind, angd);
			BodyKind += this.ToString().Split(".".ToCharArray())[2];
		}
		public void SetAsBox(float boxwidth, float boxheight)
		{
			plist.Add(makeshape(boxwidth, boxheight));
		}
		/// <summary>
		/// Boxの形や係数を宣言する
		/// </summary>
		/// <param name="boxwidth">Boxの幅</param>
		/// <param name="boxheight">Boxの高さ</param>
		/// <param name="data">Boxの係数</param>
		/// <param name="fil"></param>
		/// <param name="isSensor">ぶつからなくても当たり判定をとるかどうか、trueだとすりぬけてもAdd等が発生します</param>
		public void SetAsBox(float boxwidth, float boxheight, Polygondef2 data, bool isSensor)
		{
			Boxwidth = boxwidth; Boxheight = boxheight;
			
			#region
			/*
			Objdata[ObjectDataKind.Density] = dens;
			Objdata[ObjectDataKind.Friction] = fric;
			Objdata[ObjectDataKind.Restitution] = rest;
			foreach (int i in Enum.GetValues(typeof(ObjectDataKind)))
			{
				if (data.ContainsKey((ObjectDataKind)i)) Objdata[(ObjectDataKind)i] = data[(ObjectDataKind)i];
			}
			*/
			#endregion
			Polygondef2 p = data;
			p.SetAsBox(boxwidth, boxheight);
			#region
			/*
			p.Density = Objdata[ObjectDataKind.Density];
			p.Friction = Objdata[ObjectDataKind.Friction];
			p.Restitution = Objdata[ObjectDataKind.Restitution];
			*/
			#endregion
			p.IsSensor = isSensor;
			UserData = data.User;
			BodyKind += "Box";
			//UserDataに必要なものを追加する
			//UserData.filter = Bodydef.UserData;
			plist.Add(p);
		}
		public void SetAsCircle(CircleDef2 data, bool isSensor)
		{
			Boxwidth = data.Radius; Boxheight = data.Radius;
			//todo （SetAsBoxも）IsSensorを統合するべきかどうか
			data.IsSensor = isSensor;
			UserData = data.User;
			BodyKind += "Circle";
			plist.Add(data);

		}
		/// <summary>
		/// 旧コンストラクタの方
		/// </summary>
		/// <param name="userdata"></param>
		/// <returns></returns>
		public FilterData SetFilter(Dictionary<UserDataKind, string> userdata)
		{
			FilterData fd = new FilterData();
			fd.CategoryBits = Convert.ToUInt16("111",2);
			fd.MaskBits = Convert.ToUInt16("111",2);
			if (userdata.ContainsKey(UserDataKind.GroupIndex)) fd.GroupIndex = Convert.ToInt16(userdata[UserDataKind.GroupIndex],2);
			if (userdata.ContainsKey(UserDataKind.CategoryBits)) fd.CategoryBits = Convert.ToUInt16(userdata[UserDataKind.CategoryBits],2);
			if (userdata.ContainsKey(UserDataKind.MaskBits)) fd.MaskBits = Convert.ToUInt16(userdata[UserDataKind.MaskBits],2);
			return fd;

		}

		static PolygonDef makeshape(float boxwidth, float boxheight)
		{
			PolygonDef p = new PolygonDef();
			p.SetAsBox(boxwidth, boxheight);
			return p;

		}


		#endregion
		/*
		public void ApplyUserData(Dictionary<ObjectDataKind, float> data, FilterData fil)
		{
			if (data.ContainsKey(ObjectDataKind.Friction)) p.Friction = data[ObjectDataKind.Friction];
			if (data.ContainsKey(ObjectDataKind.Restitution)) p.Restitution = data[ObjectDataKind.Restitution];
			if (data.ContainsKey(ObjectDataKind.Density)) p.Density = data[ObjectDataKind.Density];
			p.Filter = fil;
		}
		*/

		/// <summary>
		/// 最後にWorldを渡して指定されたポリゴンを作る
		/// </summary>
		/// <param name="w"></param>
		public void MakeBody(World w)
		{
			SelfBody = w.CreateBody(Bodydef);
			plist.ForEach(delegate(ShapeDef pd)
			{
				SelfBody.CreateShape(pd);
			});
			if (!this.BodyKind.Equals("Static"))
			{
				SelfBody.SetMassFromShapes();
			}
			SelfBody.SetUserData(UserData);
			Shape s = SelfBody.GetShapeList();
			
			while (s != null)
			{
				Shapelist.Add(s);
				s=s.GetNext();
			}
			
		}

		/// <summary>
		/// 回転するかしないかを定義する、この後必ずMakeBodyを通すこと
		/// </summary>
		public bool NoRotate
		{
			get
			{
				return Bodydef.FixedRotation;
			}
			set
			{
				Bodydef.FixedRotation = value;
			}
		}
		#region 旧コンストラクタ処理
		/// <summary>
		/// 力を加えると動く物体の定義
		/// </summary>
		/// <param name="pos">場所</param>
		/// <param name="angle">角度</param>
		/// <param name="world">投入するWorld</param>
		/// <param name="boxwidth">判定の広さ</param>
		/// <param name="boxheight">判定の高さ</param>
		/// <param name="noRotateFlag">非回転フラグ</param>
		/// <param name="userdata">名前データと衝突判定</param>
		public RigidBody(Vec2 pos, float angle, World world, float boxwidth, float boxheight,
			bool noRotateFlag, Dictionary<UserDataKind, string> userdata,
			Dictionary<ObjectDataKind, float> data,
			bool allowregenerate
			)
		{
			alregene = allowregenerate;


			//ボディの仮想的な初期状態を定義する
			Bodydef.Angle = angle;
			Bodydef.Position = pos;
			Bodydef.FixedRotation = noRotateFlag;
			//ボディの回転減衰率
			//デフォルトの状態を保存しておき、dataによって変更する
			//todo 構造に不満があるんだけどどうしよ
			// 新しい属性を追加するとここに追加＞下のdefを調整
			Objdata[ObjectDataKind.Density] = dens;
			Objdata[ObjectDataKind.Friction] = fric;
			Objdata[ObjectDataKind.Restitution] = rest;
			Objdata[ObjectDataKind.AngularDamping] = angularDamping;
			Objdata[ObjectDataKind.LinearDamping] = lineardamping;
			foreach (int i in Enum.GetValues(typeof(ObjectDataKind)))
			{
				if (data.ContainsKey((ObjectDataKind)i)) Objdata[(ObjectDataKind)i] = data[(ObjectDataKind)i];
			}

			NoRotate = noRotateFlag;
			makepolygon(boxwidth, boxheight, world, userdata);
		}
		void makepolygon(float boxwidth, float boxheight, World world, Dictionary<UserDataKind, string> userdata)
		{
			//ポリゴンの形状を決定する

			//MassData mass = new MassData();
			//mass.Mass = 4;
			Boxheight = boxheight;
			Boxwidth = boxwidth;
			#region ユーザーデータ定義
			FilterData fil = new FilterData();

			UserData = new myUserData();
			if (userdata.ContainsKey(UserDataKind.Name)) UserData.name = userdata[UserDataKind.Name];
			if (userdata.ContainsKey(UserDataKind.Filter))
			{

				fil.CategoryBits = Convert.ToUInt16(userdata[UserDataKind.Filter],2);
				fil.MaskBits = fil.CategoryBits;

			}
			if (userdata.ContainsKey(UserDataKind.MaskBits))
			{
				fil.MaskBits = Convert.ToUInt16(userdata[UserDataKind.MaskBits],2);
			}
			if (userdata.ContainsKey(UserDataKind.CategoryBits))
			{
				fil.CategoryBits = Convert.ToUInt16(userdata[UserDataKind.CategoryBits],2);
			}
			if (userdata.ContainsKey(UserDataKind.GroupIndex))
			{
				fil.GroupIndex = Convert.ToInt16(userdata[UserDataKind.GroupIndex],2);
			}
			UserData.filter = fil;
			#endregion
			//Userdataや減衰度を物質の定義に渡している
			Bodydef.UserData = UserData;
			Bodydef.LinearDamping = Objdata[ObjectDataKind.LinearDamping];
			Bodydef.AngularDamping = Objdata[ObjectDataKind.AngularDamping];


			PolygonDef p;
			//p = def(rest, dens, fric, UserData.filter);
			p = def(Objdata, fil);
			p.SetAsBox(boxwidth, boxheight);
			plist.Add(p);

			if (!alregene) MakeBody(world);

		}

		/// <summary>
		/// ポリゴン定義
		/// </summary>
		/// <param name="rest">反発</param>
		/// <param name="dens">密度</param>
		/// <param name="fric">摩擦</param>
		/// <returns></returns>
		static PolygonDef def(float rest, float dens, float fric, FilterData fil)
		{
			PolygonDef p = new PolygonDef();
			p.Restitution = rest;
			p.Density = dens;
			p.Friction = fric;
			p.Filter = fil;

			return p;
		}
		static PolygonDef def(Dictionary<ObjectDataKind, float> objdata, FilterData fil)
		{
			PolygonDef p = new PolygonDef();
			p.Restitution = objdata[ObjectDataKind.Restitution];
			p.Density = objdata[ObjectDataKind.Density];
			p.Friction = objdata[ObjectDataKind.Friction];
			p.Filter = fil;

			return p;
		}
		#endregion

		#region Getプロパティ

		/// <summary>
		/// 重心を返します
		/// </summary>
		public Vec2 Center
		{
			get
			{
				return selfBody.GetWorldCenter();
			}
		}
		/// <summary>
		/// 座標を表します
		/// </summary>
		public Vec2 Position
		{
			get
			{
				return selfBody.GetPosition();
			}
			set
			{
				selfBody.SetXForm(value, Angle);
			}
		}
		/// <summary>
		/// 角度を表します
		/// </summary>
		public float Angle
		{
			get
			{
				return selfBody.GetAngle();
			}
			set
			{
				selfBody.SetXForm(Position, value);
			}
		}
		/// <summary>
		/// 加速度を表します
		/// </summary>
		public Vec2 LinearVelocity
		{
			get
			{
				return selfBody.GetLinearVelocity();
			}
			set
			{
				selfBody.SetLinearVelocity(value);
			}
		}
		/// <summary>
		/// 質量を得ます
		/// </summary>
		public float Mass
		{
			get
			{
				return selfBody.GetMass();
			}

		}
		/// <summary>
		/// 慣性を得ます
		/// </summary>
		public float Inertia
		{
			get
			{
				return selfBody.GetInertia();
			}
		}
		#endregion

		//RayCastで得られた情報
		public Vec2 normal;
		public float lambda;
		public Colors color = Colors.None;
		//イベント
		event EventHandler<RaycastEvent> raycast = new EventHandler<RaycastEvent>(Rayhit);

		/// <summary>
		/// toまでの位置に光を投げ、当たった物(最大5つ)の配列を返す
		/// </summary>
		/// <param name="from">自分の位置から見て離れた場所から光を出すこと、さもないと自分を検出する</param>
		/// <param name="to">自分から見て光を当てる位置</param>
		/// <returns></returns>
		public Shape[] Raycast(Vec2 from,Vec2 to)
		{
			Segment s;
			s.P1 = SelfBody.GetWorldPoint(from);
			s.P2 = SelfBody.GetWorldVector(to)+s.P1;
			if (color != Colors.None) Debug.Drawline(s, color);
			XForm thisx = selfBody.GetXForm();
			World w = SelfBody.GetWorld();
			Shape[] shapes = new Shape[5];
			w.Raycast(s, shapes, 5, true, null);
			if (shapes[0] != null)
			{
				EventHandler<RaycastEvent> temp = raycast;
				temp(this,new RaycastEvent(shapes));
			}
			/*
			ユーザーデータがほしいのなら
			 foreach (Shape tem in shapes) tem.GetBody().GetUserData();
			 */
			return shapes;
			
		}

		static void Rayhit(object sender, RaycastEvent ray)
		{
			foreach (Shape s in ray.Rayhit)
			{
				if (s != null) Debug.PrintContact(s.GetBody().GetPosition());
			}
		}

		/// <summary>
		/// フィルタデータを変更する
		/// </summary>
		/// <param name="f">フィルタデータ</param>
		/// <param name="idx">Shapelistのインデックス</param>
		public void SetFilter(FilterData f, int idx)
		{
			Shape s = Shapelist[idx];
			Body b = s.GetBody();
			b.GetShapeList().FilterData = f;
			UserData.filter = f;

			SelfBody.SetUserData(UserData);
		}

		

	}
	class RaycastEvent : EventArgs
	{
		public Shape[] Rayhit;
		public RaycastEvent(Shape[] rayhit) { Rayhit = rayhit; }
	}
}
