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 DataSaver {

		/// <summary>
		/// ステージをセーブします。
		/// </summary>
		/// <param name="path">ステージへのパス</param>
		/// <param name="stage">ステージ</param>
		public static void SaveStageToFile( string path , StageData stageData ) {
			XmlDocument document = CreateDocument();
			XmlElement stageElement = document.AddChildElement( "Stage" );
			stageElement.SetAttribute( "Index" , stageData.Index.ToString() );
			stageElement.AddChildElement( "MapRowCount" ).SetText( stageData.MapRowCount.ToString() );
			stageElement.AddChildElement( "MapColumnCount" ).SetText( stageData.MapColumnCount.ToString() );
			XmlElement layersElement = stageElement.AddChildElement( "Layers" );
			foreach( LayerData layerData in stageData.Layers ) {
				XmlElement layerElement = layersElement.AddChildElement( "Layer" );
				layerElement.SetAttribute( "Name" , layerData.Name );
				string chipsetPath = layerData.ChipsetPath;
				if( Path.GetExtension( chipsetPath ) == ".csv" ) {
					chipsetPath = Path.ChangeExtension( chipsetPath , ".xml" );
				}
				SaveLayerChipsetToFile( chipsetPath , layerData.ChipsetImagePath , layerData.Chips );
				layerElement.AddChildElement( "ChipsetPath" ).SetText( Utility.GetRelativePath( chipsetPath , Path.GetDirectoryName( path ) ) );
				XmlElement mapElement = layerElement.AddChildElement( "Map" );
				for( int i = 0; i < layerData.Map.GetLength( 0 ); ++i ) {
					XmlElement rowElement = null;
					for( int j = 0; j < layerData.Map.GetLength( 1 ); ++j ) {
						if( layerData.Map[ i , j ].Index > 0 ) {
							if( rowElement == null ) {
								rowElement = mapElement.AddChildElement( "Row" );
								rowElement.SetAttribute( "Index" , i.ToString() );
							}
							XmlElement columnElement = rowElement.AddChildElement( "Column" );
							columnElement.SetAttribute( "Index" , j.ToString() );
							columnElement.SetText( layerData.Map[ i , j ].Index.ToString() );
						}
					}
				}
			}
			document.Save( path );
		}

		private static void SaveLayerChipsetToFile( string path , string imagePath , IEnumerable< LayerChipData > chipsData ) {
			XmlDocument document = CreateDocument();
			XmlElement chipsetElement = document.AddChildElement( "LayerChipset" );
			chipsetElement.AddChildElement( "ImagePath" ).SetText( Utility.GetRelativePath( imagePath , Path.GetDirectoryName( path ) ) );
			XmlElement chipsNode = chipsetElement.AddChildElement( "Chips" );
			foreach( LayerChipData chipData in chipsData ) {
				XmlElement chipElement = chipsNode.AddChildElement( "Chip" );
				chipElement.SetAttribute( "Index" , chipData.Index.ToString() );
				chipElement.AddChildElement( "Name" ).SetText( chipData.Name );
				chipElement.AddChildElement( "Summary" ).SetText( chipData.Summary );
				chipElement.AddChildElement( "Type" ).SetText( chipData.Type.ToString() );
				chipElement.AddChildElement( "Friction" ).SetText( chipData.Friction.ToString() );
			}
			document.Save( path );
		}

		private static XmlDocument CreateDocument() {
			XmlDocument document = new XmlDocument();
			document.AppendChild( document.CreateXmlDeclaration( "1.0" , "UTF-8" , null ) );
			return document;
		}

		#region 書き込み
		/// <summary>
		/// ステージをセーブします。
		/// </summary>
		/// <param name="path">ステージへのパス</param>
		/// <param name="stage">ステージ</param>
		private static void SaveStage( string path , StageData stage ) {
			string currentPath = Path.GetDirectoryName( path );
			SaveLayers( currentPath , stage );

			var document = new XmlDocument();
			var declaration = document.CreateXmlDeclaration( "1.0" , "UTF-8" , null );
			document.AppendChild( declaration );
			var root = AddXmlElement( document , "SettingLocal" );
			var header = AddXmlElement( root , "Header" );
			var map = AddXmlElement( root , "Map" );

			if ( stage.BgmPath != null ) {
				var bgm = AddXmlElement( header , "Bgm" );
				bgm.InnerText = Utility.GetRelativePath( stage.BgmPath , Path.GetDirectoryName( path ) );
			}

			foreach( var layer in stage.Layers ) {
				var name = layer.Name;
				var chipsetElement = CreateChipsetXmlElement( document , currentPath , name , layer );
				var layerElement = CreateLayerXmlElement( document , name , layer );

				header.AppendChild( chipsetElement );
				map.AppendChild( layerElement );
			}

			// var charElement = CreateCharLayerElement("Character", this.map.CharacterLayer);
			//  map.AppendChild(charElement);

			document.Save( path );
		}

		/// <summary>
		/// XMLエレメントを追加します。
		/// </summary>
		/// <param name="parent">追加されるXMLエレメントの親ノード</param>
		/// <param name="child">追加されるXMLエレメントの名前</param>
		/// <returns>追加されたXMLエレメント</returns>
		private static XmlNode AddXmlElement( XmlNode parent , string child ) {
			var element = parent.OwnerDocument.CreateElement( child );
			var node = parent.AppendChild( element );
			return node;
		}

		/// <summary>
		/// チップセットを表すXMLエレメントを作成します。
		/// </summary>
		/// <param name="document">XMLドキュメント</param>
		/// <param name="currentPath">カレントディレクトリへのパス</param>
		/// <param name="layerDataName">レイヤ名</param>
		/// <param name="layerType">レイヤ</param>
		/// <returns>XMLエレメント</returns>
		private static XmlElement CreateChipsetXmlElement( XmlDocument document , string currentPath , string layerName , LayerData layer ) {
			var elementName = string.Format( "{0}ChipSetName" , layerName );
			var element = document.CreateElement( elementName );
			element.InnerText = Utility.GetRelativePath( layer.ChipsetPath , currentPath );

			return element;
		}

		/// <summary>
		/// レイヤを表すXMLエレメントを作成します。
		/// </summary>
		/// <param name="document">XMLドキュメント</param>
		/// <param name="layerDataName">レイヤの名前</param>
		/// <param name="layerType">レイヤ</param>
		/// <returns>XMLエレメント</returns>
		private static XmlElement CreateLayerXmlElement( XmlDocument document , string layerDataName , LayerData layer ) {
			var elementName = string.Format( "{0}Layer" , layerDataName );
			var element = document.CreateElement( elementName );
			element.InnerText = ToStringFromMap( layer.Map );

			return element;
		}

		/// <summary>
		/// キャラクタレイヤを表すXMLエレメントを作成します。
		/// </summary>
		/// <param name="document">XMLドキュメント</param>
		/// <param name="elementName">レイヤ名</param>
		/// <param name="map">マップ</param>
		/// <returns>XMLエレメント</returns>
		private static XmlElement CreateCharacterLayerXmlElement( XmlDocument document , string elementName , int[,] map ) {
			var element = document.CreateElement( elementName );
			if( map == null )
				return element;

			var builder = new StringBuilder();
			for( int y = 0 ; y < map.GetLength( 1 ) ; y++ ) {
				for( int x = 0 ; x < map.GetLength( 0 ) - 1 ; x++ ) {
					builder.Append( map[x , y] + ',' );
				}
				var last = map.GetLength( 0 ) - 1;
				builder.Append( map[last , y] );
				builder.AppendLine();
			}
			element.InnerText = builder.ToString();

			return element;
		}

		/// <summary>
		/// マップから文字列に変換します。
		/// </summary>
		/// <param name="map">マップ</param>
		/// <returns>文字列</returns>
		private static string ToStringFromMap( LayerChipData[,] map ) {
			var builder = new StringBuilder();
			for( int y = 0 ; y < map.GetLength( 1 ) ; y++ ) {
				for( int x = 0 ; x < map.GetLength( 0 ) - 1 ; x++ ) {
					var chipName = map[x , y];
					builder.Append( chipName + "," );
				}
				var last = map.GetLength( 0 ) - 1;
				builder.Append( map[last , y] );
				builder.AppendLine();
			}
			return builder.ToString();
		}

		/// <summary>
		/// レイヤセットをセーブします。
		/// </summary>
		/// <param name="currentPath">カレントディレクトリへのパス</param>
		/// <param name="stage">ステージ</param>
		private static void SaveLayers( string currentPath , StageData stage ) {
			foreach( var layer in stage.Layers ) {
				SaveLayer( currentPath , layer , layer.ChipsetPath.Replace( currentPath , string.Empty ).Trim( '\\' ) );
			}
		}

		/// <summary>
		/// チップセットをセーブします。
		/// </summary>
		/// <param name="currentPath">カレントディレクトリへのパス</param>
		/// <param name="layer">レイヤ</param>
		/// <param name="path">チップセットへのパス</param>
		private static void SaveLayer( string currentPath , LayerData layer , string path ) {
			var builder = new StringBuilder();

			string imgFileName = Path.GetFileNameWithoutExtension( layer.ChipsetPath ) + Path.GetExtension( layer.ChipsetImagePath );
			string saveImgDir  = Path.Combine( Path.GetDirectoryName( Path.Combine( currentPath , path ) ) , imgFileName );
			if( layer.ChipsetImagePath.Equals( saveImgDir , StringComparison.InvariantCultureIgnoreCase ) == false ) {
				File.Copy( layer.ChipsetImagePath , saveImgDir , true );
			}
			builder.AppendLine( imgFileName );
			var chips = layer.Chips;
			foreach( var chip in chips ) {
				var rows = new String[5];
				rows[0] = chip.Index.ToString();
				rows[1] = chip.Name;
				rows[2] = chip.Friction.ToString( "0.#" );
				rows[3] = ToStringFromChipType( chip.Type );
				rows[4] = chip.Summary;

				var row = string.Join( "," , rows );
				builder.AppendLine( row );
			}
			if( string.IsNullOrEmpty( currentPath ) )
				File.WriteAllText( path , builder.ToString() , Encoding.UTF8 );
			else
				File.WriteAllText( Path.Combine( currentPath , path ) , builder.ToString() , Encoding.UTF8 );
		}

		/// <summary>
		/// チップタイプから文字列に変換します。
		/// </summary>
		/// <param name="chipType">チップタイプ</param>
		/// <returns>文字列</returns>
		private static string ToStringFromChipType( LayerChipType chipType ) {
			var text = chipType.ToString();
			text = text.Replace( ", " , "|" );
			return text;
		}

		#endregion // 書き込み
	}
}
