using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Net;
using System.Windows.Forms;
using System.Xml;
using nft.util;
using System.Diagnostics;

namespace nft.framework.plugin
{
	
	/// <summary>
	/// Common base class of contributions.
	/// 
	/// A contribution is a functionality provided by a plug-in.
	/// </summary>
	[Serializable]
	public abstract class Contribution : ISerializable, IHasNameAndID, IAddable
    {
        #region Contribution ID generation
        // Contribution ID will be used as a part of filepath.
        // {Plugin_id}\{Contribution_short_id}
        static public readonly string PID_Sepalator = "\\";
        static string GenerateID( ParamsReader contrib )
		{
			string short_id = contrib["id"].InnerText;
			if( short_id == null )
			{
				string templ = Main.resources["xml.attribute_not_found"].stringValue;
				throw new PluginXmlException(contrib,string.Format(
					templ,"contribution","name",contrib.SourceURI));
			}

			string pname = PluginUtil.GetPluginDirName(contrib);
            return pname + PID_Sepalator + short_id;
		}
        static string GenerateID(Plugin owner, string short_id)
        {
            string pname = owner.ID;
            return pname + PID_Sepalator + short_id;
        }
        #endregion

        protected Contribution(Plugin owner, ParamsReader contrib)
            : this(owner, contrib, 
            contrib["name"].InnerTextOr("<unknown>"), 
            contrib["description"].InnerTextOr("")) {}

        protected Contribution(Plugin owner, ParamsReader contrib, string name, string description)
        {
            this.parent = owner;
            this.name = name;
            this.description = description;
            //Debug.WriteLine("name:" + name + " ,baseDir:" + baseDir);
            id = GenerateID(contrib);
            try
            {
                CtbType = contrib["type"].InnerText;
            }
            catch
            {
                string templ = Main.resources["xml.attribute_not_found"].stringValue;
                throw new PluginXmlException(contrib, string.Format(
                    templ, "contribution", "type", contrib.SourceURI));
            }
        }

        // for override use only. please 
        protected Contribution(Plugin owner, string _type, string short_id, string _name, string _description)
        {
            this.parent = owner;
            this.CtbType = _type;
            this.id = GenerateID(owner, short_id);
			this.name = _name;
			this.description = _description;
		}
        
		/// <summary>
		/// This method is a backdoor to configure a contribution.
		/// 
		/// We could just pass this argument through a constructor,
		/// but Contribution will be inherited multiple times, so it would be
		/// little awkward to pass a lot of parameters around.
		/// </summary>
		/// <param name="_baseUri"></param>
        [Obsolete]
		internal void SetOwner( Plugin _parent ) {
            /*
			this.parent = _parent;
            this.baseDir = _parent.dirName;
            Debug.Print(DataDirectory);
             */
		}

		#region IAddable メンバ
		internal protected InstallationState _state = InstallationState.Uninitialized;
		[NonSerialized]
		protected AttachChangeEvent onDetach;
		[NonSerialized]
		protected AttachChangeEvent onAttach;

		protected bool attached = false;

		public InstallationState State { get{ return _state; } }
		
		public virtual bool IsDetachable { get{ return false; } }

		public virtual bool QueryDetach() {	return IsDetachable; }

		public virtual void Detach()
		{
			if(IsDetachable)
			{
				attached = false;
                if (onDetach != null)
                    onDetach(this);
            }
			else
			{
				string msg = string.Format("This Contribution{0} is not detachable!",ID);
				throw new InvalidOperationException(msg);
			}
		}

		public virtual void Attach()
		{
			attached = true;
            PrepareCacheData(false);
            if (onAttach != null)
                onAttach(this);
		}

		public virtual bool IsAttached{ get{ return attached; } }
		public virtual bool IsPartiallyDetached{ get{ return false; } }
		public AttachChangeEvent OnDetach{ get{ return onDetach; } set{ onDetach = value; } }
		public AttachChangeEvent OnAttach{ get{ return onAttach; } set{ onAttach = value; } }
		#endregion

        /// <summary>
        /// Do initialization.
        /// If this contribution want to be called after all initialization complete, 
        /// returns InitCompleteEventHandler delegate.
        /// </summary>
        /// <returns>can be null. returns init complete event handler if necessaly.</returns>
        protected internal virtual InitCompleteEventHandler Initialize() {
            return null;
        }

        /// <summary>
        /// This method will called after Initialize method.
        /// Should Implement cached data preparation in sub-classes.
        /// Cached data are saved even after the application ended and use next boot.
        /// So there can be already existing data.
        /// </summary>
        /// <param name="forceUpdate">Force updating data (Old data should be overwrited)</param>
        public virtual void PrepareCacheData(bool forceUpdate) { }

        /// <summary>
		/// Type of this contribution.
		/// This is the value of the type attribute.
		/// </summary>
		public readonly string CtbType;

		/// <summary>
		/// Unique ID of this contribution.
		/// 
		/// Either GUID or URI, but can be anything as long
		/// as it's unique.
		/// </summary>
		protected readonly string id;
		/// <summary>
		/// Name of this contribution.
		/// </summary>
		protected readonly string name;

		protected readonly string description;
		public virtual string Description { get { return description; } }

		internal protected bool _hideFromCom;
		internal protected bool _hideFromPlayer;
		public bool hideFromCom{ get{ return _hideFromCom;} }
		public bool hideFromPlayer{ get{ return _hideFromPlayer;} }

        public string DataDirectory {
            get {
                if (datapath == null) {
                    datapath = Directories.MakeDataDirNameFor(this);
                }
                return datapath;
            }
        }
        private string datapath;

        /// <summary>
		/// Base URI for this contribution (which will be the same
		/// as the base URI for the plug-in.)
		/// 
		/// This poinst to the plug-in directory.
		/// </summary>
		public string PluginPath { get { return parent.PathName; } }
		
		/// <summary>
		/// Returns the Plugin object that contains this contribution.
		/// </summary>
		public Plugin Parent { get { return parent; } }
		private Plugin parent;


		/// <summary>
		/// If a plug-in is implemented by using an assembly,
		/// it should override property and return the Assembly
		/// object, so that obejcts from this assembly can be
		/// de-serialized.
		/// 
		/// Returns null if this contribution doesn't rely on
		/// any assembly.
		/// </summary>
		public virtual Assembly Assembly { get { return this.GetType().Assembly; } }

        public bool IsPrimitive {
            get {
                return IsPrimitiveContribution(this.GetType());
            }
        }

        /// <summary>
        /// return true if specified class has 'PrimitiveContribution' Attribute.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsPrimitiveContribution(Type type) {
            object[] o = type.GetCustomAttributes(typeof(PrimitiveContributionAttribute),true);
            return (o != null && o.Length > 0);
        }

		#region utility methods (comment outed)
		/*
		protected Picture loadPicture( string name ) {
			return new Picture( this.id+":"+name, new Uri( baseUri,name).LocalPath );
		}

		/// <summary>
		/// Locate the Picture from which sprites should be loaded.
		/// </summary>
		/// <param name="sprite">&lt;sprite> element in the manifest.</param>
		/// <returns>non-null valid object.</returns>
		protected Picture getPicture( XmlElement sprite ) {
			XmlElement pic = (XmlElement)XmlUtil.selectSingleNode(sprite,"picture");
			
			XmlAttribute r = pic.Attributes["ref"];
			if(r!=null)
				// reference to externally defined pictures.
				return PictureManager.get(r.Value);

			// otherwise look for local picture definition
			return new Picture(pic,
				sprite.SelectSingleNode("ancestor-or-self::contribution/@id").InnerText);
		}
		*/
		#endregion

		// serialize this object by reference
		public virtual void GetObjectData( SerializationInfo info, StreamingContext context) 
		{
			info.SetType(typeof(ReferenceImpl));
			info.AddValue("id",id);
		}
		
		[Serializable]
		internal sealed class ReferenceImpl : IObjectReference {
			private string id=null;
			public object GetRealObject(StreamingContext context) {
				object o = PluginManager.theInstance.GetContribution(id);
				if(o==null)
					throw new SerializationException(
						"コントリビューション\""+id+"\"を含むプラグインが見つかりません");
				return o;
			}
		}
		#region IHasNameAndID メンバ
        /// <summary>
        /// Contribution ID can be used as a part of file path.
        /// '{Plugin_id}\{Contribution_short_id}'
        /// </summary>
		public string ID
		{
			get
			{
				return id;
			}
		}

		public string Name
		{
			get
			{
				return name;
			}
		}

		#endregion

	}
}
