#region Copyright and GPL
/*************************************************************************
 * dataMagus - The open source database learning software
 * Copyright (C) 2007 by all authors
 * Authors: Monika Schwitter (monsy@gmx.ch)
 *          Jürg Zgraggen (j.zgraggen@ejaz.net)
 *          HSLU (Hochschule Luzern - Technik+Architektur)
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *************************************************************************/
#endregion
#region Using
using System;
using System.IO;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using dataMagus.Common.Logical;
#endregion

namespace dataMagus.Common.Physical
{
	/// <summary>
	/// This class represents a abstract physical element.
	/// </summary>
    [Serializable]
	public abstract class PhysicalElement : IElement, IXmlGeneratorWriter
	{
		#region Constants
		/// <summary>
		/// Name of the Name property.
		/// </summary>
		private const string NamePropertyName = "Name";

		/// <summary>
		/// Name of the Location.X property.
		/// </summary>
		private const string LocationPropertyNameX = "LocationX";
        
		/// <summary>
		/// Name of the Location.Y property.
		/// </summary>
		private const string LocationPropertyNameY = "LocationY";
        
		/// <summary>
		/// Name of the ExtendedProperties property.
		/// </summary>
		private const string ExtendedPropertiesPropertyName = "ExtendedProperties";
		#endregion
		
		#region Members
        /// <summary>
        /// The physical schema this element belongs to.
        /// </summary>
        // Important: we need to serialize the schema because of the "calculated" business logic 
        // in the elements. Clone uses binary serialization and otherwise we would lose information
        protected IPhysicalSchema _schema;

        /// <summary>
		/// Id of the element.
		/// </summary>
		private string _id;
		
		/// <summary>
		/// Name of the element.
		/// </summary>
		private string _name;
        
		/// <summary>
		/// Indicates if the element is in InitMode.
		/// </summary>
		/// <remarks>InitMode will suppress all changing events.</remarks>
		private bool _initMode = false;
		
		/// <summary>
		/// Indicates if the element has been cloned.
		/// </summary>
        /// <remarks>Beware: events on clones will be suppressed.</remarks>
		private bool _isClone = false;
		
		/// <summary>
		/// Location of the element.
		/// </summary>
		private Point _location = Point.Empty;
        
		/// <summary>
		/// The origin element this element is based on.
		/// </summary>
		[NonSerialized]
		private IElement _origin = null;
		#endregion
		
		#region Constructors / Destructors
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="schema">The physical schema this element belongs to.</param>
		/// <param name="id">Id of the element.</param>
		/// <param name="origin">The origin element this element is based on.</param>
		/// <param name="name">Name of the element.</param>
        protected PhysicalElement(IPhysicalSchema schema, string id, IElement origin, string name)
		{
            this._schema = schema;
			this._id = id;
			this._origin = origin;
            this._name = this.EnsureName(name);

            // Restore physical changes if available
            object nameToRestore = DataContainer.Instance.PhysicalData.GetRestoreDataItem(this._schema, this.Id, PhysicalElement.NamePropertyName);
            if (null != nameToRestore)
            {
                this._name = (string)nameToRestore;
            }

            object locationX = DataContainer.Instance.PhysicalData.GetRestoreDataItem(this._schema, this.Id, PhysicalElement.LocationPropertyNameX);
            if (null != locationX)
            {
                this._location.X = (int)locationX;
            }
            
            object locationY = DataContainer.Instance.PhysicalData.GetRestoreDataItem(this._schema, this.Id, PhysicalElement.LocationPropertyNameY);
            if (null != locationY)
            {
                this._location.Y = (int)locationY;
            }
		}

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <remarks>Only used for serialization.</remarks>
        private PhysicalElement()
        {
        }
		#endregion
		
		#region Properties
        /// <summary>
		/// Id of the element.
		/// </summary>
		[Browsable(false)]
		public string Id 
		{ 
			get { return this._id; }
		}
		
		/// <summary>
		/// Name of the element.
		/// </summary>
		public string Name
		{
			get { return this._name; }
			set 
			{ 
				if (this._name != value)
				{
                    value = this.EnsureName(value);
	                this.EnsureUpdateOriginalRestoreDataItem(this._schema, this, PhysicalElement.NamePropertyName, value);
                    this._name = value;
					this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
				}
			}
		}

		/// <summary>
		/// Indicates if the element has been cloned.
		/// </summary>
        /// <remarks>Beware: events on clones will be suppressed.</remarks>
		[Browsable(false)]
		public bool IsClone 
		{ 
			get { return this._isClone; }
		}

		/// <summary>
		/// Indicates if the element is in InitMode.
		/// </summary>
		/// <remarks>InitMode will suppress all changing events.</remarks>
		[Browsable(false)]
		public bool InitMode
		{
			get { return this._initMode; }
		}
		
		/// <summary>
		/// Location of the element.
		/// </summary>
		[Browsable(false)]
		public Point Location
		{
			get { return this._location; }
			set 
			{ 
				if (this._location != value)
				{
                    this.EnsureUpdateOriginalRestoreDataItem(this._schema, this, PhysicalElement.LocationPropertyNameX, value.X);
	                this.EnsureUpdateOriginalRestoreDataItem(this._schema, this, PhysicalElement.LocationPropertyNameY, value.Y);
					this._location = value; 
					this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
				}
			}
		}
		
		/// <summary>
		/// The origin element this element is based on.
		/// </summary>
		[Browsable(false)]
		public IElement Origin 
		{
			get { return this._origin; }
			set 
			{ 
				if (this._origin != value)
				{
					this._origin = value; 
					this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.ChangedProperty));
				}
			}
		}
		#endregion
		
		#region Events
		/// <summary>
		/// Event when the element has been changed.
		/// </summary>
        // Very important: Mark this event as NonSerialized!! The compiler, will behind the scences,
        // create a hidden field for us to maintain to a collection of all your listeners. 
        // But our listeners are type of UserControl which can not be serialized. Therefore,
        // we have to explicitely mark as NonSerialized. Esoteric indeed!
        [field: NonSerialized]	// VIC = Very Important Code!
		public event ElementChangedHandler ElementChanged;
		#endregion
		
		#region Methodes
		/// <summary>
		/// String representation of the object.
		/// </summary>
		/// <returns>Name of the element.</returns>
		public override string ToString()
		{
			return this.Name;
		}

		/// <summary>
		/// Ensures that the object has a name.
		/// </summary>
		/// <remarks>Spaces will be replaced with nothing.</remarks>
		/// <param name="name">Name to check.</param>
		/// <returns>New name.</returns>
		private string EnsureName(string name)
        {
            if (null == name)
            {
                name = String.Empty;
            }
            return name.Replace(" ", "");
        }

		/// <summary>
		/// Begin initialization mode.
		/// </summary>
		/// <remarks>Prevents event handling.</remarks>
        public virtual void BeginInit()
        {
            this._initMode = true;
        }
		
		/// <summary>
		/// End initialization mode.
		/// </summary>
		/// <remarks>Event handling will continue.</remarks>
		/// <param name="forceEvent">Indicates if an Undefined event has to be fired to inform other listeners.</param>
        public virtual void EndInit(bool forceEvent)
        {
            this._initMode = false;
            if (forceEvent)
            {
                this.FireElementChangedEvent(this, new ElementEventArgs(this, ElementChangedAction.Undefined));
            }
        }

        /// <summary>
        /// Gets the parent object.
        /// </summary>
        /// <returns>Always null, method has to be overriden by extended implementation.</returns>
        public virtual IElement GetParent()
        {
        	return null;
        }

        /// <summary>
        /// Fires an element changed event.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="args">ElementEventArgs.</param>
        public void FireElementChangedEvent(object sender, ElementEventArgs args)
        {
        	if (!this.InitMode && !this._isClone && null != this.ElementChanged && null != this._schema)
        	{
        		this.ElementChanged(sender, args);
        	}
        }
        
        /// <summary>
		/// Ensure that the restore data item only gets updated when the element not has been cloned.
		/// </summary>
		/// <param name="physicalSchema">Physical schema.</param>
		/// <param name="physicalElement">Physical element.</param>
		/// <param name="propertyName">Name of the property to update.</param>
		/// <param name="value">Value to update.</param>
	    protected void EnsureUpdateOriginalRestoreDataItem(IPhysicalSchema physicalSchema, PhysicalElement physicalElement, string propertyName, object value)
	    {
	    	if (!physicalElement.IsClone)
	    	{
				DataContainer.Instance.PhysicalData.UpdateRestoreDataItem(physicalSchema, physicalElement.Id, propertyName, value);
	    	}
	    }
        #endregion

        #region IXmlGeneratorWriter Members
        /// <summary>
        /// Writes a the instace of this class to a writer.
        /// </summary>
        /// <param name="writer">Writer object to write into.</param>
        public virtual void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("id", this.Id);
            writer.WriteAttributeString("name", this.Name);
            writer.WriteAttributeString("locationX", this.Location.X.ToString());
            writer.WriteAttributeString("locationY", this.Location.Y.ToString());
            if (null != this._origin)
            {
                writer.WriteAttributeString("origin", this._origin.Id);
            }
        }
        #endregion

        #region ICloneable Members
        /// <summary>
        /// Clones the element.
        /// </summary>
        /// <remarks>Beware: events on clones will be suppressed.</remarks>
        /// <returns>Cloned object.</returns>
        public object Clone()
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream stream = new MemoryStream();
            binaryFormatter.Serialize(stream, this);
            stream.Seek(0, SeekOrigin.Begin);
            PhysicalElement cloneElement = (PhysicalElement)binaryFormatter.Deserialize(stream);
            cloneElement._isClone = true;
            return cloneElement;
        }
        #endregion
	}
}
