using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

namespace Nlgp1.Common {
	/// <summary>
	/// ステージをロードするクラス
	/// </summary>
	internal static class DataLoader {
		/// <summary>
		/// ステージをロードします。
		/// </summary>
		/// <param name="path">ステージへのパス</param>
		/// <param name="stageIndex">ステージ番号</param>
		/// <returns>ステージ</returns>
		public static StageData LoadStageFromFile( string path , int stageIndex ) {
			XmlDocument document = new XmlDocument();
			document.Load( path );
			XmlElement stageElement = document.GetChildElement( "Stage" );
			if( stageElement == null ) {
				return LoadStage( path , stageIndex );
			}
			StageData stageData = new StageData( stageElement.GetAttribute( "Index" ).ToInt32() );
			int mapRowCount = stageElement.GetChildElement( "MapRowCount" ).GetText().ToInt32();
			int mapColumnCount = stageElement.GetChildElement( "MapColumnCount" ).GetText().ToInt32();
			stageData.LayerTable = new Dictionary< string , LayerData >();
			XmlElement layersElement = stageElement.GetChildElement( "Layers" );
			foreach( XmlElement layerElement in layersElement.GetChildElements( "Layer" ) ) {
				LayerData layerData = new LayerData( layerElement.GetAttribute( "Name" ) );
				string chipsetPath = Path.Combine( Path.GetDirectoryName( path ) , layerElement.GetChildElement( "ChipsetPath" ).GetText() );
				layerData.ChipsetPath = chipsetPath;
				string chipsetImagePath;
				SortedDictionary< int , LayerChipData > chipTableData;
				LoadLayerChipsetFromFile( out chipsetImagePath , out chipTableData , chipsetPath );
				layerData.ChipsetImagePath = chipsetImagePath;
				layerData.ChipTable = chipTableData;
				layerData.Map = new LayerChipData[ mapRowCount , mapColumnCount ];
				LayerChipData noneChipData = layerData.ChipTable[ 0 ];
				for( int i = 0; i < layerData.Map.GetLength( 0 ); ++i ) {
					for( int j = 0; j < layerData.Map.GetLength( 1 ); ++j ) {
						layerData.Map[ i , j ] = noneChipData;
					}
				}
				foreach( XmlElement rowElement in layerElement.GetChildElement( "Map" ).GetChildElements( "Row" ) ) {
					int rowIndex = rowElement.GetAttribute( "Index" ).ToInt32();
					foreach( XmlElement columnElement in rowElement.GetChildElements( "Column" ) ) {
						int columnIndex = columnElement.GetAttribute( "Index" ).ToInt32();
						layerData.Map[ rowIndex , columnIndex ] = layerData.ChipTable[ columnElement.GetText().ToInt32() ];
					}
				}
				stageData.LayerTable.Add( layerData.Name , layerData );
			};
			return stageData;
		}

		private static void LoadLayerChipsetFromFile( out string imagePath , out SortedDictionary< int , LayerChipData > chipTableData , string path ) {
			XmlDocument document = LoadDocumentFromFile( path );
			XmlElement chipsetElement = document.GetChildElement( "LayerChipset" );
			imagePath = Path.Combine( Path.GetDirectoryName( path ) , chipsetElement.GetChildElement( "ImagePath" ).GetText() );
			chipTableData = new SortedDictionary< int , LayerChipData >();
			XmlElement chipsElement = chipsetElement.GetChildElement( "Chips" );
			foreach( XmlElement chipElement in chipsElement.GetChildElements( "Chip" ) ) {
				LayerChipData chipData = new LayerChipData( chipElement.GetAttribute( "Index" ).ToInt32() );
				chipData.Name = chipElement.GetChildElement( "Name" ).GetText();
				chipData.Summary = chipElement.GetChildElement( "Summary" ).GetText();
				chipData.Type = chipElement.GetChildElement( "Type" ).GetText().ToEnum< LayerChipType >();
				chipData.Friction = chipElement.GetChildElement( "Friction" ).GetText().ToDouble();
				chipTableData.Add( chipData.Index , chipData );
			}
		}

		private static XmlDocument LoadDocumentFromFile( string path ) {
			XmlDocument document = new XmlDocument();
			document.Load( path );
			return document;
		}

		#region 読み込み
		/// <summary>
		/// ステージをロードします。
		/// </summary>
		/// <param name="path">ステージへのパス</param>
		/// <param name="stageIndex">ステージ番号</param>
		private static StageData LoadStage( string path , int stageIndex ) {
			var document = new XmlDocument();
			if( document.TryFileLoadXml( path ) == false )
				return null;

			var stage = new StageData( stageIndex );
			var bgmPath = document.TryXmlPath( "SettingLocal/Header/Bgm" );
			if( !string.IsNullOrEmpty( bgmPath ) ) {
				stage.BgmPath = bgmPath;
			}

			var layerNames = Enum.GetNames( typeof( LayerType ) );
			stage.LayerTable = new Dictionary< string , LayerData >();
			for( int i = 0 ; i < layerNames.Length ; i++ ) {
				var name = layerNames[i];
				var layer = LoadLayer( document , Path.GetDirectoryName( path ) , name );
				stage.LayerTable[ name ] = layer;
			}

			return stage;
		}

		/// <summary>
		/// レイヤをロードします。
		/// </summary>
		/// <param name="document">XMLドキュメント</param>
		/// <paran name="currentPath">カレントディレクトリへのパス</paran>
		/// <param name="name">レイヤの名前</param>
		/// <returns>レイヤ</returns>
		private static LayerData LoadLayer( XmlDocument document , string currentPath , string name ) {
			var chipsetDataXmlPath = string.Format( "SettingLocal/Header/{0}ChipSetName" , name );
			var mapXmlPath = string.Format( "SettingLocal/Map/{0}Layer" , name );
			var chipsetFile = document.TryXmlPath( chipsetDataXmlPath );
			var chipsetPath = Path.Combine( currentPath , chipsetFile.Replace( currentPath , string.Empty ).Trim( '\\' ).Replace( "..\\" , string.Empty ) );
			var chipsetImagePath = LoadLayerChipsetImagePath( chipsetPath );

			var chipTable = LoadLayerChipTable( chipsetPath );
			var map = LoadMap( document , mapXmlPath , chipTable.Values );
			return new LayerData( name , chipsetPath , chipsetImagePath , chipTable , map );
		}

		/// <summary>
		/// レイヤに含まれるイメージへのパスをロードします。
		/// </summary>
		/// <param name="path">チップセットへのパス</param>
		/// <returns>イメージへのパス</returns>
		private static string LoadLayerChipsetImagePath( string path ) {
			if( string.IsNullOrEmpty( path ) )
				return null;
			if( File.Exists( path ) == false )
				return null;

			var line = File.ReadAllLines( path );
			if( string.IsNullOrEmpty( line[0] ) )
				return null;

			var chipsetImagePath = line[0].Trim();
			if( Path.IsPathRooted( chipsetImagePath ) == false ) {
				chipsetImagePath = Path.Combine( Path.GetDirectoryName( path ) , chipsetImagePath );
			}
			return chipsetImagePath;
		}

		/// <summary>
		/// レイヤに含まれるすべてのチップをロードします。
		/// </summary>
		/// <param name="path">チップセットへのパス</param>
		/// <returns>チップの配列</returns>
		private static SortedDictionary< int , LayerChipData > LoadLayerChipTable( string path ) {
			if( string.IsNullOrEmpty( path ) )
				return null;
			if( File.Exists( path ) == false )
				return null;

			var chipTable = new SortedDictionary< int , LayerChipData >();

			var lines = File.ReadAllLines( path );
			foreach( string line in lines ) {
				if( string.IsNullOrEmpty( line ) )
					continue;

				var elements = line.Split( ',' );
				if( elements.Length < 3 )
					continue;

				foreach( var element in elements )
					element.Trim();

				var index = int.Parse( elements[0] );
				var name = elements[1];
				var friction = Utility.TryParse<double>( elements[2] );
				var flags = ToLayerChipTypeFromString( elements[3] );
				var summary = elements[4];

				var chip = new LayerChipData( index , name , summary , flags , friction );
				chipTable.Add( chip.Index , chip );
			}

			return chipTable;
		}

		/// <summary>
		/// 文字列からチップタイプに変換します。
		/// </summary>
		/// <param name="text">文字列</param>
		/// <returns>チップタイプ</returns>
		private static LayerChipType ToLayerChipTypeFromString( string text ) {
			var type = default( LayerChipType );
			var typeTexts = text.Split( '|' );

			var typeType = typeof( LayerChipType );
			var typeTypes = Enum.GetNames( typeType );

			foreach( string typeText in typeTexts ) {
				if( string.IsNullOrEmpty( typeText ) )
					continue;
				if( typeTypes.Contains( typeText ) == false )
					continue;

				type |= (LayerChipType)Enum.Parse( typeType , typeText , true );
			}
			return type;
		}

		/// <summary>
		/// レイヤに含まれるマップをロードします。
		/// </summary>
		/// <param name="document">XMLドキュメント</param>
		/// <param name="xmlPath">マップのXMLパス</param>
		/// <param name="chips">チップの配列</param>
		/// <returns>マップ</returns>
		private static LayerChipData[,] LoadMap( XmlDocument document , string xmlPath , IEnumerable< LayerChipData > chips ) {
			var data = document.TryXmlPath( xmlPath );
			if( string.IsNullOrEmpty( data ) )
				return null;

			var chipTable = chips.ToDictionary( chip => chip.Name );
			var mapSheet = new List<List<LayerChipData>>();
			foreach( string line in data.Split( Environment.NewLine.ToCharArray() ).Where( testLine => string.IsNullOrEmpty( testLine ) == false ) ) {
				if( 4096 <= mapSheet.Count )
					break;
				var mapLine = new List<LayerChipData>();
				foreach( string cell in line.Split( ',' ).Where( testRow => string.IsNullOrEmpty( testRow.Trim() ) == false ) ) {
					if( 4096 <= mapLine.Count )
						break;
					mapLine.Add( chipTable[cell.Trim()] );
				}
				mapSheet.Add( mapLine );
			}

			var map = new LayerChipData[mapSheet.Count , mapSheet[0].Count];
			for( int row = 0 ; row < map.GetLength( 0 ) ; row++ ) {
				for( int column = 0 ; column < map.GetLength( 1 ) ; column++ ) {
					map[row, column] = mapSheet[row][column];
				}
			}
			return map;
		}

		#endregion // 読み込み
	}
}
