using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;
using CaLib.Encryption;
using CaLib.User;

namespace ClipClop.User
{
	public class XmlCipheredDocument : XmlDocument
	{
		public enum CipheringStatus
		{
			initial, normal, ciphered
		}

		CipheringStatus status_;
		public CipheringStatus Status { get { return status_; } }

		public XmlCipheredDocument( CipheringStatus s)
			: base()
		{
			status_ = s;
		}

		// 概要:
		//     System.Xml.XmlDocument クラスの新しいインスタンスを初期化します。
		public XmlCipheredDocument() : base()
		{
			status_ = CipheringStatus.initial;
		}

		//
		// 概要:
		//     System.Xml.XmlNameTable を指定して、XmlDocument クラスの新しいインスタンスを初期化します。
		//
		// パラメータ:
		//   nt:
		//     使用する XmlNameTable。
		public XmlCipheredDocument(XmlNameTable nt)
			: base(nt)
		{
			status_ = CipheringStatus.initial;
		}

		#region 要素アクセス
		public XmlNode GetRoot()
		{
			foreach (XmlNode node in this.ChildNodes)
			{
				if (node.Name == ContextMenuSettingDefinition.Root_)
					return node;
			}
			return null;
		}

		public bool IsEncrypted()
		{
			XmlNode root = GetRoot();
			if (root == null)
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EF006);
			}

			XmlAttribute attr = root.Attributes[ContextMenuSettingDefinition.RootAttribute.encryption.ToString()];
			if (attr == null)
				return false;
			if (string.IsNullOrEmpty(attr.Value))
				return false;
			if (attr.Value == true.ToString().ToLower())
				return true;
			return false;
		}

		public void SetEncryptionAttribute(bool bOn)
		{
			XmlNode root = GetRoot();
			if (root == null)
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EA001);
			}

			string key = ContextMenuSettingDefinition.RootAttribute.encryption.ToString();

			XmlAttribute attr = root.Attributes[key];
			if (attr == null)
			{
				attr = base.CreateAttribute(key);
				attr.Value = bOn.ToString().ToLower();
				root.Attributes.Append(attr);
				return;
			}
			attr.Value = bOn.ToString().ToLower();
		}

		public void CheckRoot()
		{
			XmlNode root = GetRoot();
			if (root == null)
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EA001);
			}

			if (!ContextMenuSettingHelper.IsRoot(root))
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EF006);
			}

			if (root.Attributes[ContextMenuSettingDefinition.RootAttribute.magicnumber.ToString()].Value != ContextMenuSettingDefinition.MagicNumber)
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EF006);
			}

			int ver = Convert.ToInt32(root.Attributes[ContextMenuSettingDefinition.RootAttribute.version.ToString()].Value, 16);
			if (ver > ContextMenuSettingDefinition.CurrnetVersion)
			{
				throw new FundamentalException(global::ClipClop.Properties.Resources.EF007);
			}
		}
		#endregion


		public void SaveTo(string filePath, string password)
		{
			Debug.Assert(status_ != CipheringStatus.ciphered);

			if (!this.IsEncrypted())
			{
				//暗号化せずに書き込む場合

				base.Save(filePath);
				return;
			}

			//属性などを暗号化する
			RecursiveEncrypt(this.GetRoot(), password);
			this.Save(filePath);

			//元に戻す
			RecursiveDecrypt(this.GetRoot(), password);
		}

		/// <summary>
		/// 読み込み処理。何かあると例外投入する。
		/// </summary>
		/// <param name="filePath"></param>
		public void Read(string filePath)
		{
			string xsdPath = ContextMenuSettingDefinition.GetXsdFilePath();

			XmlFileReader reader = new XmlFileReader(xsdPath);

			bool ret = reader.Read(this, filePath);

			if (ret == false)
			{
				throw new FundamentalException(reader.GetErrorMessage());
			}

			//読み込み後のチェック
			//不正な場合は例外投入
			this.CheckRoot();

			if (this.IsEncrypted())
				status_ = XmlCipheredDocument.CipheringStatus.ciphered;
			else
				status_ = XmlCipheredDocument.CipheringStatus.normal;
		}

		public void Decrypt(string password)
		{
			Debug.Assert(status_ == CipheringStatus.ciphered);
			Debug.Assert(!string.IsNullOrEmpty(password));

			RecursiveDecrypt(this.GetRoot(), password);

			status_ = XmlCipheredDocument.CipheringStatus.normal;
		}

		#region 外部非公開暗号処理関数
		static void RecursiveDecrypt(XmlNode parentnode, string password)
		{
			XmlAttribute attri;
			string work;

			foreach (XmlNode childXmlNode in parentnode.ChildNodes)
			{
				switch (ContextMenuSettingHelper.GetSettingType(childXmlNode))
				{
					case ContextMenuSettingDefinition.SettingType.separator:
						continue;
					case ContextMenuSettingDefinition.SettingType.sentence:
						switch (ContextMenuSettingHelper.GetMode(childXmlNode))
						{
							case ContextMenuSettingDefinition.Mode.clipboardhistory:
							case ContextMenuSettingDefinition.Mode.editfile:
								break;

							case ContextMenuSettingDefinition.Mode.execute://暗号化対象
							case ContextMenuSettingDefinition.Mode.template://暗号化対象

								attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Name_];
								if (attri != null)
								{
									//名前属性はないかもしれない
									work = SSTCryptographer.Decrypt(attri.Value, password);
									attri.Value = work;
								}

								//値属性は必ずある
								attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Value_];
								Debug.Assert(null != attri);
								work = SSTCryptographer.Decrypt(attri.Value, password);
								attri.Value = work;
								break;
						}
						break;
					case ContextMenuSettingDefinition.SettingType.folder://暗号化対象
						//フォルダは必ず名前しかない（値は持たない）
						Debug.Assert(null == childXmlNode.Attributes[ContextMenuSettingDefinition.Value_]);
						Debug.Assert(null != childXmlNode.Attributes[ContextMenuSettingDefinition.Name_]);

						attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Name_];
						work = SSTCryptographer.Decrypt(attri.Value, password);
						attri.Value = work;

						// 再帰呼び出し
						RecursiveDecrypt(childXmlNode, password);
						break;
				}
			}
		}


		static void RecursiveEncrypt(XmlNode parentnode, string password)
		{
			XmlAttribute attri;
			string work;

			foreach (XmlNode childXmlNode in parentnode.ChildNodes)
			{
				switch (ContextMenuSettingHelper.GetSettingType(childXmlNode))
				{
					case ContextMenuSettingDefinition.SettingType.separator:
						continue;
					case ContextMenuSettingDefinition.SettingType.sentence:
						switch (ContextMenuSettingHelper.GetMode(childXmlNode))
						{
							case ContextMenuSettingDefinition.Mode.clipboardhistory:
							case ContextMenuSettingDefinition.Mode.editfile:
								break;

							case ContextMenuSettingDefinition.Mode.execute://暗号化対象
							case ContextMenuSettingDefinition.Mode.template://暗号化対象

								attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Name_];
								if (attri != null)
								{
									//名前属性はないかもしれない
									work = SSTCryptographer.Encrypt(attri.Value, password);
									attri.Value = work;
								}

								//値属性は必ずある
								attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Value_];
								Debug.Assert(null != attri);
								work = SSTCryptographer.Encrypt(attri.Value, password);
								attri.Value = work;
								break;
						}
						break;
					case ContextMenuSettingDefinition.SettingType.folder://暗号化対象
						//フォルダは必ず名前しかない（値は持たない）
						Debug.Assert(null == childXmlNode.Attributes[ContextMenuSettingDefinition.Value_]);
						Debug.Assert(null != childXmlNode.Attributes[ContextMenuSettingDefinition.Name_]);

						attri = childXmlNode.Attributes[ContextMenuSettingDefinition.Name_];
						work = SSTCryptographer.Encrypt(attri.Value, password);
						attri.Value = work;

						// 再帰呼び出し
						RecursiveEncrypt(childXmlNode, password);
						break;
				}
			}
		}
		#endregion

	}
}
