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

namespace Orciid.Core
{

	/// <summary>
	/// An individual slide within a Slideshow
	/// </summary>
	/// <remarks>
	/// Each slide object represents an individual slide within a <see cref="Slideshow"/>.
	/// </remarks>
	public class Slide
	{
		/// <summary>
		/// The slideshow this slide is in
		/// </summary>
		/// <remarks>
		/// This is <c>null</c> if the slide has been newly created or has been removed from a slideshow.
		/// </remarks>
		internal Slideshow slideshow;

		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// This is the identifier used for this slide in the database. It is <c>0</c> if the slide has
		/// just been created or removed from a slideshow. (A slide cannot exist in the database without
		/// being connected to a slideshow.)
		/// </remarks>
		internal int id;

		/// <summary>
		/// Image represented by this slide
		/// </summary>
		/// <remarks>
		/// Each slide must represent an image from any collection.
		/// </remarks>
		internal ImageIdentifier imageid;

		/// <summary>
		/// Slide annotation
		/// </summary>
		/// <remarks>
		/// Every slide can hold a textual annotation, which is only relevant for the slide within a slideshow,
		/// not for the image itself.
		/// </remarks>
		private string annotation;

		/// <summary>
		/// Scratch space indicator
		/// </summary>
		/// <remarks>
		/// Slideshows can hold additional slides which are not displayed in presentation mode.
		/// In the editor, they should appear in the scratch space.
		/// </remarks>
		private bool scratch;

		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// Creates a new slide
		/// </remarks>
		public Slide()
		{
		}

		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// Returns the internal identifier for this slide. It is <c>0</c> if the slide has
		/// just been created or removed from a slideshow.
		/// </remarks>
		/// <value>
		/// The internal identifier for this slide.
		/// </value>
		public int ID
		{
			get
			{
				return id;
			}
		}

		/// <summary>
		/// Slide image
		/// </summary>
		/// <remarks>
		/// Every slide is connected to exactly one image from any collection in the system.
		/// </remarks>
		/// <value>
		/// The internal identifier of the image used for this slide.
		/// </value>
		public ImageIdentifier ImageID
		{
			get
			{
				return imageid;
			}
			set
			{
				if (slideshow != null)
					slideshow.MarkModified();
				imageid = value;
			}
		}

		/// <summary>
		/// Slide annotation
		/// </summary>
		/// <remarks>
		/// Every slide can be annotated. This annotation is connected to the slide and is distinct from
		/// any annotation the represented image might have by itself or in another slideshow.
		/// </remarks>
		/// <value>
		/// The annotation for this slide image in this slideshow.
		/// </value>
		public string Annotation
		{
			get
			{
				return annotation;
			}
			set
			{
				if (slideshow != null)
					slideshow.MarkModified();
				annotation = value;
			}
		}

		/// <summary>
		/// Scratch space indicator
		/// </summary>
		/// <remarks>
		/// Slideshows can hold additional slides which are not displayed in presentation mode.
		/// In the editor, they should appear in the scratch space.
		/// </remarks>
		/// <value>
		/// A boolean indicating if a slide is part of the slideshow presentation or
		/// the scratch space.
		/// </value>
		public bool Scratch
		{
			get
			{
				return scratch;
			}
			set
			{
				if (slideshow != null)
					slideshow.MarkModified();
				scratch = value;
			}
		}
	}

	/// <summary>
	/// Collection of slides
	/// </summary>
	/// <remarks>
	/// A slideshow holds individual slides representing images from any collection. Every <see cref="Slide"/> 
	/// within a slideshow has a position and can be annotated.
	/// </remarks>
	public class Slideshow:
		ModifiableObject, IAccessControlled, IComparable
	{
		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier of this slideshow. This is <c>0</c> for newly created or deleted slideshows.
		/// </remarks>
		private int id;

		/// <summary>
		/// Slideshow owner
		/// </summary>
		/// <remarks>
		/// This is the internal identifier of the user account this slide show is associated with.
		/// </remarks>
		private int userid;

		/// <summary>
		/// Folder holding the slideshow
		/// </summary>
		/// <remarks>
		/// This is the internal identifier of the folder the slideshow is stored in. This can be <c>0</c> to
		/// signify the slideshow is stored in the root folder of a user's account.
		/// </remarks>
		private int folderid;

		/// <summary>
		/// Title
		/// </summary>
		/// <remarks>
		/// The title of the slideshow. This cannot be <c>null</c> or empty.
		/// </remarks>
		private string title;

		/// <summary>
		/// Access password
		/// </summary>
		/// <remarks>
		/// If an access password is set for a slideshow, it must be provided in order to view the slideshow
		/// if the user does not have manage privileges for the show.
		/// </remarks>
		private string password;

		/// <summary>
		/// Creation date
		/// </summary>
		/// <remarks>
		/// The creation date of the slideshow in the format [M]M/[D]D/YYYY [H]H:MM AM|PM
		/// </remarks>
		private string creationdate;

		/// <summary>
		/// Modification date
		/// </summary>
		/// <remarks>
		/// The last modification date of the slideshow in the format [M]M/[D]D/YYYY [H]H:MM AM|PM
		/// </remarks>
		private string modificationdate;

		/// <summary>
		/// Access control
		/// </summary>
		/// <remarks>
		/// The <see cref="AccessControl"/> object for this slideshow.
		/// </remarks>
		private AccessControl accesscontrol;

		/// <summary>
		/// List of slides
		/// </summary>
		/// <remarks>
		/// The list of slides within this slideshow.
		/// </remarks>
		private ArrayList slides = new ArrayList();

		/// <summary>
		/// Archive flag
		/// </summary>
		/// <remarks>
		/// If this flag is set, access to the slide show is further restricted, independent
		/// of the actual settings in the access control list
		/// </remarks>
		private bool archiveflag;

		private string description;

		private bool slidesloaded = false;

		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// The default constructor for a slideshow. It automatically sets the owner of the slideshow to
		/// the currently registered user.
		/// </remarks>
		public Slideshow():
			base()
		{
			accesscontrol = new AccessControl(this);
			archiveflag = true;
			User user = User.CurrentUser();
			if (user != null)
				userid = user.ID;
		}

		/// <summary>
		/// Dummy constructor
		/// </summary>
		/// <remarks>
		/// This constructor is called when creating a slideshow object to be setup from database values.
		/// </remarks>
		/// <param name="init">Dummy parameter, should be <c>false</c>.</param>
		internal Slideshow(bool init):
			base(init)
		{
		}

		/// <summary>
		/// Compares the titles of two slide shows
		/// </summary>
		/// <remarks>
		/// This method is implemented for the IComparable interface. It allows slide shows
		/// to be sorted by their title.
		/// </remarks>
		/// <param name="c">Another slide show to compare this slide show's title to</param>
		/// <returns>The result of the comparison of the slide shows' titles</returns>
		public int CompareTo(object c)
		{
			Slideshow s = c as Slideshow;
			if (s != null)
				return title.CompareTo(s.title);
			throw new Exception("Slideshow object expected");
		}

		/// <summary>
		/// Retrieve slideshow by internal identifier
		/// </summary>
		/// <remarks>
		/// This method returns a slideshow specified by its internal identifier.
		/// </remarks>
		/// <param name="id">An internal slideshow identifier</param>
		/// <returns>A slideshow object</returns>
		public static Slideshow GetByID(int id) 
		{
			Slideshow[] i = GetByID(new int[] { id });
			if (i != null && i.Length == 1)
				return i[0];
			else
				return null;
		}

		private void LoadSlides()
		{
			if (slidesloaded)
				return;
			slidesloaded = true;
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT ID,ImageID,CollectionID,Annotation,Scratch FROM Slides 
					WHERE SlideshowID={id} ORDER BY DisplayOrder");
				query.AddParam("id", id);
				DataTable table = conn.SelectQuery(query);
				for (int row = 0; row < table.Rows.Count; row++)
				{
					DataRow data = table.Rows[row];
					Slide slide = new Slide();
					slide.id = conn.DataToInt(data["ID"], 0);
					slide.imageid.ID = conn.DataToInt(data["ImageID"], 0);
					slide.imageid.CollectionID = conn.DataToInt(data["CollectionID"], 0);
					slide.Annotation = conn.DataToString(data["Annotation"]);
					slide.Scratch = conn.DataToBool(data["Scratch"], false);
					AddLoading(slide);
				}
			}
		}

		/// <summary>
		/// Retrieve slideshows by internal identifiers
		/// </summary>
		/// <remarks>
		/// This method returns an array of slideshows specified by their internal identifiers.
		/// The length of the resulting array may not be the same length as the list of identifiers in case
		/// not all identifiers are valid or accessible. The order of the slideshows may not correspond to the
		/// order of the identifiers.
		/// </remarks>
		/// <param name="id">An array of internal slideshow identifiers</param>
		/// <returns>An array of slideshow objects.</returns>
		public static Slideshow[] GetByID(params int[] id) 
		{
			User user = User.CurrentUser();
			if (id == null || id.Length == 0)
				return new Slideshow[0];
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT ID,UserID,FolderID,Title,AccessPassword,CreationDate,ModificationDate,
					ArchiveFlag,Description 
					FROM Slideshows WHERE ID IN {ids}");
				query.AddParam("ids", id);
				DataTable table = conn.SelectQuery(query);
				Hashtable slideshows = new Hashtable();
				for (int row = 0; row < table.Rows.Count; row++)
				{
					DataRow data = table.Rows[row];
					Slideshow show = new Slideshow(false);
					show.id = conn.DataToInt(data["ID"], 0);
					show.userid = conn.DataToInt(data["UserID"], 0);
					show.folderid = conn.DataToInt(data["FolderID"], 0);
					show.title = conn.DataToString(data["Title"]);
					show.password = conn.DataToString(data["AccessPassword"]);
					show.creationdate = conn.DataToString(data["CreationDate"]);
					show.modificationdate = conn.DataToString(data["ModificationDate"]);
					show.archiveflag = conn.DataToBool(data["ArchiveFlag"], false);
					show.description = conn.DataToString(data["Description"]);
					show.accesscontrol = new AccessControl(show);
					if (CheckBasicAccess(show))
						slideshows.Add(show.id, show);
				}	
				Slideshow[] temp = new Slideshow[slideshows.Count];
				slideshows.Values.CopyTo(temp, 0);
				Array.Sort(temp);
				return temp;
			}
		}

		/// <summary>
		/// Find a user's slideshows
		/// </summary>
		/// <remarks>
		/// This method returns an array of slideshows for a given user.
		/// </remarks>
		/// <param name="userid">The internal identifier of the user</param>
		/// <returns>An array of slideshows for the specified user.</returns>
		public static Slideshow[] GetByUser(int userid)
		{
			int[] ids;
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					"SELECT ID FROM Slideshows WHERE UserID={userid}");
				query.AddParam("userid", userid);
				DataTable table = conn.SelectQuery(query);
				ids = new int[table.Rows.Count];
				for (int row = 0; row < table.Rows.Count; row++)
					ids[row] = conn.DataToInt(table.Rows[row]["ID"], 0);
			}
			return GetByID(ids);
		}

		/// <summary>
		/// Get a list of slideshows in one folder
		/// </summary>
		/// <remarks>
		/// This method returns an array of slideshows for a given user and folder.
		/// </remarks>
		/// <param name="userid">The internal identifier of the user</param>
		/// <param name="folderid">The internal identifier of the folder</param>
		/// <returns>An array of slideshows in the specified folder.</returns>
		public static Slideshow[] GetByFolder(int userid, int folderid)
		{
			int[] ids;
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT ID FROM Slideshows WHERE UserID={userid} AND FolderID={folderid}");
				query.AddParam("userid", userid);
				query.AddParam("folderid", folderid);
				DataTable table = conn.SelectQuery(query);
				ids = new int[table.Rows.Count];
				for (int row = 0; row < table.Rows.Count; row++)
					ids[row] = conn.DataToInt(table.Rows[row]["ID"], 0);
			}
			return GetByID(ids);
		}

		/// <summary>
		/// Delete slideshow
		/// </summary>
		/// <remarks>
		/// This method deletes the slideshow and resets the modification state. The object should
		/// no longer be used.
		/// </remarks>
		public void Delete()
		{
			User.RequirePrivilege(Privilege.DeleteSlideshow, this);
			accesscontrol.Clear();
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"DELETE FROM Slides WHERE SlideshowID={slideshowid}");
				query.AddParam("slideshowid", id);
				conn.ExecQuery(query);

				query = new Query(conn,
					@"DELETE FROM Slideshows WHERE ID={id}");
				query.AddParam("id", id);
				conn.ExecQuery(query);
			}
			id = 0;
			ResetModified();
		}

		/// <summary>
		/// Delete slideshow when user account is deleted
		/// </summary>
		/// <remarks>
		/// This method deletes the slideshow and resets the modification state. The object should
		/// no longer be used.  This method does not check for the deletion privilege, since a user
		/// manager removing an account may not have access to the slideshow.
		/// </remarks>
		internal void DeleteWithUser()
		{
			accesscontrol.Clear();
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"DELETE FROM Slides WHERE SlideshowID={slideshowid}");
				query.AddParam("slideshowid", id);
				conn.ExecQuery(query);

				query = new Query(conn,
					@"DELETE FROM Slideshows WHERE ID={id}");
				query.AddParam("id", id);
				conn.ExecQuery(query);
			}
			id = 0;
			ResetModified();
		}

		/// <summary>
		/// Check modification privilege
		/// </summary>
		/// <remarks>
		/// This method checks if a specified user has sufficient privileges to modify this object.
		/// </remarks>
		/// <param name="user">The user to check privileges for</param>
		/// <returns><c>true</c> if the User has sufficient privileges to modify this object, 
		/// <c>false</c> otherwise.</returns>
		public override bool CanModify(User user)
		{
			return User.HasPrivilege(Privilege.ModifySlideshow, this, user);
		}

		/// <summary>
		/// Check for ModifyACL privilege
		/// </summary>
		/// <remarks>
        /// Calls <see cref="User.HasPrivilege(Privilege, IAccessControlled, User)" /> to check for <see cref="Privilege.ModifyACL"/>.
		/// In addition, the user must have the <see cref="Privilege.PublishSlideshow"/> privilege
		/// for the system.
		/// </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) && 
				(clearing || // don't check for PublishSlideshow privilege when clearing ACL
				User.HasPrivilege(Privilege.PublishSlideshow, SystemAccess.GetSystemAccess(), user));
		}

		/// <summary>
		/// Check creation privilege
		/// </summary>
		/// <remarks>
		/// This method checks if a specified user has sufficient privileges to create this object.
		/// This method is called from the constructor, so the object already exists and therefore CanCreate
		/// does not need to be static.
		/// </remarks>
		/// <param name="user">The user to check privileges for</param>
		/// <returns><c>true</c> if the User has sufficient privileges to create this object, 
		/// <c>false</c> otherwise.</returns>
		public override bool CanCreate(User user)
		{
			return User.HasPrivilege(Privilege.CreateSlideshow, user);
		}

		/// <summary>
		/// Title
		/// </summary>
		/// <remarks>
		/// The title of a slideshow must be set before it is committed to the database. It cannot be
		/// <c>null</c> or empty.
		/// </remarks>
		/// <value>
		/// The title of the slideshow.
		/// </value>
		/// <exception cref="CoreException">Thrown if the specified title is <c>null</c> or empty.</exception>
		public string Title
		{
			get
			{
				return title;
			}
			set
			{
				if (value != null && value.Length > 0) 
				{
					MarkModified();
					title = (value.Length > 50 ? value.Substring(0, 50) : value);
				}
				else
					throw new CoreException("Title must not be null or empty.");
			}
		}

		/// <summary>
		/// Title
		/// </summary>
		/// <remarks>
		/// The title can also be retrieved using the <see cref="Title"/> property.
		/// </remarks>
		/// <returns>
		/// The title of the slideshow.
		/// </returns>
		public string GetTitle()
		{
			return title;
		}

		/// <summary>
		/// Writes a modified collection object to the database
		/// </summary>
		/// <remarks>After writing the collection to the database, the object is returned
		/// to an unmodifiable state.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if the object cannot be written
		/// to the database.</exception>
		/// <exception cref="CoreException">Thrown if the title or owner for
		/// the collection are not set.</exception>
		protected override void CommitChanges()
		{
			if (title == null || title.Length == 0 || userid == 0)
				throw new CoreException("Title and Owner have to be set for a Collection");
			int result;
			LoadSlides();
			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query;
				if (id == 0) 
				{
					query = new Query(conn,
						@"INSERT INTO Slideshows (UserID,FolderID,Title,AccessPassword,
						CreationDate,ModificationDate,ArchiveFlag,Description) 
						VALUES ({userid},{folderid},{title},{accesspassword},
						{creationdate},{modificationdate},{archiveflag},{description})");
					query.AddParam("userid", userid);
					query.AddParam("folderid", folderid);
					query.AddParam("title", title);
					query.AddParam("accesspassword", password);
					query.AddParam("creationdate", DateTime.Now);
					query.AddParam("modificationdate", DateTime.Now);
					query.AddParam("archiveflag", archiveflag);
					query.AddParam("description", description);
					result = conn.ExecQuery(query);
					if (result == 1)
					{
						id = conn.LastIdentity("Slideshows");
						accesscontrol.GenerateDefaultPrivileges();
					}
					else
						throw new CoreException("Could not write new slideshow object to database.");
				}
				else
				{
					query = new Query(conn,
						@"UPDATE Slideshows SET FolderID={folderid},Title={title},
						AccessPassword={accesspassword},ModificationDate={modificationdate},
						ArchiveFlag={archiveflag},Description={description} 
						WHERE ID={id}");
					query.AddParam("folderid", folderid);
					query.AddParam("title", title);
					query.AddParam("accesspassword", password);
					query.AddParam("modificationdate", DateTime.Now);
					query.AddParam("archiveflag", archiveflag);
					query.AddParam("description", description);
					query.AddParam("id", id);
					result = conn.ExecQuery(query);
					if (result != 1) 
						throw new CoreException("Could not write slideshow object to database");
					if (slides.Count > 0)
					{
						ArrayList ids = new ArrayList();
						foreach (Slide slide in slides)
							if (slide.id != 0)
								ids.Add(slide.id);
						if (ids.Count > 0)
						{
							query = new Query(conn,
								@"DELETE FROM Slides WHERE SlideshowID={slideshowid} 
								AND ID NOT IN {ids}");
							query.AddParam("slideshowid", id);
							query.AddParam("ids", ids);
							conn.ExecQuery(query);
						}
					}
					else
					{
						query = new Query(conn,
							@"DELETE FROM Slides WHERE SlideshowID={slideshowid}");
						query.AddParam("slideshowid", id);
						conn.ExecQuery(query);
					}
				}
				int displayorder = 0;
				foreach (Slide slide in slides)
				{
					if (slide.id > 0)
					{
						query = new Query(conn,
							@"UPDATE Slides SET SlideshowID={slideshowid},ImageID={imageid},
							CollectionID={collectionid},DisplayOrder={displayorder},
							Annotation={annotation},Scratch={scratch} WHERE ID={id}");
						query.AddParam("slideshowid", id);
						query.AddParam("imageid", slide.ImageID.ID);
						query.AddParam("collectionid", slide.ImageID.CollectionID);
						query.AddParam("displayorder", displayorder);
						query.AddParam("annotation", slide.Annotation);
						query.AddParam("scratch", slide.Scratch);
						query.AddParam("id", slide.id);
						conn.ExecQuery(query);
					}
					else
					{
						query = new Query(conn,
							@"INSERT INTO Slides 
							(SlideshowID,ImageID,CollectionID,DisplayOrder,Annotation,Scratch) 
							VALUES ({slideshowid},{imageid},{collectionid},{displayorder},
							{annotation},{scratch})");
						query.AddParam("slideshowid", id);
						query.AddParam("imageid", slide.ImageID.ID);
						query.AddParam("collectionid", slide.ImageID.CollectionID);
						query.AddParam("displayorder", displayorder);
						query.AddParam("annotation", slide.Annotation);
						query.AddParam("scratch", slide.Scratch);
						conn.ExecQuery(query);
						slide.id = conn.LastIdentity("Slides");
					}
					displayorder++;
				}
			}
		}

		/// <summary>
		/// Slides in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns a sorted list of slides in this slideshow. If slides are modified, the 
		/// <see cref="ModifiableObject.Update"/> method of the slideshow must be called to commit the changes to the database.
		/// </remarks>
		/// <param name="pwd">The access password for the slideshow, or <c>null</c> if no password is given
		/// or required.</param>
		/// <returns>A sorted array of slides.</returns>
		/// <exception cref="PrivilegeException">Thrown if the specified password does not match the required password, or if
		/// no password is required but one has been specified.</exception>
		public Slide[] GetSlides(string pwd)
		{
			CheckAccess(pwd);
			LoadSlides();
			Slide[] s = new Slide[slides.Count];
			slides.CopyTo(s);
			return s;
		}

		/// <summary>
		/// Create a copy of this slideshow
		/// </summary>
		/// <remarks>
		/// This method creates a copy of this slideshow.  The title of the new slideshow 
		/// will be prefixed with "Copy of ".  The owner of the new show will be the
		/// current user.
		/// </remarks>
		/// <returns>A copy of this slideshow</returns>
		public Slideshow CreateCopy()
		{
			User.RequirePrivilege(Privilege.CopySlideshow, this);
			Slideshow copy = new Slideshow();
			copy.Title = "Copy of " + this.Title;
			LoadSlides();
			foreach (Slide slide in this.slides)
			{
				Slide newslide = new Slide();
				newslide.ImageID = slide.ImageID;
				newslide.Scratch = slide.Scratch;
				newslide.Annotation = slide.Annotation;
				copy.Add(newslide);
			}	
			copy.Update();
			return copy;
		}

		/// <summary>
		/// Slides in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns a sorted list of slides in this slideshow. If slides are modified, the 
		/// <see cref="ModifiableObject.Update"/> method of the slideshow must be called to commit the changes to the database.
		/// If an access password is required for this slideshow, use <see cref="GetSlides(string)"/>.
		/// </remarks>
		/// <returns>A sorted array of slides.</returns>
		public Slide[] GetSlides()
		{
			return GetSlides(null);
		}

		private static bool CheckBasicAccess(Slideshow show)
		{
			User user = User.CurrentUser();
			if (user == null || show == null)
				return false;
			return 
				user.Administrator || 
				user.ID == show.userid ||
				(User.HasPrivilege(Privilege.ViewSlideshow, show) && !show.archiveflag) || 
				User.HasPrivilege(Privilege.ModifySlideshow, show) ||
				User.HasPrivilege(Privilege.CopySlideshow, show);
		}

		private void CheckAccess(string pwd)
		{
			User user = User.CurrentUser();
			if (user == null)
				throw new PrivilegeException("Access to slideshow is denied to unauthenticated users.");
			if (user.Administrator || 
				user.ID == this.userid ||
				User.HasPrivilege(Privilege.ModifySlideshow, this) ||
				User.HasPrivilege(Privilege.CopySlideshow, this))
				return;
			if (!User.HasPrivilege(Privilege.ViewSlideshow, this) ||
				this.archiveflag)
				throw new PrivilegeException("Access to slideshow is denied.");
			if (!IsPasswordValid(pwd))
				throw new PrivilegeException("Access to this slideshow is denied without access password.");
		}

		/// <summary>
		/// Check slideshow password
		/// </summary>
		/// <remarks>
		/// This method checks if a given slideshow password is correct.  Use this method to
		/// if the current user does not have modification access to the slideshow and therefore
		/// cannot read the <c>Password</c> property.
		/// </remarks>
		/// <param name="pwd">The password to check</param>
		/// <returns><c>true</c> if the password is correct (including <c>null</c> passwords),
		/// <c>false</c> otherwise</returns>
		public bool IsPasswordValid(string pwd)
		{
			string p = (pwd != null && pwd.Length > 50 ? pwd.Substring(0, 50) : pwd);
			if (p == "")
				p = null;
			return (password == p);
		}

		/// <summary>
		/// Retrieve a slide by its identifier
		/// </summary>
		/// <param name="slideid">The internal identifier of the slide to retrieve</param>
		/// <param name="pwd">The access password for this slideshow, or <c>null</c>
		/// if no password is required.</param>
		/// <returns>The requested slide or <c>null</c> if the slide does not exist or is
		/// not part of this slideshow.</returns>
		/// <remarks>
		/// This method searches for a slide in this slideshow and returns it if it is found.
		/// </remarks>
		/// <exception cref="PrivilegeException">Thrown if the provided password does
		/// not match the specified password.  This exception is thrown even if the slideshow
		/// does not require a password, but one was specified.</exception>
		public Slide GetSlideByID(int slideid, string pwd)
		{
			CheckAccess(pwd);
			LoadSlides();
			foreach (Slide slide in slides)
				if (slide.ID == slideid)
					return slide;
			return null;
		}

		/// <summary>
		/// Get images for slides in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns image objects for the slides in this slideshow, regardless of
		/// access privileges of the collections the images are located in.  No slideshow
		/// password is provided.
		/// </remarks>
		/// <returns>An array of images</returns>
		public Image[] GetImages()
		{
			return GetImages(null);
		}

		/// <summary>
		/// Get images for slides in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns image objects for the slides in this slideshow, regardless of
		/// access privileges of the collections the images are located in.
		/// </remarks>
		/// <param name="pwd">The slideshow access password; <c>null</c> if the slideshow
		/// is not password protected</param>
		/// <returns>An array of images</returns>
		public Image[] GetImages(string pwd)
		{
			CheckAccess(pwd);
			LoadSlides();
			ImageIdentifier[] ids = new ImageIdentifier[slides.Count];
			for (int i = 0; i < slides.Count; i++)
				ids[i] = ((Slide)slides[i]).ImageID;
			// get the images without checking read privileges on collections
			return Image.GetByID(false, ids);
		}

		/// <summary>
		/// Get individual image in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns an image object for a slide in this slideshow, regardless
		/// of access privileges of the collection the image is located in.
		/// Using this method assumes no slideshow access password is required.
		/// </remarks>
		/// <param name="image">The identifier of the image</param>
		/// <returns>The image object, or <c>null</c> if the requested image
		/// is not in this slideshow</returns>
		public Image GetImage(ImageIdentifier image)
		{
			return GetImage(null, image);
		}

		/// <summary>
		/// Get individual image in this slideshow
		/// </summary>
		/// <remarks>
		/// This method returns an image object for a slide in this slideshow, regardless
		/// of access privileges of the collection the image is located in.
		/// </remarks>
		/// <param name="pwd">The access password for this slideshow</param>
		/// <param name="image">The identifier of the image</param>
		/// <returns>The image object, or <c>null</c> if the requested image
		/// is not in this slideshow</returns>
		public Image GetImage(string pwd, ImageIdentifier image)
		{
			CheckAccess(pwd);
			LoadSlides();
			foreach (Slide slide in slides)
				if (slide.ImageID == image)
					return Image.GetByID(false, image);
			return null;
		}

		/// <summary>
		/// Get image resource data
		/// </summary>
		/// <remarks>
		/// This method returns the resource data for an image in this slideshow.  Certain
		/// access restrictions to collections are ignored to allow users to view slideshows with
		/// images they may not have access to otherwise.
		/// </remarks>
		/// <param name="pwd">The slideshow access password</param>
		/// <param name="imageid">The internal identifier of the image</param>
		/// <param name="format">The format of the image</param>
		/// <returns>A stream containing the image data, or an error message rendered and 
		/// encoded as a JPEG stream</returns>
		public Stream GetResourceData(string pwd, ImageIdentifier imageid, ImageSize format)
		{
			try
			{
				CheckAccess(pwd);
			}
			catch (PrivilegeException)
			{
				return Collection.RenderMessage("Slideshow access denied", format);
			}
			Image image = Image.GetByID(false, imageid);
			if (image != null)
			{
				Collection coll = Collection.GetByID(false, imageid.CollectionID);
				if (coll != null)
				{
					// see if requested image is in this slideshow
					bool imageinslideshow = false;
					if (slidesloaded)
					{
						foreach (Slide slide in slides)
							if (slide.ImageID == image.ID)
							{
								imageinslideshow = true;
								break;
							}
					}
					else
					{
						using (DBConnection conn = DBConnector.GetConnection())
						{
							Query query = new Query(conn,
								@"SELECT COUNT(*) FROM Slides 
								WHERE SlideshowID={slideshowid} AND 
								ImageID={imageid} AND CollectionID={collectionid}");
							query.AddParam("slideshowid", id);
							query.AddParam("imageid", imageid.ID);
							query.AddParam("collectionid", imageid.CollectionID);
							imageinslideshow = (conn.DataToInt(conn.ExecScalar(query), 0) > 0);
						}
					}
					if (imageinslideshow)
						return coll.GetResourceData(false, image.Resource, format);
					else
						return Collection.RenderMessage("Slide access denied", format);
				}
				else
					return Collection.RenderMessage("Collection not available", format);
			}
			else
				return Collection.RenderMessage("Image not available [2]", format);
		}

		/// <summary>
		/// Retrieve a slide by its identifier
		/// </summary>
		/// <param name="slideid">The internal identifier of the slide to retrieve</param>
		/// <returns>The requested slide or <c>null</c> if the slide does not exist or is
		/// not part of this slideshow.</returns>
		/// <remarks>
		/// This method searches for a slide in this slideshow and returns it if it is found.
		/// </remarks>
		public Slide GetSlideByID(int slideid)
		{
			return GetSlideByID(slideid, null);
		}

		/// <summary>
		/// Internal Identifier
		/// </summary>
		/// <remarks>
		/// This value is <c>0</c> if the slideshow is newly created or deleted.
		/// </remarks>
		/// <value>
		/// The internal identifier of this slideshow in the database.
		/// </value>
		public int ID
		{
			get
			{
				return id;
			}
		}

		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier is the primary key used in the database.
		/// </remarks>
		/// <returns>
		/// The internal identifier of this slideshow.
		/// </returns>
		public override int GetID()
		{
			return id;
		}

		/// <summary>
		/// Add slides
		/// </summary>
		/// <remarks>
		/// This method appends slides to the end of the slideshow. Slides that already exist in this
		/// or another slideshow will not be added again.
		/// </remarks>
		/// <param name="slides">An array of slides to be added.</param>
		public void Add(params Slide[] slides)
		{
			LoadSlides();
			MarkModified();
			AddLoading(slides);
		}

		private void AddLoading(params Slide[] slides)
		{
			foreach (Slide slide in slides)
				if (slide.slideshow == null)
				{
					slide.slideshow = this;
					this.slides.Add(slide);
				}
		}

		/// <summary>
		/// Add slides
		/// </summary>
		/// <remarks>
		/// This method inserts slides into a slideshow at a given position. Slides that already exist in this
		/// or another slideshow will not be added again.
		/// </remarks>
		/// <param name="position">The position to insert the slides at</param>
		/// <param name="slides">An array of slides to be inserted</param>
		public void Insert(int position, params Slide[] slides)
		{
			LoadSlides();
			MarkModified();
			foreach (Slide slide in slides)
				if (slide.slideshow == null)
				{
					slide.slideshow = this;
					this.slides.Insert(position++, slide);
				}
		}

		/// <summary>
		/// Remove slides
		/// </summary>
		/// <remarks>
		/// This method removes slides from this slideshow. Slides that are not in this slideshow or no slideshow
		/// at all are ignored.
		/// </remarks>
		/// <param name="slides">An array of slides to be removed.</param>
		public void Remove(params Slide[] slides)
		{
			LoadSlides();
			MarkModified();
			foreach (Slide slide in slides)
				if (slide.slideshow == this)
				{
					slide.slideshow = null;
					slide.id = 0;
					this.slides.Remove(slide);
				}
		}

		/// <summary>
		/// Remove slide
		/// </summary>
		/// <remarks>
		/// This method removes a slide at a given position.
		/// </remarks>
		/// <param name="position">The position of the slide to be removed.</param>
		public void RemoveAt(int position)
		{
			LoadSlides();
			MarkModified();
			Slide slide = (Slide)slides[position];
			slide.slideshow = null;
			slide.id = 0;
			slides.RemoveAt(position);
		}

		/// <summary>
		/// Remove all slides
		/// </summary>
		/// <remarks>
		/// This method removes all slides frm this slideshow.
		/// </remarks>
		public void RemoveAll()
		{
			LoadSlides();
			MarkModified();
			foreach (Slide slide in slides)
			{
				slide.slideshow = null;
				slide.id = 0;
			}
			slides.Clear();
		}

		/// <summary>
		/// Set a new slide order
		/// </summary>
		/// <remarks>
		/// This method quickly reorders all slides in this slideshow. Slides not listed
		/// in the parameter array are removed from the slideshow.
		/// </remarks>
		/// <param name="slideids">An array of slide identifiers in the order in which they
		/// should appear in the slideshow. Existing slides that are not listed in this
		/// parameter are removed from the slideshow. Identifiers of slides that do not exist
		/// yet in this slideshow are ignored.</param>
		public void Reorder(int[] slideids)
		{
			LoadSlides();
			MarkModified();
			ArrayList newslides = new ArrayList();
			if (slideids != null)
				foreach (int id in slideids)
					for (int i = 0; i < slides.Count; i++)
						if (((Slide)slides[i]).id == id)
						{
							newslides.Add(slides[i]);
							slides.RemoveAt(i);
							break;
						}
			RemoveAll();
			slides = newslides;
		}

		/// <summary>
		/// Set a new slide order and update scratch flags
		/// </summary>
		/// <remarks>
		/// This method quickly reorders all slides in this slideshow. Slides not listed
		/// in the parameter array are removed from the slideshow. The scratch flags are
		/// set depending on which parameter the slide appears in.
		/// </remarks>
		/// <param name="slideids">An array of slide identifiers in the order in which they
		/// should appear in the slideshow. The scratch flag of all specified slides will
		/// be set to <c>false</c>. Existing slides that are not listed in one of the two
		/// parameters are removed from the slideshow. Identifiers of slides that do not exist
		/// yet in this slideshow are ignored.</param>
		/// <param name="scratchids">An array of slide identifiers in the order in which they
		/// should be stored in the slideshow. The scratch flag of all specified slides will
		/// be set to <c>true</c>. Existing slides that are not listed in one of the two
		/// parameters are removed from the slideshow. Identifiers of slides that do not exist
		/// yet in this slideshow are ignored.</param>
		public void Reorder(int[] slideids, int[] scratchids)
		{
			LoadSlides();
			MarkModified();
			ArrayList newslides = new ArrayList();
			if (slideids != null)
				foreach (int id in slideids)
					for (int i = 0; i < slides.Count; i++)
						if (((Slide)slides[i]).id == id)
						{
							((Slide)slides[i]).Scratch = false;
							newslides.Add(slides[i]);
							slides.RemoveAt(i);
							break;
						}
			if (scratchids != null)
				foreach (int id in scratchids)
					for (int i = 0; i < slides.Count; i++)
						if (((Slide)slides[i]).id == id)
						{
							((Slide)slides[i]).Scratch = true;
							newslides.Add(slides[i]);
							slides.RemoveAt(i);
							break;
						}
			RemoveAll();
			slides = newslides;
		}

		/// <summary>
		/// Identifying character
		/// </summary>
		/// <remarks>
		/// The identifying character for slideshows within the access control system.
		/// Every class using access control must have a different character.
		/// </remarks>
		/// <returns>
		/// The identifying character <c>'S'</c>.
		/// </returns>
		public char GetObjectTypeIdentifier()
		{
			return 'S';
		}

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

		/// <summary>
		/// Slideshow owner
		/// </summary>
		///	<remarks>
		///	This method returns the internal identifier of the owner of the slideshow.
		///	It is equivalent to the Owner property.
		///	</remarks>
		/// <returns>the internal identifier of the owner of the slideshow</returns>
		public int GetOwner()
		{
			return userid;
		}

		/// <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.ModifySlideshow |
				Privilege.DeleteSlideshow |
				Privilege.ViewSlideshow |
				Privilege.CopySlideshow;
		}

		/// <summary>
		/// Slideshow owner
		/// </summary>
		/// <remarks>
		/// This property represents the user owning the slideshow. When a new owner is set,
		/// the user account automatically receives admin privileges for this slideshow.
		/// Setting the owner does not remove existing privileges for the previous owner.
		/// When a new owner is set, the slideshow is moved into the root folder of that user.
		/// </remarks>
		/// <value>
		/// The internal identifier of the user owning the slideshow
		/// </value>
		public int Owner
		{
			get
			{
				return userid;
			}
			set
			{
				if (!this.CanModifyACL(User.CurrentUser(), false))
					throw new PrivilegeException("Cannot change slideshow owner");
				if (value != userid)
				{
					MarkModified();
					userid = value;
					folderid = 0;
				}
			}
		}

		/// <summary>
		/// Internal folder identifier
		/// </summary>
		/// <remarks>
		/// This property provides the internal identifier of the folder in which the slideshow
		/// is stored. It is <c>0</c> if the slideshow is stored in the user's root folder.
		/// The folder has to belong to the owner of this slideshow, otherwise an exception is raised.
		/// </remarks>
		/// <value>The internal identifier of the folder in which the slideshow is stored.</value>
		/// <exception cref="CoreException">Thrown if the specified folder does not belong to the
		/// owner of the slideshow</exception>
		public int Folder
		{
			get
			{
				return folderid;
			}
			set
			{
				User user = User.GetByID(userid);
				if (value == 0 || (user != null && user.GetFolders().ContainsValue(value)))
				{
					MarkModified();
					folderid = value;
				}
				else
					throw new CoreException("Owner or Folder not found.");
			}
		}

		/// <summary>
		/// Archive flag
		/// </summary>
		/// <remarks>
		/// The archive flag can be used to easily restrict access to a slideshow, no matter
		/// what the entries in the access control list are. If the flag is set, only the owner
		/// is allowed to view the slideshow.
		/// </remarks>
		/// <value><c>true</c> if the flag is set and the slideshow is archived,
		/// <c>false</c> otherwise</value>
		public bool ArchiveFlag
		{
			get
			{
				return archiveflag;
			}
			set
			{
				MarkModified();
				if (value == true || CanModifyACL(User.CurrentUser(), false))
					archiveflag = value;
				else
					throw new PrivilegeException("Cannot unarchive slideshow");
			}
		}

		/// <summary>
		/// Description
		/// </summary>
		/// <remarks>
		/// This property can be used to add comments, usage agreements etc. to a slideshow.
		/// </remarks>
		/// <value>
		/// The slideshow's description
		/// </value>
		public string Description
		{
			get
			{
				return description;
			}
			set
			{
				MarkModified();
				description = value;
			}
		}

		/// <summary>
		/// The slideshow access password
		/// </summary>
		/// <remarks>
		/// Slideshows can be password protected.  If slide information for a password protected
		/// slideshow is requested by a user who only has the ViewSlideshow privilege, the password
		/// must be provided.  Users with additional privileges do not have to provide the password.
		/// </remarks>
		/// <value>
		/// The slideshow access password
		/// </value>
		public string Password
		{
			get
			{
				User.RequirePrivilege(Privilege.ModifySlideshow, this);
				return password;
			}
			set
			{
				MarkModified();
				if (value == null || value == "")
					password = null;
				else
					password = (value.Length > 50 ? value.Substring(0, 50) : value);
			}
		}

		/// <summary>
		/// Slideshow password protection check
		/// </summary>
		/// <remarks>
		/// Slideshows can be password protected.  If slide information for a password protected
		/// slideshow is requested by a user who only has the ViewSlideshow privilege, the password
		/// must be provided.  Users with additional privileges do not have to provide the password.
		/// </remarks>
		/// <returns>
		/// <c>true</c> if the slideshow is password protected, <c>false</c> otherwise
		/// </returns>
		public bool IsPasswordProtected()
		{
			return (password != null && password.Length > 0);
		}

		/// <summary>
		/// Creation date
		/// </summary>
		/// <remarks>
		/// This read-only property provides the creation date of the slideshow as a string.
		/// </remarks>
		/// <value>
		/// A string representation of the date and time the slideshow was created.
		/// </value>
		public string CreationDate
		{
			get
			{
				try
				{
					return DateTime.Parse(creationdate).ToShortDateString();
				}
				catch
				{
					return creationdate;
				}
			}
		}

		/// <summary>
		/// Modification date
		/// </summary>
		/// <remarks>
		/// This read-only property provides the modification date of the slideshow as a string.
		/// </remarks>
		/// <value>
		/// A string representation of the date and time the slideshow was last modified.
		/// </value>
		public string ModificationDate
		{
			get
			{
				try
				{
					return DateTime.Parse(modificationdate).ToShortDateString();
				}
				catch
				{
					return modificationdate;
				}
			}
		}
	}

}
