using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Box2DX.Common;
using DxLibDLL;
using Nlgp1.Common;
using Nlgp1.Drawers;
using Nlgp1.Sprites;
using Nlgp1.Stages;

namespace Nlgp1.Drawers {
	/// <summary>
	/// スプライトのモーション
	/// </summary>
	public sealed class SpriteDrawer {
		/// <summary>
		/// ステージを読み込みます。
		/// </summary>
		/// <param name="chipCount">チップ数</param>
		public static void LoadStage( int chipCount ) {
			drawerList = new List< SpriteDrawer >( chipCount );
			ChipsetImage.LoadStage( chipCount );
		}

		private static List< SpriteDrawer > drawerList;
		/// <summary>
		/// モーションを取得します。
		/// </summary>
		/// <param name="chipId">イメージID</param>
		/// <param name="getLocation">現在の座標</param>
		/// <param name="imageTypeName">イメージディレクトリ名</param>
		/// <returns></returns>
		public static SpriteDrawer Load( ChipId chipId , Vec2 location , string imageTypeName ) {
			return new SpriteDrawer( null , chipId , () => location , imageTypeName );
		}
		/// <summary>
		/// モーションを取得します。
		/// </summary>
		/// <param name="Sprite">スプライト</param>
		/// <param name="imageTypeName">イメージディレクトリ名</param>
		/// <returns></returns>
		/// <summary>
		public static SpriteDrawer Load( Sprite sprite , string imageTypeName ) {
			return new SpriteDrawer( sprite , sprite.ChipId , () => sprite.Location , imageTypeName );
		}
		/// 読み込まれているすべてのスプライトのモーションを取得します。
		/// </summary>
		public static IEnumerable< SpriteDrawer > GetCollection() {
			lock( drawerList )
				return drawerList.ToArray();
		}
		/// <summary>
		/// 読み込まれているすべてのスプライトのモーションを解放します。
		/// </summary>
		public static void ClearCollection() {
			lock( drawerList )
				drawerList.Clear();
		}

		private Dictionary< MotionChipType , Motion > motions;

		private SpriteDrawer( Sprite sprite , ChipId chipId , Func< Vec2 > getLocation , string imageTypeName ) {
			lock( drawerList )
				drawerList.Add( this );

			this.ChipId = chipId;
			this.getLocation = getLocation;
			this.Sprite = sprite;

			if( chipId.LayerType == LayerType.Characters ) {
				this.chipsetImage = ChipsetImage.Load( string.Format( @"{0}\{1:D3}.png" , imageTypeName , chipId.Index ) , chipId );
				if( this.chipsetImage == null )
					throw new NullReferenceException( string.Format( "{0} > {1} に対するチップセットを読み込めません。" , imageTypeName , chipId.Index ) );

				motions = new Dictionary< MotionChipType , Motion >( Enum.GetNames( typeof( MotionChipType ) ).Length );

				#region インデックス定義ファイルを読み込む
				string defPath = Path.Combine( Program.CurrentDirectory , string.Format( @"{0}\{1:D3}.def" , imageTypeName , chipId.Index ) );

				if( File.Exists( defPath ) == false )
					defPath = Path.Combine( Program.CurrentDirectory , string.Format( @"{0}\Common.def" , imageTypeName ) );

				if( File.Exists( defPath ) ) {
					foreach( var line in File.ReadAllLines( defPath ) ) {
						if( string.IsNullOrEmpty( line.Trim() ) )
							continue;
						string[] cells = line.Split( ',' );
						if( cells.Length < 4 )
							continue;

						string name  = cells[0].Trim();
						foreach( var item in Enum.GetValues( typeof( MotionChipType ) ) ) {
							MotionChipType type = (MotionChipType)item;
							if( name.Equals( Enum.GetName( typeof( MotionChipType ) , type ) , StringComparison.InvariantCultureIgnoreCase ) ) {
								Motion motion = new Motion();
								motion.Start = Utility.TryParse<int>( cells[1].Trim() );
								motion.End = Utility.TryParse<int>( cells[2].Trim() );
								motion.Time = Utility.TryParse<int>( cells[3].Trim() );
								motion.IsOnce = Utility.TryParse<bool>( cells[4].Trim() );
								motions[ type ] = motion;
							}
						}
					}
				}
				#endregion
			}
			else {
				this.chipsetImage = ChipsetImage.Load( imageTypeName , new ChipId( chipId.LayerType , 0 ) );
				motions = new Dictionary< MotionChipType , Motion >();
				Motion motion = new Motion();
				motion.Start = chipId.Index;
				motion.End = chipId.Index;
				motion.Time = 0;
				motion.IsOnce = false;
				motions[ MotionChipType.None ] = motion;
			}
		}

		/// <summary>
		/// モーションに適応されるチップセットを取得します。
		/// </summary>
		private ChipsetImage chipsetImage;

		/// <summary>
		/// スプライトの状態から該当するチップイメージを取得します。
		/// </summary>
		/// <returns>該当するチップイメージ</returns>
		public ChipImage GetChipImage() {
			MotionChipType nowType = GetMotionType();

			if( nowType == this.LastMotionType && this.FrameCount <= motions[nowType].Time && this.FrameCount >= 1 ) {
				this.FrameCount++;
				return this.chipsetImage.GetChipImage( this.LastMotionIndex - 1 );
			} else {
				this.LastMotionType = nowType;
				if( nowType != this.LastMotionType )
					this.LastMotionIndex = 0;

				if( motions[nowType].IsOnce && motions[nowType].End < this.LastMotionIndex ) {
					this.LastMotionIndex = motions[nowType].End;
				} else if( motions[nowType].Start > this.LastMotionIndex || motions[nowType].End < this.LastMotionIndex )
					this.LastMotionIndex = motions[nowType].Start;

				this.FrameCount = 1;
				if( this.LastMotionIndex < 0 || this.chipsetImage.Count <= this.LastMotionIndex )
					this.LastMotionIndex = 0;
				return this.chipsetImage.GetChipImage( this.LastMotionIndex++ );
			}
		}

		/// <summary>
		/// スプライトの状態からモーションの種類を取得します。
		/// </summary>
		/// <returns>モーションの種類</returns>
		private MotionChipType GetMotionType() {
			if( this.ChipId.LayerType == LayerType.Characters ) {
				return MotionChipType.RestRight;
			}
			else {
				return MotionChipType.None;
			}
		}

		public Sprite Sprite {
			get;
			private set;
		}

		public ChipId ChipId {
			get;
			private set;
		}
		
		public void Draw( Vec2 target ) {
			this.GetChipImage().Draw( target );
		}

		public Vec2 Location {
			get {
				return this.getLocation();
			}
		}

		private readonly Func< Vec2 > getLocation;

		#region スプライトのモーション
		public MotionChipType LastMotionType {
			get;
			set;
		}
		public int LastMotionIndex {
			get;
			set;
		}
		public int FrameCount {
			get;
			set;
		}
		#endregion

		/// <summary>
		/// モーションの描画方法
		/// </summary>
		private class Motion {
			/// <summary>
			/// モーションの開始インデックス
			/// </summary>
			public int Start;
			/// <summary>
			/// モーションの終了インデックス
			/// </summary>
			public int End;
			/// <summary>
			/// モーションの終了インデックスに達したときに開始インデックスにリセットしないようにするかどうか
			/// </summary>
			public bool IsOnce;
			/// <summary>
			/// 次のチップイメージに切り替えるまでの描画回数。
			/// </summary>
			public int Time;
		}
	}
}
