using System;
using System.Threading;

namespace Orciid.Core
{
	/// <summary>
	/// Base class for modifiable objects
	/// </summary>
	/// <remarks>
	/// Modifiable objects are objects whose property values are stored in the database.
	/// For better performance, not every property change is written to the database immediately.
	/// This class allows the system to keep track of if an object has modified properties and who
	/// is currently modifying the properties.
	/// <para>The class must be used in the following way:
	/// <list type="bullet">
	/// <item>The constructor of the derived class must call the base constructor. At this
	/// point, the abstract CanCreate() method is called to check if the current user has the necessary
	/// privileges to create a new object of this type.</item>
	/// <item>Whenever a property is about to be modified, the MarkModified() method must be called.
	/// If this is the first time a property changes, the abstract CanModify() method is called to
	/// check if the current user has the necessary privileges to change this object.</item>
	/// <item>After the properties are set, the Update() method must be called, which in turn calls
	/// the abstract method CommitChanges() and resets the modification flag.</item>
	/// <item>If one user/thread starts modifying properties, no other user/thread can modify the object
	/// until the first user calls Update(). The second thread will block until this happens, so there
	/// is a chance for deadlocks! Within one thread, only one object at a time should be modified to avoid
	/// this.</item>
	/// </list>
	/// </para>
	/// </remarks>
	public abstract class ModifiableObject
	{
		/// <summary>
		/// Modified flag
		/// </summary>
		/// <remarks>
		/// This flag is set whenever MarkModified() is called. If this flag is set when Update() is called,
		/// the abstract method CommitChanges() will be called.
		/// </remarks>
		private bool modified = false;
		/// <summary>
		/// Modifiable flag
		/// </summary>
		/// <remarks>
		/// This flag is set to <c>true</c> if the privileges of the currently modifying user have been successfully
		/// checked. It is used to prevent unnecessary, potentially expensive, privilege checks.
		/// </remarks>
		private bool modifiable = false;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Calls CanCreate() to check if the current user has sufficient privileges to create a new object.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if no user has been activated for this thread.</exception>
		/// <exception cref="PrivilegeException">Thrown if the current user does not have sufficient privileges
		/// to create a new object.</exception>
		public ModifiableObject()
		{
			User current = User.CurrentUser();
			if (current == null)
				throw new CoreException("Cannot create objects before activating user");
			if (CanCreate(current))
				modifiable = true;
			else
				throw new PrivilegeException("Insufficient privileges to modify this object.");
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Does not initialize members or check for privileges. This constructor is used
		/// to create user objects from the database.
		/// </remarks>
		/// <param name="init">dummy parameter, should be <c>false</c></param>
		protected ModifiableObject(bool init)
		{
		}

		/// <summary>
		/// Initiate object modification
		/// </summary>
		/// <remarks>
		/// This method initiates the modification of properties of this object. It tries to lock
		/// the object, so no other user can modify it until Update() is called. If the object is already
		/// locked, the method waits until the object unlocks. It then checks the privileges of the current
		/// user.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if no user has been activated for this thread.</exception>
		/// <exception cref="PrivilegeException">Thrown if the current user does not have the necessary
		/// privileges to modify this object.</exception>
		/// <exception cref="CoreException">Thrown if the object could not be locked for modification.</exception>
		protected void BeginUpdate()
		{
			User current = User.CurrentUser();
			if (current == null)
				throw new CoreException("Cannot modify objects before activating user");
			if (!modifiable)
			{
				if (CanModify(current))
					modifiable = true;
				else
					throw new PrivilegeException("Insufficient privileges to modify this object.");
			}
		}

		/// <summary>
		/// Update after modification of properties
		/// </summary>
		/// <remarks>
		/// Once all properties are set and/or modified, Update must be called to commit the changes
		/// to the database. Update will call the abstract method <see cref="CommitChanges"/>, which
		/// has to be overridden and must contain the code to write the object to the database.
		/// If the modified flag is not set, CommitChanges will not be called.
		/// </remarks>
		public void Update()
		{
			BeginUpdate();
			if (modified)
				CommitChanges();
			ResetModified();
		}

		/// <summary>
		/// Mark object modification
		/// </summary>
		/// <remarks>
		/// This method must be called before a property that needs to be written to the database is
		/// modified. MarkModified calls <see cref="BeginUpdate"/> to initialize the modification process.
		/// It also sets the modified flag.
		/// </remarks>
		public virtual void MarkModified()
		{
			BeginUpdate();
			modified = true;
		}

		/// <summary>
		/// Reset object modification flag
		/// </summary>
		/// <remarks>
		/// This method is automatically called by <see cref="Update"/>. It may also be called to abort
		/// the modification, although the calling method must make sure that changes to object properties
		/// are abandoned or reset properly.
		/// </remarks>
		public virtual void ResetModified()
		{
			modified = false;
			modifiable = false;
		}

		/// <summary>
		/// Modified flag
		/// </summary>
		/// <remarks>
		/// This flag is set by the <see cref="MarkModified"/> method. It is reset whenever <see cref="Update"/>
		/// is called.
		/// </remarks>
		/// <value>
		/// <c>true</c> if the object has been modified, but not committed to the database, <c>false</c> otherwise.
		/// </value>
		public bool Modified
		{
			get
			{
				return modified;
			}
		}

		/// <summary>
		/// Checks modification privileges
		/// </summary>
		/// <remarks>
		/// This abstract method is called whenever the object is about to be modified.
		/// </remarks>
		/// <param name="user">The <see cref="User"/> trying to modify the object.</param>
		/// <returns><c>true</c> if the user has sufficient privileges to modify the object, 
		/// <c>false</c> otherwise.</returns>
		public abstract bool CanModify(User user);

		/// <summary>
		/// Checks creation privileges
		/// </summary>
		/// <remarks>
		/// This abstract method is called whenever a new object is about to be created. It is called from
		/// the constructor, so it does not need to be static.
		/// </remarks>
		/// <param name="user">The <see cref="User"/> trying to create the object.</param>
		/// <returns><c>true</c> if the user has sufficient privileges to create the object, 
		/// <c>false</c> otherwise.</returns>
		public abstract bool CanCreate(User user);

		/// <summary>
		/// Commit changes to the database
		/// </summary>
		/// <remarks>
		/// This method writes the current object properties to the database. It does not
		/// need to check for the modification status (it is never called if the object has
		/// not been modified) or the user privileges (they have been checked before this
		/// method is called).
		/// </remarks>
		protected abstract void CommitChanges();

		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier is the primary key used in the database.
		/// </remarks>
		/// <returns>
		/// The internal identifier of this object.
		/// </returns>
		public abstract int GetID();
	}
}
