using System;
using System.Collections;
using System.Data;

namespace Orciid.Core
{
	/// <summary>
	/// User privileges
	/// </summary>
	/// <remarks>
	/// This enumeration is a complete list of all available user privileges in the system. 
	/// The privileges a <see cref="User"/> has are
	/// then determined by the <see cref="AccessControl"/> of the system or the respective collection or slideshow.
	/// Privileges must be "positive", i.e. they must allow something, not deny something.
	/// In general, privileges can be combined with the OR operator <c>|</c> before being passed to any method.
	/// </remarks>
	[Flags]
	public enum Privilege:
		int
	{
		/// <summary>
		/// No access
		/// </summary>
		None					= 0,
		/// <summary>
		/// Can modify ACL
		/// </summary>
		ModifyACL				= 1 << 0,
		/// <summary>
		/// Can create new collections (system-wide privilege)
		/// </summary>
		CreateCollection		= 1 << 1,
		/// <summary>
		/// Can manage a collection
		/// </summary>
		ManageCollection		= 1 << 2,
		/// <summary>
		/// Can delete a collection
		/// </summary>
		DeleteCollection		= 1 << 3,

		// UNUSED ENTRY			= 1 << 4,
		
		/// <summary>
		/// Can modify images in a collection
		/// </summary>
		ModifyImages			= 1 << 5,

		// UNUSED ENTRY			= 1 << 6,
		
		/// <summary>
		/// Can read a collection
		/// </summary>
		ReadCollection			= 1 << 7,
		/// <summary>
		/// Can create new slideshows (system-wide privilege)
		/// </summary>
		CreateSlideshow			= 1 << 8,
		/// <summary>
		/// Can modify a slideshow
		/// </summary>
		ModifySlideshow			= 1 << 9,
		/// <summary>
		/// Can delete a slideshow
		/// </summary>
		DeleteSlideshow			= 1 << 10,
		/// <summary>
		/// Can view a slideshow
		/// </summary>
		ViewSlideshow			= 1 << 11,
		/// <summary>
		/// Can copy a slideshow
		/// </summary>
		CopySlideshow			= 1 << 12,
		/// <summary>
		/// Has access to full sized images in a collection
		/// </summary>
		FullSizedImages			= 1 << 13,
		/// <summary>
		/// Can annonate images
		/// </summary>
		AnnotateImages			= 1 << 14,
		/// <summary>
		/// Can manage user accounts and user groups
		/// </summary>
		ManageUsers				= 1 << 15,
		/// <summary>
		/// Can use ImageViewer
		/// </summary>
		ImageViewerAccess		= 1 << 16,
		/// <summary>
		/// Can make slideshows public
		/// </summary>
		PublishSlideshow		= 1 << 17,
		/// <summary>
		/// Can reset user account passwords
		/// </summary>
		ResetPassword			= 1 << 18,

		// OBSOLETE ENTRY		= 1 << 19,

		// OBSOLETE ENTRY		= 1 << 20,

		/// <summary>
		/// Can manage announcements
		/// </summary>
		ManageAnnouncements		= 1 << 21,

		// UNUSED ENTRY			= 1 << 22,

		/// <summary>
		/// Can manage controlled lists
		/// </summary>
		ManageControlledLists	= 1 << 23,

		// UNUSED ENTRY			= 1 << 24,

		/// <summary>
		/// Can manage collection groups
		/// </summary>
		ManageCollectionGroups	= 1 << 25,

		/// <summary>
		/// Has access to user options page (required to change password)
		/// </summary>
		UserOptions				= 1 << 26,

		/// <summary>
		/// User can add personal images to collection
		/// </summary>
		PersonalImages			= 1 << 27,

		/// <summary>
		/// User can share personal images with others
		/// </summary>
		ShareImages				= 1 << 28,

		/// <summary>
		/// User can suggest personal images for inclusion into the regular collection
		/// </summary>
		SuggestImages			= 1 << 29,

		// UNUSED ENTRY			= 1 << 30,

		/// <summary>
		/// Unknown privilege
		/// </summary>
		/// <remarks>
		/// This privilege flag is used as a placeholder in connection with the
		/// <see cref="PrivilegeException"/> class.
		/// </remarks>
		Unknown					= 1 << 31,
	}
	
	/// <summary>
	/// Interface for access controlled objects
	/// </summary>
	/// <remarks>
	/// Classes that need access control to individual objects need to implement this interface. In addition,
	/// every object needs to have a reference to an accompanying <see cref="AccessControl"/> object and
	/// return that reference through <see cref="IAccessControlled.GetAccessControl"/>. If an object
	/// returns an GetID of less than zero, access privileges for that object will only
	/// be stored in memory, not in the database. This allows creation of 'virtual' access controlled 
	/// objects with fixed access control lists.
	/// </remarks>
	public interface IAccessControlled
	{
		/// <summary>
		/// Type identifier
		/// </summary>
		/// <remarks>
		/// Every class must have its own unique type identifier. A type identifier is a single character
		/// used in connection with the <see cref="GetID"/> as a key in the database.
		/// </remarks>
		/// <returns>
		/// A single character representing the type identifier for this class.
		/// </returns>
		char GetObjectTypeIdentifier();

		/// <summary>
		/// Object identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier for the object. Together with the <see cref="GetObjectTypeIdentifier"/>, this
		/// must be unique in the system.
		/// </remarks>
		/// <returns>
		/// The internal identifier for this object.
		/// </returns>
		int GetID();

		/// <summary>
		/// Associated <see cref="AccessControl"/> object
		/// </summary>
		/// <remarks>
		/// Every object must have an AccessControl object associated with it, which holds the
		/// access control lists for this object.
		/// </remarks>
		/// <returns>
		/// The AccessControl object associated with this object.
		/// </returns>
		IAccessControl GetAccessControl();

		/// <summary>
		/// Return privileges relevant for this class
		/// </summary>
		/// <remarks>
		/// Not all available <see cref="Privilege"/>s are relevant for every
		/// access controlled class. This method must return the privileges that
		/// are relevant for this class.
		/// </remarks>
		/// <returns>A combined <see cref="Privilege"/> of all relevant privileges for
		/// this class</returns>
		Privilege GetRelevantPrivileges();

		/// <summary>
		/// The owner of the object
		/// </summary>
		/// <remarks>
		/// The owner of the object has unlimited access to the object.
		/// </remarks>
		/// <returns>The internal identifier of the owner of the object, or <c>0</c>
		/// if the object does not have an owner.</returns>
		int GetOwner();

		/// <summary>
		/// The object's title
		/// </summary>
		/// <remarks>
		/// Every access controlled object must provide a title to appear in
		/// the user interface.
		/// </remarks>
		/// <returns>The title of the object</returns>
		string GetTitle();

		/// <summary>
		/// Check for ModifyACL privilege
		/// </summary>
		/// <remarks>
		/// The ModifyACL privilege is handled slightly differently from other privileges,
		/// since for some classes (e.g. <see cref="Slideshow"/>) modification of the ACL
		/// may not be allowed, even though a user has the ModifyACL privilege.  You should
		/// always use this method instead of testing for <see cref="Privilege.ModifyACL"/>
		/// directly.
		/// </remarks>
		/// <param name="user">The user to check the privilege for</param>
		/// <param name="clearing">Set to <c>true</c> if the ACL is being cleared because
		/// the related object is being deleted, this eases access restrictions for
		/// modifying the ACL</param>
		/// <returns><c>true</c> if the specified user is allowed to modify the access control
		/// list for this object, <c>false</c> otherwise.</returns>
		bool CanModifyACL(User user, bool clearing);
	}

	/// <summary>
	/// Privilege Assignee interface
	/// </summary>
	/// <remarks>
	/// This interface contains methods common to classes that can be assigned privileges, e.g.
	/// <see cref="User"/> and <see cref="UserGroup"/>
	/// </remarks>
	public interface IPrivilegeAssignee
	{
		/// <summary>
		/// Name
		/// </summary>
		/// <remarks>
		/// The returned value should be a name of the object that a list of objects can be sorted by
		/// </remarks>
		/// <returns>The name of the object</returns>
		string GetName();

		/// <summary>
		/// Identifier
		/// </summary>
		/// <remarks>
		/// The unique identifier of this object
		/// </remarks>
		/// <returns>The identifier of this object</returns>
		int GetID();
	}

	/// <summary>
	/// Generic access control interface
	/// </summary>
	/// <remarks>
	/// Some access controlled objects do not need a full-blown access control object, they
	/// can create new classes that implement this interface to create new behaviors.
	/// </remarks>
	public interface IAccessControl
	{
/*		/// <summary>
		/// Return granted user privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> the user has for the controlled object.
		/// </remarks>
		/// <param name="userid">The internal identifier of the 
		/// <see cref="User"/> whose privileges to return.</param>
		/// <returns>The <see cref="User"/>'s <see cref="Privilege"/></returns>
		Privilege GetGrantedUserPrivileges(int userid);

		/// <summary>
		/// Return denied user privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> that are denied for the 
		/// user for the controlled object.
		/// </remarks>
		/// <param name="userid">The internal identifier of the 
		/// <see cref="User"/> whose privileges to return.</param>
		/// <returns>The <see cref="User"/>'s denied <see cref="Privilege"/></returns>
		Privilege GetDeniedUserPrivileges(int userid);

		/// <summary>
		/// Return granted group privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> the user group has for the controlled object.
		/// </remarks>
		/// <param name="groupid">The internal identifier of the <see cref="UserGroup"/> whose privileges to return.</param>
		/// <returns>An <see cref="UserGroup"/>'s <see cref="Privilege"/>.</returns>
		Privilege GetGrantedUserGroupPrivileges(int groupid);

		/// <summary>
		/// Return denied group privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> that are denied to the user group 
		/// for the controlled object.
		/// </remarks>
		/// <param name="groupid">The internal identifier of the <see cref="UserGroup"/> 
		/// whose privileges to return.</param>
		/// <returns>Denied <see cref="Privilege"/> for the <see cref="UserGroup"/>.</returns>
		Privilege GetDeniedUserGroupPrivileges(int groupid);
*/
		/// <summary>
		/// Return granted privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the privileges that are granted to the privilege assignee
		/// for the controlled object.
		/// </remarks>
		/// <param name="i">The privilege assignee (usually a <see cref="User"/>
		/// or <see cref="UserGroup"/>)</param>
		/// <returns>The granted privileges</returns>
		Privilege GetGrantedPrivileges(IPrivilegeAssignee i);
		
		/// <summary>
		/// Return denied privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the privileges that are denied to the privilege assignee
		/// for the controlled object.
		/// </remarks>
		/// <param name="i">The privilege assignee (usually a <see cref="User"/>
		/// or <see cref="UserGroup"/>)</param>
		/// <returns>The denied privileges</returns>
		Privilege GetDeniedPrivileges(IPrivilegeAssignee i);

		/// <summary>
		/// Set <see cref="Privilege"/> for users for this controlled object
		/// </summary>
		/// <remarks>
		/// This method sets a <see cref="Privilege"/> for every <see cref="User"/> given in the parameter list.
		/// These changes are written to the database immediately.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if the controlled object is newly created and has
		/// not been written to the database yet.</exception>
		/// <param name="granted">The granted <see cref="Privilege"/> to assign to every specified user</param>
		/// <param name="denied">The denied <see cref="Privilege"/> to assign to every specified user</param>
		/// <param name="userids">The internal identifiers of the users to assign the privileges to</param>
		void SetUserPrivilege(Privilege granted, Privilege denied, params int[] userids);

		/// <summary>
		/// Set <see cref="Privilege"/> for user groups for this controlled object
		/// </summary>
		/// <remarks>
		/// This method sets a <see cref="Privilege"/> for every <see cref="UserGroup"/> given in the parameter list.
		/// These changes are written to the database immediately.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if the controlled object is newly created and has
		/// not been written to the database yet.</exception>
		/// <param name="granted">The <see cref="Privilege"/> to grant to every specified user group</param>
		/// <param name="denied">The <see cref="Privilege"/> to deny to every specified user group</param>
		/// <param name="groupids">The internal identifiers of the user groups to assign the privileges to</param>
		void SetUserGroupPrivilege(Privilege granted, Privilege denied, params int[] groupids);

		/// <summary>
		/// Give additional <see cref="Privilege"/> to a <see cref="User"/>s
		/// </summary>
		/// <remarks>
		/// This method grants additional privileges for an item to a User. Privileges already
		/// granted are not removed or replaced.
		/// </remarks>
		/// <param name="granted">The privileges to grant</param>
		/// <param name="denied">The privileges to deny</param>
		/// <param name="userids">The internal identifiers of the users to assign the privileges</param>
		void AddUserPrivilege(Privilege granted, Privilege denied, params int[] userids);
	
		/// <summary>
		/// Give additional <see cref="Privilege"/> to <see cref="UserGroup"/>s
		/// </summary>
		/// <remarks>
		/// This method grants additional privileges for an item to a User group. Privileges already
		/// granted are not removed or replaced.
		/// </remarks>
		/// <param name="granted">The privileges to grant</param>
		/// <param name="denied">The privileges to deny</param>
		/// <param name="groupids">The internal identifiers of the user groups to assign the privileges</param>
		void AddUserGroupPrivilege(Privilege granted, Privilege denied, params int[] groupids);

		/// <summary>
		/// Get list of users with assigned privileges on this object
		/// </summary>
		/// <remarks>
		/// This method returns a list of internal identifiers of all <see cref="User"/>s
		/// that have some privileges assigned for this object.
		/// </remarks>
		/// <returns>An array of internal user identifiers</returns>
		int[] GetPrivilegedUsers();

		/// <summary>
		/// Get list of groups with assigned privileges on this object
		/// </summary>
		/// <remarks>
		/// This method returns a list of internal identifiers of all <see cref="UserGroup"/>s
		/// that have some privileges assigned for this object.
		/// </remarks>
		/// <returns>An array of internal group identifiers</returns>
		int[] GetPrivilegedGroups();
	}

	/// <summary>
	/// Object Access Control
	/// </summary>
	/// <remarks>
	/// This class holds the access control information for object instances of classes implementing
	/// the <see cref="IAccessControlled"/> interface. Every object instance of such a class must
	/// have an AccessControl object associated with it.
	/// </remarks>
	public class AccessControl: IAccessControl
	{

		private struct AssignedPrivilege
		{
			public Privilege granted;
			public Privilege denied;

			public static bool operator==(AssignedPrivilege a, AssignedPrivilege b)
			{
				return (a.granted == b.granted && a.denied == b.denied);
			}

			public static bool operator!=(AssignedPrivilege a, AssignedPrivilege b)
			{
				return (a.granted != b.granted || a.denied != b.denied);
			}

			public override bool Equals(object o)
			{
				return (o is AssignedPrivilege && (AssignedPrivilege)o == this);
			}

			public override int GetHashCode()
			{
				return base.GetHashCode();
			}

		}

		/// <summary>
		/// List of users and their privileges for this item
		/// </summary>
		/// <remarks>
		/// The users' internal identifiers are the keys for this Hashtable. ArrayLists holding
		/// privilege objects are the values.
		/// </remarks>
		private Hashtable userprivileges = new Hashtable();
		/// <summary>
		/// List of user groups and their privileges for this object
		/// </summary>
		/// <remarks>
		/// The groups' internal identifiers are the keys for this Hashtable. ArrayLists holding
		/// privilege objects are the values.
		/// </remarks>
		private Hashtable groupprivileges = new Hashtable();
		/// <summary>
		/// Object whose access is being controlled
		/// </summary>
		/// <remarks>
		/// This property references the object which is being controlled by this AccessControl object.
		/// </remarks>
		private IAccessControlled parent;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// The constructor reads the user and group lists and privileges for the given access controlled object
		/// from the database.
		/// </remarks>
		/// <param name="p">The object whose access is being controlled</param>
		public AccessControl(IAccessControlled p)
		{
			parent = p;
			if (parent.GetID() != 0)
				ReadACL();
		}

		/// <summary>
		/// Access controlled object
		/// </summary>
		/// <remarks>
		/// Every access control list is linked to an access controlled object
		/// </remarks>
		/// <returns>The access controlled object for this access control list</returns>
		public IAccessControlled GetParent()
		{
			return parent;
		}

		private void ReadACL()
		{
			userprivileges.Clear();
			groupprivileges.Clear();
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT UserID,GroupID,GrantPriv,DenyPriv FROM AccessControl 
					WHERE ObjectType={objecttype} AND ObjectID={objectid}");
				query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
				query.AddParam("objectid", parent.GetID());
				DataTable table = conn.SelectQuery(query);
				foreach (DataRow row in table.Rows)
				{
					int userid = conn.DataToInt(row["UserID"], 0);
					int groupid = conn.DataToInt(row["GroupID"], 0);
					AssignedPrivilege privilege;
					privilege.granted = (Privilege)conn.DataToInt(row["GrantPriv"], 0);
					privilege.denied = (Privilege)conn.DataToInt(row["DenyPriv"], 0);
					if (userid != 0 && (privilege.granted != Privilege.None || privilege.denied != Privilege.None))
						userprivileges[userid] = privilege;
					if (groupid != 0 && (privilege.granted != Privilege.None || privilege.denied != Privilege.None))
						groupprivileges[groupid] = privilege;
				}
			}
		}

		/// <summary>
		/// Create privilege entries for new object
		/// </summary>
		/// <remarks>
		/// This method copies a set of rows in the database specifying the default privileges
		/// for a class.  This set of rows must be identified by the correct <c>ObjectType</c>
		/// and an <c>ObjectID</c> of <c>-1</c>.
		/// </remarks>
		public void GenerateDefaultPrivileges()
		{
			if (parent.GetID() != 0)
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"INSERT INTO AccessControl 
						(ObjectType,ObjectID,UserID,GroupID,GrantPriv,DenyPriv) 
						SELECT ObjectType,{parentid},UserID,GroupID,GrantPriv,DenyPriv 
						FROM AccessControl WHERE ObjectType={objecttype} AND ObjectID=-1");
					query.AddParam("parentid", parent.GetID());
					query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
					conn.ExecQuery(query);
				}
			ReadACL();
		}

		/// <summary>
		/// Return granted user privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> the user has for the controlled object.
		/// </remarks>
		/// <param name="userid">The internal identifier of the 
		/// <see cref="User"/> whose privileges to return.</param>
		/// <returns>The <see cref="User"/>'s <see cref="Privilege"/></returns>
		internal Privilege GetGrantedUserPrivileges(int userid)
		{
			if (userprivileges.ContainsKey(userid))
				return ((AssignedPrivilege)userprivileges[userid]).granted;
			else
				return Privilege.None;
		}

		/// <summary>
		/// Return denied user privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> that are denied for the 
		/// user for the controlled object.
		/// </remarks>
		/// <param name="userid">The internal identifier of the 
		/// <see cref="User"/> whose privileges to return.</param>
		/// <returns>The <see cref="User"/>'s denied <see cref="Privilege"/></returns>
		internal Privilege GetDeniedUserPrivileges(int userid)
		{
			if (userprivileges.ContainsKey(userid))
				return ((AssignedPrivilege)userprivileges[userid]).denied;
			else
				return Privilege.None;
		}

		/// <summary>
		/// Return granted group privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> the user group has for the controlled object.
		/// </remarks>
		/// <param name="groupid">The internal identifier of the <see cref="UserGroup"/> whose privileges to return.</param>
		/// <returns>An <see cref="UserGroup"/>'s <see cref="Privilege"/>.</returns>
		internal Privilege GetGrantedUserGroupPrivileges(int groupid)
		{
			if (groupprivileges.ContainsKey(groupid))
				return ((AssignedPrivilege)groupprivileges[groupid]).granted;
			else
				return Privilege.None;
		}

		/// <summary>
		/// Return denied group privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="Privilege"/> that are denied to the user group 
		/// for the controlled object.
		/// </remarks>
		/// <param name="groupid">The internal identifier of the <see cref="UserGroup"/> 
		/// whose privileges to return.</param>
		/// <returns>Denied <see cref="Privilege"/> for the <see cref="UserGroup"/>.</returns>
		internal Privilege GetDeniedUserGroupPrivileges(int groupid)
		{
			if (groupprivileges.ContainsKey(groupid))
				return ((AssignedPrivilege)groupprivileges[groupid]).denied;
			else
				return Privilege.None;
		}

		/// <summary>
		/// Return granted privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the privileges that are granted to the privilege assignee
		/// for the controlled object.
		/// </remarks>
		/// <param name="i">The privilege assignee (usually a <see cref="User"/>
		/// or <see cref="UserGroup"/>)</param>
		/// <returns>The granted privileges</returns>
		public Privilege GetGrantedPrivileges(IPrivilegeAssignee i)
		{
			if (i is User)
				return GetGrantedUserPrivileges(i.GetID());
			if (i is UserGroup)
				return GetGrantedUserGroupPrivileges(i.GetID());
			throw new CoreException("Invalid object type");
		}

		/// <summary>
		/// Return denied privileges for the controlled object
		/// </summary>
		/// <remarks>
		/// This method returns the privileges that are denied to the privilege assignee
		/// for the controlled object.
		/// </remarks>
		/// <param name="i">The privilege assignee (usually a <see cref="User"/>
		/// or <see cref="UserGroup"/>)</param>
		/// <returns>The denied privileges</returns>
		public Privilege GetDeniedPrivileges(IPrivilegeAssignee i)
		{
			if (i is User)
				return GetDeniedUserPrivileges(i.GetID());
			if (i is UserGroup)
				return GetDeniedUserGroupPrivileges(i.GetID());
			throw new CoreException("Invalid object type");
		}

		private void RequireModifyACL(bool clearing)
		{
			if (parent == null || !parent.CanModifyACL(User.CurrentUser(), clearing))
				throw new PrivilegeException("Cannot modify Access Control List");
		}

		/// <summary>
		/// Get list of users with assigned privileges on this object
		/// </summary>
		/// <remarks>
		/// This method returns a list of internal identifiers of all <see cref="User"/>s
		/// that have some privileges assigned for this object.
		/// </remarks>
		/// <returns>An array of internal user identifiers</returns>
		public int[] GetPrivilegedUsers()
		{
			RequireModifyACL(false);
			int[] ids = new int[userprivileges.Keys.Count];
			userprivileges.Keys.CopyTo(ids, 0);
			return ids;
		}

		/// <summary>
		/// Get list of groups with assigned privileges on this object
		/// </summary>
		/// <remarks>
		/// This method returns a list of internal identifiers of all <see cref="UserGroup"/>s
		/// that have some privileges assigned for this object.
		/// </remarks>
		/// <returns>An array of internal group identifiers</returns>
		public int[] GetPrivilegedGroups()
		{
			RequireModifyACL(false);
			int[] ids = new int[groupprivileges.Keys.Count];
			groupprivileges.Keys.CopyTo(ids, 0);
			return ids;
		}

		/// <summary>
		/// Remove all privileges
		/// </summary>
		/// <remarks>
		/// This method removes all privileges set for users and user groups.
		/// </remarks>
		public void Clear()
		{
			RequireModifyACL(true);
			if (parent.GetID() != 0)
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"DELETE FROM AccessControl 
						WHERE ObjectType={objecttype} AND ObjectID={objectid}");
					query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
					query.AddParam("objectid", parent.GetID());
					conn.ExecQuery(query);
				}
			userprivileges.Clear();
			groupprivileges.Clear();
		}

		/// <summary>
		/// Set <see cref="Privilege"/> for users for this controlled object
		/// </summary>
		/// <remarks>
		/// This method sets a <see cref="Privilege"/> for every <see cref="User"/> given in the parameter list.
		/// These changes are written to the database immediately.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if the controlled object is newly created and has
		/// not been written to the database yet.</exception>
		/// <param name="granted">The granted <see cref="Privilege"/> to assign to every specified user</param>
		/// <param name="denied">The denied <see cref="Privilege"/> to assign to every specified user</param>
		/// <param name="userids">The internal identifiers of the users to assign the privileges to</param>
		public void SetUserPrivilege(Privilege granted, Privilege denied, params int[] userids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int userid in userids)
					if (userprivileges.ContainsKey(userid))
					{
						if (granted != Privilege.None || denied != Privilege.None)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND UserID={userid}");
							query.AddParam("grantpriv", granted);
							query.AddParam("denypriv", denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("userid", userid);
							conn.ExecQuery(query);

							AssignedPrivilege priv;
							priv.granted = granted;
							priv.denied = denied;
							userprivileges[userid] = priv;
						}
						else
						{
							query = new Query(conn,
								@"DELETE FROM AccessControl 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND UserID={userid}");
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("userid", userid);
							conn.ExecQuery(query);

							userprivileges.Remove(userid);
						}
					}
					else if (granted != Privilege.None || denied != Privilege.None)
					{
						query = new Query(conn,
							@"INSERT INTO AccessControl (ObjectType,ObjectID,UserID,GrantPriv,DenyPriv) 
							VALUES ({objecttype},{objectid},{userid},{grantpriv},{denypriv})");
						query.AddParam("grantpriv", granted);
						query.AddParam("denypriv", denied);
						query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
						query.AddParam("objectid", pid);
						query.AddParam("userid", userid);
						conn.ExecQuery(query);

						AssignedPrivilege priv;
						priv.granted = granted;
						priv.denied = denied;
						userprivileges.Add(userid, priv);
					}
		}

		/// <summary>
		/// Set <see cref="Privilege"/> for user groups for this controlled object
		/// </summary>
		/// <remarks>
		/// This method sets a <see cref="Privilege"/> for every <see cref="UserGroup"/> given in the parameter list.
		/// These changes are written to the database immediately.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if the controlled object is newly created and has
		/// not been written to the database yet.</exception>
		/// <param name="granted">The <see cref="Privilege"/> to grant to every specified user group</param>
		/// <param name="denied">The <see cref="Privilege"/> to deny to every specified user group</param>
		/// <param name="groupids">The internal identifiers of the user groups to assign the privileges to</param>
		public void SetUserGroupPrivilege(Privilege granted, Privilege denied, params int[] groupids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int groupid in groupids)
					if (groupprivileges.ContainsKey(groupid))
					{
						if (granted != Privilege.None || denied != Privilege.None)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND GroupID={groupid}");
							query.AddParam("grantpriv", granted);
							query.AddParam("denypriv", denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("groupid", groupid);
							conn.ExecQuery(query);

							AssignedPrivilege priv;
							priv.granted = granted;
							priv.denied = denied;
							groupprivileges[groupid] = priv;
						}
						else
						{
							query = new Query(conn,
								@"DELETE FROM AccessControl 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND GroupID={groupid}");
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("groupid", groupid);
							conn.ExecQuery(query);

							groupprivileges.Remove(groupid);
						}
					}
					else if (granted != Privilege.None || denied != Privilege.None)
					{
						query = new Query(conn,
							@"INSERT INTO AccessControl (ObjectType,ObjectID,GroupID,GrantPriv,DenyPriv) 
							VALUES ({objecttype},{objectid},{groupid},{grantpriv},{denypriv})");
						query.AddParam("grantpriv", granted);
						query.AddParam("denypriv", denied);
						query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
						query.AddParam("objectid", pid);
						query.AddParam("groupid", groupid);
						conn.ExecQuery(query);

						AssignedPrivilege priv;
						priv.granted = granted;
						priv.denied = denied;
						groupprivileges.Add(groupid, priv);
					}
		}

		/// <summary>
		/// Give additional <see cref="Privilege"/> to a <see cref="User"/>s
		/// </summary>
		/// <remarks>
		/// This method grants additional privileges for an item to a User. Privileges already
		/// granted are not removed or replaced.
		/// </remarks>
		/// <param name="granted">The privileges to grant</param>
		/// <param name="denied">The privileges to deny</param>
		/// <param name="userids">The internal identifiers of the users to assign the privileges</param>
		public void AddUserPrivilege(Privilege granted, Privilege denied, params int[] userids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			if (granted == Privilege.None && denied == Privilege.None)
				return;
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int userid in userids)
					if (userprivileges.ContainsKey(userid))
					{
						AssignedPrivilege oldpriv = (AssignedPrivilege)userprivileges[userid];
						AssignedPrivilege newpriv;
						newpriv.granted = granted | oldpriv.granted;
						newpriv.denied = denied | oldpriv.denied;
						if (newpriv != oldpriv)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND UserID={userid}");
							query.AddParam("grantpriv", newpriv.granted);
							query.AddParam("denypriv", newpriv.denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("userid", userid);
							conn.ExecQuery(query);

							userprivileges[userid] = newpriv;
						}
					}
					else
					{
						query = new Query(conn,
							@"INSERT INTO AccessControl (ObjectType,ObjectID,UserID,GrantPriv,DenyPriv) 
							VALUES ({objecttype},{objectid},{userid},{grantpriv},{denypriv})");
						query.AddParam("grantpriv", granted);
						query.AddParam("denypriv", denied);
						query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
						query.AddParam("objectid", pid);
						query.AddParam("userid", userid);
						conn.ExecQuery(query);
						
						AssignedPrivilege priv;
						priv.granted = granted;
						priv.denied = denied;
						userprivileges.Add(userid, priv);
					}		
		}

		/// <summary>
		/// Give additional <see cref="Privilege"/> to <see cref="UserGroup"/>s
		/// </summary>
		/// <remarks>
		/// This method grants additional privileges for an item to a User group. Privileges already
		/// granted are not removed or replaced.
		/// </remarks>
		/// <param name="granted">The privileges to grant</param>
		/// <param name="denied">The privileges to deny</param>
		/// <param name="groupids">The internal identifiers of the user groups to assign the privileges</param>
		public void AddUserGroupPrivilege(Privilege granted, Privilege denied, params int[] groupids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			if (granted == Privilege.None && denied == Privilege.None)
				return;
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int groupid in groupids)
					if (groupprivileges.ContainsKey(groupid))
					{
						AssignedPrivilege oldpriv = (AssignedPrivilege)groupprivileges[groupid];
						AssignedPrivilege newpriv;
						newpriv.granted = granted | oldpriv.granted;
						newpriv.denied = denied | oldpriv.denied;
						if (newpriv != oldpriv)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND GroupID={groupid}");
							query.AddParam("grantpriv", newpriv.granted);
							query.AddParam("denypriv", newpriv.denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("groupid", groupid);
							conn.ExecQuery(query);

							groupprivileges[groupid] = newpriv;
						}
					}
					else 
					{
						query = new Query(conn,
							@"INSERT INTO AccessControl (ObjectType,ObjectID,GroupID,GrantPriv,DenyPriv) 
							VALUES ({objecttype},{objectid},{groupid},{grantpriv},{denypriv})");
						query.AddParam("grantpriv", granted);
						query.AddParam("denypriv", denied);
						query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
						query.AddParam("objectid", pid);
						query.AddParam("groupid", groupid);
						conn.ExecQuery(query);
						
						AssignedPrivilege priv;
						priv.granted = granted;
						priv.denied = denied;
						groupprivileges.Add(groupid, priv);
					}
		}

		/// <summary>
		/// Remove privileges on an item from a User
		/// </summary>
		/// <remarks>
		/// This method removes privileges for an item from users. The users do not have to have
		/// the privileges that are being removed. 
		/// </remarks>
		/// <param name="granted">The granted privileges to remove</param>
		/// <param name="denied">The denied privileges to remove</param>
		/// <param name="userids">The internal identifiers of the users to remove the privileges from</param>
		public void RemoveUserPrivilege(Privilege granted, Privilege denied, params int[] userids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			if (granted == Privilege.None && denied == Privilege.None)
				return;
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int userid in userids)
					if (userprivileges.ContainsKey(userid))
					{
						AssignedPrivilege oldpriv = (AssignedPrivilege)userprivileges[userid];
						AssignedPrivilege newpriv;
						newpriv.granted = ~granted & oldpriv.granted;
						newpriv.denied = ~denied & oldpriv.denied;
						if (newpriv.granted == Privilege.None && newpriv.denied == Privilege.None)
						{
							query = new Query(conn,
								@"DELETE FROM AccessControl WHERE ObjectType={objecttype} 
								AND ObjectID={objectid} AND UserID={userid}");
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("userid", userid);
							conn.ExecQuery(query);

							userprivileges.Remove(userid);
						}
						else if (newpriv != oldpriv)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND UserID={userid}");
							query.AddParam("grantpriv", newpriv.granted);
							query.AddParam("denypriv", newpriv.denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("userid", userid);
							conn.ExecQuery(query);

							userprivileges[userid] = newpriv;
						}
					}
		}

		/// <summary>
		/// Remove privileges on an item from a UserGroup
		/// </summary>
		/// <remarks>
		/// This method removes privileges for an item from user groups. The user groups do not have to have
		/// the privileges that are being removed.
		/// </remarks>
		/// <param name="granted">The granted privileges to remove</param>
		/// <param name="denied">The denied privileges to remove</param>
		/// <param name="groupids">The internal identifiers of the user groups to remove the privileges from</param>
		public void RemoveUserGroupPrivilege(Privilege granted, Privilege denied, params int[] groupids)
		{
			RequireModifyACL(false);
			int pid = parent.GetID();
			if (pid == 0)
				throw new CoreException("Object must be written to database first.");
			if (granted == Privilege.None && denied == Privilege.None)
				return;
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
				foreach (int groupid in groupids)
					if (groupprivileges.ContainsKey(groupid))
					{
						AssignedPrivilege oldpriv = (AssignedPrivilege)groupprivileges[groupid];
						AssignedPrivilege newpriv;
						newpriv.granted = ~granted & oldpriv.granted;
						newpriv.denied = ~denied & oldpriv.denied;
						if (newpriv.granted == Privilege.None && newpriv.denied == Privilege.None)
						{
							query = new Query(conn,
								@"DELETE FROM AccessControl WHERE ObjectType={objecttype} 
								AND ObjectID={objectid} AND GroupID={groupid}");
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("groupid", groupid);
							conn.ExecQuery(query);

							groupprivileges.Remove(groupid);
						}
						if (newpriv != oldpriv)
						{
							query = new Query(conn,
								@"UPDATE AccessControl SET GrantPriv={grantpriv},DenyPriv={denypriv} 
								WHERE ObjectType={objecttype} AND ObjectID={objectid} AND GroupID={groupid}");
							query.AddParam("grantpriv", newpriv.granted);
							query.AddParam("denypriv", newpriv.denied);
							query.AddParam("objecttype", parent.GetObjectTypeIdentifier());
							query.AddParam("objectid", pid);
							query.AddParam("groupid", groupid);
							conn.ExecQuery(query);

							groupprivileges[groupid] = newpriv;
						}
					}
		}

		/// <summary>
		/// Retrieve an access controlled object
		/// </summary>
		/// <remarks>
		/// This methods allows the retrieval of an access controlled object by its object type
		/// identifier and object identifier.  If the object identifier is <c>-1</c>, a generic
		/// object with the default access control list is returned.  This allows the modification
		/// of the access control defaults for the different object types.
		/// </remarks>
		/// <param name="objecttypeid">The object type identifier</param>
		/// <param name="objectid">The object identifier, or <c>-1</c></param>
		/// <returns>The access controlled object</returns>
		public static IAccessControlled GetAccessControlledObject(char objecttypeid, int objectid)
		{
			if (objectid == -1)
			{
				AccessControl acl = new AccessControlDefaults(objecttypeid);
				return acl.GetParent();
			}
			switch (objecttypeid)
			{
				case 'O':
					return SystemAccess.GetSystemAccess();
				case 'C':
					return Collection.GetByID(objectid);
				case 'S':
					return Slideshow.GetByID(objectid);
				default:
					throw new CoreException("Invalid ObjectTypeID");
			}
		}
	}

	/// <summary>
	/// Support class for access control defaults
	/// </summary>
	/// <remarks>
	/// This class is used to modify the default access control lists for the different
	/// access controlled classes.
	/// </remarks>
	public class AccessControlDefaults:
		AccessControl
	{

		private class DummyAccessControlled:
			IAccessControlled
		{
			private char objecttypeid;
			private AccessControl accesscontrol;

			public DummyAccessControlled(char id)
			{
				objecttypeid = id;
				accesscontrol = new AccessControl(this);
			}

			public char GetObjectTypeIdentifier()
			{
				return objecttypeid;
			}

			public int GetID()
			{
				return -1;
			}

			public IAccessControl GetAccessControl()
			{
				return accesscontrol;
			}

			public Privilege GetRelevantPrivileges()
			{
				switch (objecttypeid)
				{
					case 'O':
						return SystemAccess.GetSystemAccess().GetRelevantPrivileges();
					case 'C':
						return new Collection(false).GetRelevantPrivileges();
					case 'S':
						return new Slideshow(false).GetRelevantPrivileges();
					default:
						throw new CoreException("Invalid ObjectTypeID");
				}
			}

			public int GetOwner()
			{
				return 0;
			}

			public string GetTitle()
			{
				switch (objecttypeid)
				{
					case 'O':
						return "Default System Access";
					case 'C':
						return "Default Collection";
					case 'S':
						return "Default Slideshow";
					default:
						throw new CoreException("Invalid ObjectTypeID");
				}
			}

			public bool CanModifyACL(User user, bool clearing)
			{
				return true;
			}
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates an instance to modify the default access control list for a 
		/// particular object type.
		/// </remarks>
		/// <param name="id">The object type identifier of the desired access controlled 
		/// class</param>
		public AccessControlDefaults(char id):
			base(new DummyAccessControlled(id))
		{
			User.RequirePrivilege(Privilege.ModifyACL);
		}
	}

	/// <summary>
	/// System-wide privilege control
	/// </summary>
	/// <remarks>
	/// This is a helper class representing the system. It is used to hold system-wide roles and access privileges
	/// for users and user groups. It is implemented as a singleton class.
	/// Privileges granted on this object also apply to every other access controlled object in the
	/// system, e.g. if a user is granted the ModifyACL privilege on the SystemAccess object, she
	/// has the ModifyACL privilege on every object in the system. This design-choice is based on
	/// the fact that there is almost always a way to gain privileges on all objects by having
	/// privileges on the SystemAccess object anyway. This may change in later versions.
	/// </remarks>
	public class SystemAccess:
		CachableObject, IAccessControlled
	{
		/// <summary>
		/// The access control object associated with the system
		/// </summary>
		private AccessControl accesscontrol;

		private const string classidentifier = "SystemAccess";
		private const int objectid = 1;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// This constructor is private to enforce the singleton pattern. It creates the associated 
		/// access control object.
		/// </remarks>
		private SystemAccess():
			base(false)
		{
			accesscontrol = new AccessControl(this);
		}

		/// <summary>
		/// Object type identifier
		/// </summary>
		/// <remarks>
		/// The object type identifier for this class is <c>'O'</c>.
		/// </remarks>
		/// <returns>The object type identifier for this class, <c>'O'</c></returns>
		public char GetObjectTypeIdentifier()
		{
			return 'O';
		}

		/// <summary>
		/// Class identifier
		/// </summary>
		/// <remarks>
		/// Returns the class identifier for this class.
		/// </remarks>
		/// <returns><c>"SystemAccess"</c></returns>
		protected override string GetClassIdentifier()
		{
			return classidentifier;
		}

		/// <summary>
		/// Object identifier
		/// </summary>
		/// <remarks>
		/// Since this class represents the system, of which there can be only one, the object identifier
		/// is always <c>1</c>.
		/// </remarks>
		/// <returns><c>1</c></returns>
		public override int GetID()
		{
			return objectid;
		}

		/// <summary>
		/// Check creation privilege
		/// </summary>
		/// <remarks>
		/// No user is allowed to create an instance of this class.
		/// </remarks>
		/// <param name="user">The user to check the privilege for</param>
		/// <returns><c>false</c></returns>
		public override bool CanCreate(User user)
		{
			return false;
		}

		/// <summary>
		/// Check modification privilege
		/// </summary>
		/// <remarks>
		/// No user is allowed to modify an instance of this class.
		/// </remarks>
		/// <param name="user">The user to check the privilege for</param>
		/// <returns><c>false</c></returns>
		public override bool CanModify(User user)
		{
			return false;
		}

		/// <summary>
		/// Commit changes
		/// </summary>
		/// <remarks>
		/// This method does nothing.
		/// </remarks>
		protected override void CommitChanges()
		{
		}

		/// <summary>
		/// Access control object
		/// </summary>
		/// <remarks>
		/// This method returns the associated <see cref="AccessControl"/> object.
		/// </remarks>
		/// <returns>The access control object for the system access object.</returns>
		public IAccessControl GetAccessControl()
		{
			return accesscontrol;
		}

		/// <summary>
		/// The owner of the system
		/// </summary>
		/// <remarks>
		/// Since the system does not have an associated owner, this method always returns <c>0</c>.
		/// </remarks>
		/// <returns><c>0</c></returns>
		public int GetOwner()
		{
			return 0;
		}

		/// <summary>
		/// System access object
		/// </summary>
		/// <remarks>
		/// This class is implemented as a singleton class. This method returns the single instance of this class.
		/// </remarks>
		/// <returns>The single instance of the SystemAccess class.</returns>
		public static SystemAccess GetSystemAccess()
		{
			SystemAccess sa = (SystemAccess)GetFromCache(classidentifier, objectid);
			if (sa == null)
			{
				sa = new SystemAccess();
				AddToCache(sa);
			}
			return sa;
		}

		/// <summary>
		/// Return privileges relevant for this class
		/// </summary>
		/// <remarks>
		/// Not all available <see cref="Privilege"/>s are relevant for every
		/// access controlled class. This method must return the privileges that
		/// are relevant for this class.
		/// </remarks>
		/// <returns>A combined <see cref="Privilege"/> of all relevant privileges for
		/// this class</returns>
		public Privilege GetRelevantPrivileges()
		{
			return 
				Privilege.ModifyACL |
				Privilege.CreateCollection |
				Privilege.CreateSlideshow |
				Privilege.PublishSlideshow |
				Privilege.ManageUsers |
				Privilege.ResetPassword |
				Privilege.ManageAnnouncements |
				Privilege.ManageCollectionGroups |
				Privilege.ImageViewerAccess |
				Privilege.UserOptions;
		}

		/// <summary>
		/// Title
		/// </summary>
		/// <remarks>
		/// The title of the system access object
		/// </remarks>
		/// <returns>
		/// <c>"System Access"</c>
		/// </returns>
		public string GetTitle()
		{
			return "System Access";
		}

		/// <summary>
		/// Check for ModifyACL privilege
		/// </summary>
		/// <remarks>
		/// Calls <see cref="User.HasPrivilege(Privilege, IAccessControlled, User)" /> to check for <see cref="Privilege.ModifyACL"/>.
		/// </remarks>
		/// <param name="user">The user to check the privilege for</param>
		/// <param name="clearing">Set to <c>true</c> if the ACL is being cleared because
		/// the related object is being deleted, this eases access restrictions for
		/// modifying the ACL</param>
		/// <returns><c>true</c> if the specified user is allowed to modify the access control
		/// list for this object, <c>false</c> otherwise.</returns>
		public bool CanModifyACL(User user, bool clearing)
		{
			return User.HasPrivilege(Privilege.ModifyACL, this, user);
		}
	}

}
