using System;
using System.Collections;
using System.Data;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using Orciid.Media.Util;

namespace Orciid.Core
{
	/// <summary>
	/// Image Identifier struct
	/// </summary>
	/// <remarks>
	/// This structure holds all information necessary to uniquely identify an image
	/// </remarks>
	[Serializable()] 
	public struct ImageIdentifier:
		IComparable
	{
		/// <summary>
		/// Internal Image Identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier of the image. This must be unique within every collection.
		/// It is automatically unique within an individual orciid installation, since the
		/// database uses an autonumber field.
		/// </remarks>
		public int ID;
		
		/// <summary>
		/// Internal Collection Identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier of the collection holding the image. This must be unique within
		/// this orciid installation.
		/// </remarks>
		public int CollectionID;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new ImageIdentifier
		/// </remarks>
		/// <param name="i">The internal image identifier within a collection</param>
		/// <param name="c">The internal identifier of the collection</param>
		public ImageIdentifier(int i, int c)
		{
			ID = i;
			CollectionID = c;
		}

		/// <summary>
		/// Equality operator
		/// </summary>
		/// <remarks>
		/// ImageIdentifiers are equal if both the ID and the CollectionID are equal.
		/// </remarks>
		/// <param name="a">An ImageIdentifier to compare</param>
		/// <param name="b">An ImageIdentifier to compare</param>
		/// <returns><c>true</c> if a.ID == b.ID and a.CollectionID == b.CollectionID, <c>false</c> otherwise.</returns>
		public static bool operator==(ImageIdentifier a, ImageIdentifier b)
		{
			return (a.ID == b.ID && a.CollectionID == b.CollectionID);
		}

		/// <summary>
		/// Inequality operator
		/// </summary>
		/// <remarks>
		/// ImageIdentifiers are not equal if either the ID and the CollectionID are not equal.
		/// </remarks>
		/// <param name="a">An ImageIdentifier to compare</param>
		/// <param name="b">An ImageIdentifier to compare</param>
		/// <returns><c>true</c> if a.ID != b.ID or a.CollectionID != b.CollectionID, <c>false</c> otherwise.</returns>
		public static bool operator!=(ImageIdentifier a, ImageIdentifier b)
		{
			return (a.ID != b.ID || a.CollectionID != b.CollectionID);
		}

		/// <summary>
		/// Object equality operator
		/// </summary>
		/// <remarks>
		/// Compares an object to an ImageIdentifier
		/// </remarks>
		/// <param name="o">The object to compare to this ImageIdentifier</param>
		/// <returns><c>true</c> if the object is also an ImageIdentifier and its ID and CollectionID
		/// match this ImageIdentifier's ID and collectionID.</returns>
		public override bool Equals(object o)
		{
			return (o is ImageIdentifier && (ImageIdentifier)o == this);
		}

		/// <summary>
		/// Hash code
		/// </summary>
		/// <remarks>
		/// This method calculates a hash code for this ImageIdentifier
		/// </remarks>
		/// <returns>A hash code for this ImageIdentifier</returns>
		public override int GetHashCode()
		{
			return (CollectionID & 0xFFFF) << 16 + (ID & 0xFFFF);
		}

		/// <summary>
		/// Convert an identifier to a string
		/// </summary>
		/// <remarks>
		/// In certain situations image identifiers must be expressed as strings. This is
		/// done in the form <c>CollectionID:ImageID</c>.
		/// </remarks>
		/// <returns>The identifier as a string</returns>
		public override string ToString()
		{
			return String.Format("{0}:{1}", CollectionID, ID);
		}

		/// <summary>
		/// Convert a string to an identifier
		/// </summary>
		/// <remarks>
		/// In certain situations image identifiers must be expressed as strings. This is
		/// done in the form <c>CollectionID:ImageID</c>.
		/// </remarks>
		/// <param name="s">The string to be converted</param>
		/// <returns>An identifier as expressed in the string</returns>
		/// <exception cref="CoreException">Thrown if the string cannot be parsed</exception>
		public static ImageIdentifier Parse(string s)
		{
			Regex regex = new Regex(@"^(\d+):(\d+)$");
			Match match = regex.Match(s);
			if (match.Success)
			{
				ImageIdentifier i;
				i.CollectionID = Int32.Parse(match.Groups[1].Value);
				i.ID = Int32.Parse(match.Groups[2].Value);
				return i;
			}
			else
				throw new CoreException("Invalid ImageIdentifier string");
		}

		/// <summary>
		/// Compare two image identifiers
		/// </summary>
		/// <remarks>
		/// Compares two image identifiers
		/// </remarks>
		/// <param name="obj">Another image identifier object to compare</param>
		/// <returns>The result of the comparison</returns>
		public int CompareTo(object obj)
		{
			ImageIdentifier temp = (ImageIdentifier)obj;
			int c = this.CollectionID.CompareTo(temp.CollectionID);
			return (c == 0 ? this.ID.CompareTo(temp.ID) : c);
		}
	}

	/// <summary>
	/// An individual image record
	/// </summary>
	/// <remarks>
	/// This class represents an individual image record in the system. Every image must be assigned to
	/// exactly one <see cref="Collection"/>. The image holds the <see cref="FieldData"/> for the <see cref="Field"/>s
	/// of the collection.
	/// </remarks>
	public class Image:
		ModifiableObject
	{
		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// This is the primary key used in the database for this image record. It is <c>0</c>
		/// for newly created images or for deleted images.
		/// </remarks>
		private ImageIdentifier id;
		/// <summary>
		/// String identifying the image resource 
		/// </summary>
		/// <remarks>
		/// The system does not make any assumptions about what kind of resource is identified or
		/// how the identification is expressed. This property could be a simple alphanumeric image
		/// identifier, a URL, a file path etc.
		/// </remarks>
		private string resource;
		/// <summary>
		/// Field data
		/// </summary>
		/// <remarks>
		/// A hashtable holding the field data for the image record. The internal field identifiers are
		/// used as keys for the hashtable, the values are <see cref="FieldData"/> objects.
		/// </remarks>
		internal Hashtable fielddata = new Hashtable();

		internal DateTime createddate = DateTime.MinValue;

		internal DateTime modifieddate = DateTime.MinValue;

		private string recordmaintenance = null;

		private ExtendedResource extendedresource = null;

		internal int userid = 0;

		internal string username = null;

		internal ImageFlag flags = ImageFlag.None;

		internal DateTime cacheduntil = DateTime.MinValue;

		internal DateTime expires = DateTime.MinValue;

		internal string remoteid = null;

		internal ImageRecordStatus recordstatus = ImageRecordStatus.Ok;

		internal string mimetype = null;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// The newly created image will be modifiable. After all properties
		/// are set, <see cref="ModifiableObject.Update"/> must be called.
		/// </remarks>
		internal Image():
			base()
		{
		}

		/// <summary>
		/// Check image creation privileges
		/// </summary>
		/// <remarks>
		/// Every user can create images. Privileges are checked once an image is assigned to a collection
		/// via the <see cref="CollectionID"/> property.
		/// </remarks>
		/// <param name="user">The user to check privileges for</param>
		/// <returns><c>true</c></returns>
		public override bool CanCreate(User user)
		{
			return true;
		}

		/// <summary>
		/// Check image modification privileges
		/// </summary>
		/// <remarks>
		/// Every user can modify images while they have not been assigned to a collection. After an
		/// image has been assigned to a collection, the user must have appropriate privileges.
		/// </remarks>
		/// <param name="user">The user to check privileges for</param>
		/// <returns><c>true</c></returns>
		public override bool CanModify(User user)
		{
			Collection coll = (id.CollectionID == 0 ? null : Collection.GetByID(id.CollectionID));
			return (id.CollectionID == 0 || (coll != null && coll.CanModifyImage(user, this)));
		}
		
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <remarks>
		/// Does not initialize or check for privileges. This constructor is used when
		/// image objects are created and initialized with records from the database.
		/// </remarks>
		/// <param name="init">dummy parameter, should be <c>false</c></param>
		private Image(bool init):
			base(init)
		{
		}

		internal Image(ImageIdentifier ident, string res):
			this(false)
		{
			id = ident;
			CheckResource(res);
			resource = res;
		}

		/// <summary>
		/// Retrieve image
		/// </summary>
		/// <remarks>
		/// Retrieves an image by its internal identifier.
		/// </remarks>
		/// <param name="id">The internal identifier of the image to return</param>
		/// <returns>The image object or <c>null</c> if the image cannot be found or the
		/// current user does not have the required privileges to retrieve the image.
		/// </returns>
		public static Image GetByID(ImageIdentifier id) 
		{
			return GetByID(true, id);
		}

		internal static Image GetByID(bool enforcepriv, ImageIdentifier id)
		{
			Image[] i = GetByID(enforcepriv, new ImageIdentifier[] { id });
			if (i != null && i.Length == 1)
				return i[0];
			else
				return null;
		}

		/// <summary>
		/// Retrieve images
		/// </summary>
		/// <remarks>
		/// Retrieves one or more Image objects by their internal identifier. The images
		/// returned will be in the same order as the submitted identifiers. Note: if requested images
		/// are located in collections for which the user does not have read privileges, or if
		/// a requested image cannot be found, <c>null</c>
		/// will be returned for that specific image, so the size of the returned array and the positions
		/// of individual images will correspond with the size of the array and positions of the image identifiers.
		/// </remarks>
		/// <param name="id">The identifiers of the images to return</param>
		/// <returns>An array of images with the specified identifier</returns>
		public static Image[] GetByID(params ImageIdentifier[] id) 
		{
			return GetByID(true, id);
		}

		internal static Image[] GetByID(bool enforcepriv, params ImageIdentifier[] id)
		{
			if (id.Length == 0)
				return null;
			// split requested images by collection
			Hashtable requested = new Hashtable();
			foreach (ImageIdentifier i in id)
			{
				if (requested.ContainsKey(i.CollectionID))
					((ArrayList)requested[i.CollectionID]).Add(i);
				else
				{
					ArrayList a = new ArrayList();
					a.Add(i);
					requested[i.CollectionID] = a;
				}
			}
			// retrieve images by collection
			Hashtable result = new Hashtable();
			foreach (int cid in requested.Keys)
			{
				Collection coll = Collection.GetByID(enforcepriv, cid);
				if (coll != null)
					result[cid] = coll.GetImagesByID(enforcepriv, (ArrayList)requested[cid]);
			}
			// build image array in same order as incoming image ids
			Image[] images = new Image[id.Length];
			for (int i = 0; i < id.Length; i++)
			{
				if (result.ContainsKey(id[i].CollectionID) && result[id[i].CollectionID] != null)
					foreach (Image im in (ArrayList)result[id[i].CollectionID])
						if (im.ID == id[i])
						{
							images[i] = im;
							break;
						}
			}
			return images;
		}

		/// <summary>
		/// Delete an Image
		/// </summary>
		/// <remarks>
		/// Deletes an Image from the database. The object itself is invalid afterwards.
		/// WARNING: All data contained in this field is dropped from the database.
		/// </remarks>
		public void Delete()
		{
			Delete(true);
		}

		internal void Delete(bool enforcepriv)
		{
			if (id.ID > 0 && id.CollectionID > 0)
			{
				Collection coll = Collection.GetByID(enforcepriv, id.CollectionID);
				if (coll == null)
					throw new CoreException("Collection not found");
				if (recordmaintenance == null)
					recordmaintenance = GetDataAsXmlString();
				if (coll.HasRecordHistory)
					WriteRecordMaintenance(RecordMaintenanceType.Delete);
				coll.DeleteImage(enforcepriv, this);
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"DELETE FROM ImageAnnotations 
						WHERE ImageID={imageid} AND CollectionID={collectionid}");
					query.AddParam("imageid", id.ID);
					query.AddParam("collectionid", id.CollectionID);
					conn.ExecQuery(query);

					query = new Query(conn,
						@"DELETE FROM FavoriteImages 
						WHERE ImageID={imageid} AND CollectionID={collectionid}");
					query.AddParam("imageid", id.ID);
					query.AddParam("collectionid", id.CollectionID);
					conn.ExecQuery(query);
				}
			}
			id.ID = 0;
			id.CollectionID = 0;
			ResetModified();
			fielddata = null;
		}

        /// <summary>
        /// Duplicate image
        /// </summary>
        /// <remarks>
        /// The duplicate image will not have been committed to the database yet
        /// </remarks>
        /// <returns>A copy of this Image object</returns>
		public Image Duplicate()
		{
			Collection coll = Collection.GetByID(true, id.CollectionID);
			if (coll == null)
				return null;
			Image newimg = coll.CreateImage(userid != 0);
			foreach (object key in fielddata.Keys)
				newimg.fielddata[key] = ((FieldData)fielddata[key]).DuplicateForImage(newimg);
			return newimg;
		}

		/// <summary>
		/// Commit changes
		/// </summary>
		/// <remarks>
		/// Writes a modified Image object to the database and returns
		/// the object 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 image is not assigned to
		/// a collection.</exception>
		protected override void CommitChanges()
		{
			if (id.CollectionID == 0)
				throw new CoreException("Cannot write Image to database without collection assignment");
			// FogBugz case 542: check if privileges should be checked, this was an issue with remote collection updates in background tasks
			Collection coll = Collection.GetByID(false, id.CollectionID);  
			if (coll == null)
				throw new CoreException("Collection not found");
			RecordMaintenanceType r = (id.ID == 0 ? RecordMaintenanceType.Create : RecordMaintenanceType.Update);
			coll.UpdateImage(this);
			// if we are creating a new image, store initial field data, otherwise, store previous data
			if (r == RecordMaintenanceType.Create)
				recordmaintenance = GetDataAsXmlString();
			if (coll.HasRecordHistory)
				WriteRecordMaintenance(r);
		}

		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// The internal identifier of an image is the primary key of its record in the database.
		/// It is <c>0</c> for newly created images and for deleted images.
		/// </remarks>
		/// <value>
		/// The read-only internal identifier of the image.
		/// </value>
		public ImageIdentifier ID
		{
			get
			{
				return id;
			}
		}

		/// <summary>
		/// Internal identifier (this method is not available)
		/// </summary>
		/// <remarks>
		/// Use the <see cref="ID"/> property to get the internal identifier of this image.
		/// Calling this method will raise an exception.
		/// </remarks>
		/// <exception cref="CoreException">Thrown if this method is called.</exception>
		/// <returns>
		/// n/a
		/// </returns>
		public override int GetID()
		{
			throw new CoreException("Cannot return image identifier, use ID property instead.");
		}

		internal void SetID(ImageIdentifier i)
		{
			id = i;
		}

		/// <summary>
		/// Internal identifier of assigned collection
		/// </summary>
		/// <remarks>
		/// Every image must be assigned to exactly one <see cref="Collection"/> in the system. This property
		/// can only be set for newly created images. It is currently not possible to move images between
		/// collections.
		/// </remarks>
		/// <exception cref="CoreException">Thrown when a collection identifier is assigned to an
		/// image which has already been written to the database.</exception>
		/// <exception cref="CoreException">Thrown when a collection identifier for a non-existing
		/// collection is assigned.</exception>
		/// <value>The internal identifier of the collection the image is assigned to.</value>
		public int CollectionID
		{
			get
			{
				return id.CollectionID;
			}
		}

		/// <summary>
		/// String identifying the image resource 
		/// </summary>
		/// <remarks>
		/// The system does not make any assumptions about what kind of resource is identified or
		/// how the identification is expressed. This property could be a simple alphanumeric image
		/// identifier, a URL, a file path etc. to the actual image file.
		/// </remarks>
		/// <value>A string identifying the image resource.</value>
		public string Resource
		{
			get
			{
				return resource;
			}
			set
			{
				if (resource == value)
					return;
				if (userid != 0 && resource != null)
					throw new CoreException("Cannot change image resource on personal image");
				MarkModified();
				CheckResource(value);
				resource = value;
			}
		}

		internal void SetResource(string res)
		{
			CheckResource(res);
			resource = res;
		}

		private static void CheckResource(string res)
		{
			if (res != null)
			{
				if (res.IndexOf(Path.DirectorySeparatorChar) != -1 ||
					res.IndexOf(Path.AltDirectorySeparatorChar) != -1)
					throw new CoreException("No path information allowed.");
				if (res.Length > 255)
					throw new CoreException("Maximum resource length is 255 characters.");
			}
		}

		/// <summary>
		/// Image owner
		/// </summary>
		/// <remarks>
		/// Every image may belong to a user.  If an image belongs to the regular collection
		/// and does not have an owner, this property is <c>0</c>.
		/// </remarks>
		/// <value>The internal identifier of the user owning this image</value>
		public int UserID
		{
			get
			{
				return userid;
			}
		}

		/// <summary>
		/// Name of image owner
		/// </summary>
		/// <remarks>
		/// Every image may belong to a user.  If an image belongs to the regular collection
		/// and does not have an owner, this property is <c>null</c>.
		/// </remarks>
		/// <value>The full name of the user owning this image</value>
		public string UserName
		{
			get
			{
				if (userid != 0)
				{
					if (username == null)
					{
						User user = User.GetByID(userid);
						if (user != null)
							username = user.FullName;
					}
					return username;
				}
				else
					return null;
			}
		}

		/// <summary>
		/// Image flags
		/// </summary>
		/// <remarks>
		/// Every image has a set of flags that determine the share status etc.
		/// </remarks>
		/// <value>
		/// The flags for this image.
		/// </value>
		public ImageFlag Flags
		{
			get
			{
				return flags;
			}
		}

		/// <summary>
		/// Shareable status
		/// </summary>
		/// <remarks>
		/// An image is shareable if it belongs to a user.  This property does not
		/// check if the user has the required <see cref="Privilege.ShareImages"/> privilege
		/// to actually share the image.
		/// </remarks>
		/// <value>
		/// <c>true</c> if the image has an owner, <c>false</c> otherwise.
		/// </value>
		public bool IsShareable
		{
			get
			{
				return userid != 0;
			}
		}

		/// <summary>
		/// Share status
		/// </summary>
		/// <remarks>
		/// An image is shared if it is shareable and the share flag is set.
		/// <seealso cref="ImageFlag"/>
		/// </remarks>
		/// <value><c>true</c> if the image is shared, <c>false</c> otherwise</value>
		public bool IsShared
		{
			get
			{
				return IsShareable && ((flags & ImageFlag.Shared) == ImageFlag.Shared);
			}
		}

		/// <summary>
		/// Suggestion status
		/// </summary>
		/// <remarks>
		/// An image is suggested for inclusion in the collection if the corresponding flag is set
		/// </remarks>
		/// <value><c>true</c> if the image is suggested for inclusion in the collection,
		/// <c>false</c> otherwise</value>
		public bool IsSuggested
		{
			get
			{
				return IsShareable && ((flags & ImageFlag.InclusionSuggested) == ImageFlag.InclusionSuggested);
			}
		}

		/// <summary>
		/// Creation Date
		/// </summary>
		/// <remarks>
		/// The date and time this image was created
		/// </remarks>
		/// <value>
		/// The creation date of the image
		/// </value>
		public DateTime CreatedDate
		{
			get
			{
				return createddate;
			}
		}

		/// <summary>
		/// Modification date
		/// </summary>
		/// <remarks>
		/// The date and time this image was last modified
		/// </remarks>
		/// <value>
		/// The last modification date of the image
		/// </value>
		public DateTime ModifiedDate
		{
			get
			{
				return modifieddate;
			}
		}

		/// <summary>
		/// Record status
		/// </summary>
		/// <remarks>
		/// The status of the image record
		/// </remarks>
		/// <value>
		/// The status of the image record
		/// </value>
		public ImageRecordStatus RecordStatus
		{
			get
			{
				return recordstatus;
			}
		}

		/// <summary>
		/// Record mime type
		/// </summary>
		/// <remarks>
		/// The mime type of the object represented by this image record
		/// </remarks>
		/// <value>
		/// The mime type of the object represented by this image record
		/// </value>
		public string MimeType
		{
			get
			{
				return mimetype;
			}
		}

		/// <summary>
		/// List of available field data
		/// </summary>
		/// <remarks>
		/// The image does not have to hold data for every <see cref="Field"/> in a <see cref="Collection"/>.
		/// This property returns a list (<see cref="ICollection"/>) of internal field identifiers 
		/// (<see cref="Field.ID"/>) for which this image record has values.
		/// </remarks>
		/// <value>
		/// A list of fields for which this image record holds values.
		/// </value>
		public ICollection Attributes
		{
			get
			{
				return fielddata.Keys;
			}
		}

		/// <summary>
		/// Field data values
		/// </summary>
		/// <remarks>
		/// This property gives access to the <see cref="FieldData"/> for a 
		/// specified field. Note that this property cannot be written to. To add, modify or delete
		/// values, get the FieldData object and then perform the necessary actions on it.
		/// </remarks>
		/// <example>
		/// This example removes all values from a particular field for an image and then adds two new values:
		/// <code>
		/// Image i;
		/// Field f;
		/// // ...
		/// FieldData data = i[f];
		/// data.RemoveAll();
		/// data[0] = "A new value";
		/// data[1] = "And another one";
		/// // ...
		/// i.Update();
		/// </code>
		/// </example>
		/// <value>
		/// The values for this image for an individual field
		/// </value>
		/// <param name="field"><see cref="Field"/> to return data for</param>
		public FieldData this[Field field]
		{
			get
			{
				if (fielddata.ContainsKey(field.ID))
					return (FieldData)fielddata[field.ID];
				else if (field.CollectionID != this.id.CollectionID)
					return null;
				else
				{
					// add new field to collection
					FieldData data = FieldData.CreateFieldData(this, field);
					fielddata.Add(field.ID, data);
					return data;
				}
			}
		}

		/// <summary>
		/// Field data values
		/// </summary>
		/// <remarks>
		/// This property gives access to the <see cref="FieldData"/> for a 
		/// specified field. Note that this property cannot be written to. To add, modify or delete
		/// values, get the FieldData object and then perform the necessary actions on it.
		/// </remarks>
		/// <value>
		/// The values for this image for an individual field
		/// </value>
		/// <param name="fieldid">The internal identifier of the <see cref="Field"/> to return data for</param>
		public FieldData this[int fieldid]
		{
			get
			{
				Collection coll = Collection.GetByID(id.CollectionID);
				if (coll == null)
					return null;
				Field field = coll.GetField(fieldid);
				return this[field];
			}
		}

		/// <summary>
		/// Field data values
		/// </summary>
		/// <remarks>
		/// This property gives access to the <see cref="FieldData"/> for a 
		/// specified field. Note that this property cannot be written to. To add, modify or delete
		/// values, get the FieldData object and then perform the necessary actions on it.
		/// </remarks>
		/// <value>
		/// The values for this image for an individual field
		/// </value>
		/// <param name="fieldname">The name of the <see cref="Field"/> to return data for</param>
		public FieldData this[string fieldname]
		{
			get
			{
				Collection coll = Collection.GetByID(id.CollectionID);
				if (coll == null)
					return null;
				Field field = coll.GetField(fieldname);
				if (field == null)
					return null;
				else
					return this[field];
			}
		}

		internal void SetFieldData(Field field, FieldData data)
		{
			data.LinkToImage(this);
			if (field.CollectionID != this.id.CollectionID)
				throw new CoreException("Specified field is not in collection this image belongs to");
			if (fielddata.ContainsKey(field.ID))
				fielddata[field.ID] = data;
			else
				fielddata.Add(field.ID, data);
		}

		/// <summary>
		/// List of full display values
		/// </summary>
		/// <remarks>
		/// Retrieves an array of strings containing the full display values for the specified field.
		/// </remarks>
		/// <param name="field">The field whose values should be returned</param>
		/// <returns>An array of strings containing the display values for the specified field,
		/// or null if the specified field has no values for this image</returns>
		public string[] GetDisplayValues(Field field)
		{
			return GetDisplayValues(field, ViewMode.Long, null);
		}

		/// <summary>
		/// List of formatted display values
		/// </summary>
		/// <remarks>
		/// Retrieves an array of strings containing the display values for the specified field.
		/// The field values will be formatted using the specified <see cref="ViewMode"/>.
		/// </remarks>
		/// <param name="field">The <see cref="Field"/> whose values should be returned</param>
		/// <param name="mode">The <see cref="ViewMode"/> to be used</param>
		/// <param name="ellipsis">A string to be added to the value if 
		/// the value was truncated. This string does not count towards the character limit.
		/// Usually this string should contain an ellipsis or something comparable.</param>
		/// <returns>An array of strings containing the formatted display values for the specified field,
		/// or null if the specified field has no values for this image or the <see cref="DisplayMode"/> 
		/// for the current field and view mode is <c>None</c></returns>
		public string[] GetDisplayValues(Field field, ViewMode mode, string ellipsis)
		{
			DisplayMode dmode = field.GetDisplayMode(mode);
			if (dmode == DisplayMode.None)
				return new string[0];
			if (fielddata.ContainsKey(field.ID))
			{
				FieldData data = (FieldData)fielddata[field.ID];
				string[] s = new string[data.Count];
				for (int i = 0; i < data.Count; i++)
					s[i] = FormatDisplayValue(data[i], dmode, ellipsis);
				return s;
			}
			else
				return new string[0];
		}

		/// <summary>
		/// Format single display value
		/// </summary>
		/// <remarks>
		/// This function formats a given string depending on a given <see cref="DisplayMode"/>.
		/// If the mode is <see cref="DisplayMode.FirstParagraph"/> and there is a newline character,
		/// the newline character is truncated as well. If the mode is <see cref="DisplayMode.FirstSentence"/>,
		/// whatever character ends a sentence is not truncated.
		/// </remarks>
		/// <param name="v">The display value</param>
		/// <param name="mode">The display mode</param>
		/// <param name="ellipsis">A string to be added to the value if 
		/// the value was truncated. This string does not count towards the character limit.
		/// Usually this string should contain an ellipsis or something comparable.</param>
		/// <returns>The formatted display value, or <c>null</c> if the display mode is <c>None</c></returns>
		public string FormatDisplayValue(string v, DisplayMode mode, string ellipsis)
		{
			if (mode == DisplayMode.None || v == null)
				return null;
			string result = v;
			int i;
			switch (mode)
			{
				case DisplayMode.First20Char:
					if (v.Length > 20)
					{
						result = v.Substring(0, 20);
						if (v[20] != ' ')
						{
							i = result.LastIndexOf(' ');
							if (i > 0)
								result = result.Substring(0, i);
							if (ellipsis != null)
								result += ellipsis;
						}
					}
					break;
				case DisplayMode.First50Char:
					if (v.Length > 50)
					{
						result = v.Substring(0, 50);
						if (v[50] != ' ')
						{
							i = result.LastIndexOf(' ');
							if (i > 0)
								result = result.Substring(0, i);
							if (ellipsis != null)
								result += ellipsis;
						}
					}
					break;
				case DisplayMode.FirstParagraph:
					i = v.IndexOf('\n');
					if (i > 0)
					{
						result = v.Substring(0, i);
						if (ellipsis != null && i < v.Length - 1)
							result += ellipsis;
					}
					break;
				case DisplayMode.FirstSentence:
					i = v.IndexOfAny(new char[] {'.', '!', '?'});
					if (i > 0)
					{
						result = v.Substring(0, i + 1);
						if (ellipsis != null && i < v.Length - 1)
							result += ellipsis;
					}
					break;
				default:
					break;
			}
			return result;
		}

		/// <summary>
		/// The fields of the collection this image belongs to
		/// </summary>
		/// <remarks>
		/// The array of fields returned by this method should not be modified directly.  Instead,
		/// use the appropriate methods for field manipulation on the respective collection.
		/// This method returns the fields regardless of access privileges.  This is used in
		/// slideshows where the viewer of the slideshow may not have read access to a collection.
		/// </remarks>
		/// <returns>An array of fields of the collection this image belongs to</returns>
		public Field[] GetFields()
		{
			Collection coll = Collection.GetByID(false, id.CollectionID);
			if (coll != null)
				return coll.GetFields();
			else
				return null;
		}

		/// <summary>
		/// The title of a collection
		/// </summary>
		/// <remarks>
		/// This method returns the title of a collection regardless of access privileges.  
		/// This is used in slideshows where the viewer of the slideshow may not have read 
		/// access to a collection.
		/// </remarks>
		/// <returns>The title of the collection this image belongs to</returns>
		public string GetCollectionTitle()
		{
			Collection coll = Collection.GetByID(false, id.CollectionID);
			if (coll != null)
				return coll.Title;
			else
				return "";
		}

		/// <summary>
		/// The resource modification date
		/// </summary>
		/// <remarks>
		/// This method returns the resource modification date regardless of access privileges.
		/// This is used in slideshows where the viewer of the slideshow may not have read 
		/// access to a collection.
		/// </remarks>
		/// <param name="format">The image format the modification date is requested for</param>
		/// <returns>The resource modification date, or <c>DateTime.MinValue</c> if the
		/// collection this image belongs to was not found.</returns>
		public DateTime GetResourceModificationDate(ImageSize format)
		{
			Collection coll = Collection.GetByID(false, id.CollectionID);
			if (coll != null)
				return coll.GetResourceModificationDate(false, resource, format);
			else
				return DateTime.MinValue;
		}

		/// <summary>
		/// Extended resource check
		/// </summary>
		/// <remarks>
		/// An extended resource is an XML file that contains additional information about
		/// the record and associated files, compared to a regular resource, which is just
		/// a JPEG file.
		/// </remarks>
		/// <returns><c>true</c> if this image has an extended resource, <c>false</c> if
		/// the image has a regular resource.</returns>
		public bool IsExtendedResource()
		{
			return Collection.IsExtendedResource(resource);
		}

		/// <summary>
		/// Retrieve extended resource
		/// </summary>
		/// <remarks>
		/// An extended resource is an XML file that contains additional information about
		/// the record and associated files, compared to a regular resource, which is just
		/// a JPEG file.
		/// </remarks>
		/// <returns>The extended resource for this image</returns>
		public ExtendedResource GetExtendedResource()
		{
			if (extendedresource == null)
			{
				if (!IsExtendedResource())
					return null;
				Collection coll = Collection.GetByID(false, id.CollectionID);
				if (coll != null)
					extendedresource = coll.GetExtendedResource(resource);
				else
					return null;
			}
			return extendedresource;
		}

		/// <summary>
		/// Set image annotation for current user
		/// </summary>
		/// <remarks>
		/// This method sets an annotation for this image and current user. Image annotations
		/// are associated with a user, so every user can have a different annotation for the
		/// same image. To remove an existing annotation, call this method with <c>null</c> as
		/// parameter.
		/// </remarks>
		/// <param name="annotation">The annotation to set, or <c>null</c> to remove an
		/// existing annotation.</param>
		public void SetAnnotation(string annotation)
		{
			SetAnnotation(annotation, User.CurrentUser());
		}

		/// <summary>
		/// Set image annotation for a user
		/// </summary>
		/// <remarks>
		/// This method sets an annotation for this image and the given user. Image annotations
		/// are associated with a user, so every user can have a different annotation for the
		/// same image. To remove an existing annotation, call this method with <c>null</c> as
		/// parameter.
		/// </remarks>
		/// <param name="annotation">The annotation to set, or <c>null</c> to remove an
		/// existing annotation.</param>
		/// <param name="user">The user to set the annotation for</param>
		/// <exception cref="PrivilegeException">Thrown if the specified user is different
		/// from the current user and the current user does not have administrator privileges
		/// </exception>
		/// <exception cref="PrivilegeException">Thrown if the specified user does not
		/// have sufficient privileges to annotate this image in its collection</exception>
		/// <exception cref="CoreException">Thrown if the image has not been committed to
		/// the database</exception>
		public void SetAnnotation(string annotation, User user)
		{
			User current = User.CurrentUser();
			if (user != current && !user.Administrator)
				throw new CoreException("Cannot annotate images for different user.");
			Collection coll = Collection.GetByID(id.CollectionID);
			User.RequirePrivilege(Privilege.AnnotateImages, coll, user);
			if (id.ID != 0)
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"DELETE FROM ImageAnnotations 
						WHERE ImageID={imageid} AND CollectionID={collectionid} AND UserID={userid}");
					query.AddParam("imageid", id.ID);
					query.AddParam("collectionid", id.CollectionID);
					query.AddParam("userid", user.ID);
					conn.ExecQuery(query);
					if (annotation != null && annotation.Length > 0)
					{
						query = new Query(conn,
							@"INSERT INTO ImageAnnotations (ImageID,CollectionID,UserID,Annotation) 
							VALUES ({imageid},{collectionid},{userid},{annotation})");
						query.AddParam("imageid", id.ID);
						query.AddParam("collectionid", id.CollectionID);
						query.AddParam("userid", user.ID);
						query.AddParam("annotation", annotation);
						conn.ExecQuery(query);
					}
				}
			else
				throw new CoreException("Could not set annotation for newly created or deleted image.");
		}

		/// <summary>
		/// Retrieve image annotation for given user
		/// </summary>
		/// <remarks>
		/// This method retrieves an annotation for this image and the specified user. Image annotations
		/// are associated with a user, so every user can have a different annotation for the
		/// same image.
		/// </remarks>
		/// <param name="user">The user to retrieve the image annotation for</param>
		/// <returns>The annotation for this image and the specified user, or <c>null</c> if no
		/// annotation is set or the user does not have privileges to annotate images.</returns>
		public string GetAnnotation(User user)
		{
			string[] a = GetAnnotations(user, id);
			return (a == null || a.Length == 0 ? null : a[0]);
		}

		/// <summary>
		/// Retrieve image annotations for specified user
		/// </summary>
		/// <remarks>
		/// This method retrieves an annotation for a number of images and specified user. Image annotations
		/// are associated with a user, so every user can have a different annotation for the
		/// same image.
		/// </remarks>
		/// <param name="user">The user whose annotations should be returned</param>
		/// <param name="images">An array of image identifiers</param>
		/// <returns>An array of strings containing the image annotations. The positions
		/// of the strings in the array correspond to the image identifiers. If an annotation
		/// is not set or unavailable, the respective string is <c>null</c>. If the images
		/// parameter is <c>null</c>, this method returns <c>null</c>, otherwise the returned
		/// array has the same dimensions as the images parameter.</returns>
		public static string[] GetAnnotations(User user, params ImageIdentifier[] images)
		{
			if (images == null || user == null)
				return null;
			Hashtable imagesByColl = new Hashtable();
			foreach (ImageIdentifier id in images)
				if (imagesByColl.ContainsKey(id.CollectionID) && id.ID != 0)
					((ArrayList)imagesByColl[id.CollectionID]).Add(id.ID);
				else
				{
					ArrayList a = new ArrayList();
					a.Add(id.ID);
					imagesByColl.Add(id.CollectionID, a);
				}
			Hashtable annotations = new Hashtable();
			using (DBConnection conn = DBConnector.GetConnection())
			{
				string where = "";
				foreach (int collid in imagesByColl.Keys)
				{
					Query q = new Query(conn, "(CollectionID={collid} AND ImageID IN {ids})");
					q.AddParam("collid", collid);
					q.AddParam("ids", (ArrayList)imagesByColl[collid]);
					where += (where.Length > 0 ? " OR " : "") + q.SQL;
				}
				if (where == "")
					return new string[images.Length];
				Query query = new Query(conn,
					@"SELECT CollectionID,ImageID,Annotation FROM ImageAnnotations 
					WHERE UserID={userid} AND (" + where + ")");
				query.AddParam("userid", user.ID);
				DataTable table = conn.SelectQuery(query);
				foreach (DataRow row in table.Rows)
				{
					ImageIdentifier id;
					id.CollectionID = conn.DataToInt(row["CollectionID"], 0);
					id.ID = conn.DataToInt(row["ImageID"], 0);
					annotations[id] = conn.DataToString(row["Annotation"]);
				}
			}
			string[] result = new string[images.Length];
			for (int i = 0; i < images.Length; i++)
				result[i] = (string)annotations[images[i]];
			return result;
		}

		/// <summary>
		/// Store image resource data
		/// </summary>
		/// <remarks>
		/// This method creates three differently sized versions of an image stored in the
		/// stream passed in the <c>stream</c> parameter.  The image in the stream can be stored 
		/// in any format supported by .NET, e.g. JPEG, TIFF, GIF, BMP etc.
		/// </remarks>
		/// <param name="stream">The source image stream</param>
		/// <param name="mimetype">The mime type of the image stream</param>
		/// <returns><c>true</c> if all image versions were stored correctly, <c>false</c>
		/// otherwise</returns>		
		public bool SetResourceData(Stream stream, string mimetype)
		{
			string tempfile = Path.GetTempFileName();
			string sourcefile = tempfile + "." + Media.MimeType.GetExtension(mimetype);
			File.Move(tempfile, sourcefile);

			using (FileStream outstream = File.OpenWrite(sourcefile))
			{
				byte[] buffer = new byte[16384];
				int bytesread;
				do
				{
					bytesread = stream.Read(buffer, 0, 16384);
					if (bytesread > 0)
						outstream.Write(buffer, 0, bytesread);
				} while (bytesread > 0);
				outstream.Close();
			}

			try
			{
				return SetResourceData(sourcefile);
			}
			finally
			{
				File.Delete(sourcefile);
			}
		}

		/// <summary>
		/// Store image resource data
		/// </summary>
		/// <remarks>
		/// This method creates three differently sized versions of an image stored in the
		/// stream passed in the <c>stream</c> parameter.  The image in the stream can be stored 
		/// in any format supported by .NET, e.g. JPEG, TIFF, GIF, BMP etc.
		/// </remarks>
		/// <param name="sourcefile">The source image file</param>
		/// <returns><c>true</c> if all image versions were stored correctly, <c>false</c>
		/// otherwise</returns>
		public bool SetResourceData(string sourcefile)
		{
			Collection coll = Collection.GetByID(id.CollectionID);
			if (coll == null)
				throw new CoreException("Image is not in a valid collection");
			if (userid != 0)
				User.RequirePrivilege(Privilege.PersonalImages, coll);
			else
				User.RequirePrivilege(Privilege.ModifyImages, coll);
			if (resource == null || resource.Length == 0)
			{
				Resource = Collection.CreateImageResource(id.CollectionID, id.ID);
				Update();
			}
			bool success = true;
			success = success && coll.SetResourceData(resource, ImageSize.Full, sourcefile);
			success = success && coll.SetResourceData(resource, ImageSize.Medium, sourcefile);
			success = success && coll.SetResourceData(resource, ImageSize.Thumbnail, sourcefile);
			return success;
		}

		private static Hashtable IPTCMappings;

		static Image()
		{
			IPTCMappings = new Hashtable();
			IPTCMappings.Add("title", "object name");
			IPTCMappings.Add("type", "category");
			IPTCMappings.Add("date", "date created");
			IPTCMappings.Add("creator", "by-line");
			IPTCMappings.Add("identifier", "headline");
			IPTCMappings.Add("coverage.spatial", "country/primary location name");
			IPTCMappings.Add("publisher", "credit");
			IPTCMappings.Add("source", "source");
			IPTCMappings.Add("rights", "copyright notice");
			IPTCMappings.Add("description", "caption/abstract");
			IPTCMappings.Add("contributor", "writer/editor");
			IPTCMappings.Add("subject", "keywords");
			IPTCMappings.Add("format", "supplemental category");
		}

		/// <summary>
		/// Adds IPTC data to a JPEG stream
		/// </summary>
		/// <remarks>
		/// IPTC data can be added to the header of a JPEG file.  This method
		/// takes cataloging data and adds it as IPTC data if the collection fields
		/// have mappings to the appropriate Dublin Core fields, which are used for
		/// mapping to IPTC.
		/// </remarks>
		/// <param name="instream">A stream containing a valid JPEG file</param>
		/// <returns>A stream with added IPTC data</returns>
		public Stream AddCatalogingDataToStream(Stream instream)
		{
			return AddCatalogingDataToStream(instream, null, null);
		}

		/// <summary>
		/// Adds IPTC data to a JPEG stream
		/// </summary>
		/// <remarks>
		/// IPTC data can be added to the header of a JPEG file.  This method
		/// takes cataloging data and adds it as IPTC data if the collection fields
		/// have mappings to the appropriate Dublin Core fields, which are used for
		/// mapping to IPTC. It also adds an image and slide annotation if available.
		/// </remarks>
		/// <param name="instream">A stream containing a valid JPEG file</param>
		/// <param name="imageAnnotationOwner">The owner of the image annotation</param>
		/// <param name="slideannotation">A slide annotation to add</param>
		/// <returns>A stream with added IPTC data</returns>
		public Stream AddCatalogingDataToStream(Stream instream, User imageAnnotationOwner,
			string slideannotation)
		{
			if (id.CollectionID == 0)
				return instream;
			Collection coll = Collection.GetByID(false, id.CollectionID);
			if (coll == null)
				return instream;
			// try to add cataloging data to JPEG file
			try
			{
				IPTCData data = new IPTCData(instream);
				foreach (Field field in coll.GetFields())
				{
					FieldData fd = this[field];
					if (fd != null && fd.Count > 0 && field.DCName != null)
					{
						string iptcname = (string)IPTCMappings[field.DCName.ToLower()];
						if (iptcname != null)
						{
							if (iptcname == "keywords")
								data.AddKeywords(fd.GetAll());
							else if (iptcname == "supplemental category")
								data.AddSupplementalCategories(fd.GetAll());
							else
                                data[iptcname] = String.Join("; ", fd.GetAll());
						}
					}
				}
				string note = slideannotation;
				if (imageAnnotationOwner != null)
				{
					string a = this.GetAnnotation(imageAnnotationOwner);
					if (a != null && a.Length > 0)
					{
						if (note != null)
							note += " " + a;
						else
							note = a;
					}
				}
				if (note != null)
					data["special instructions"] = note;
				Stream outstream = new MemoryStream();
				data.Save(outstream);
				outstream.Position = 0;
				return outstream;
			}
			catch (Exception e)
			{
				TransactionLog.Instance.AddException("IPTC injection failed", 
					id.ToString(), e);
				if (instream.CanSeek)
					instream.Position = 0;
				return instream;
			}
		}

		/// <summary>
		/// Mark this image record as modified
		/// </summary>
		/// <remarks>
		/// Since image records keep a maintenance history, this overriding method takes
		/// a snapshot of the current image data before any of that data is modified.
		/// </remarks>
		public override void MarkModified()
		{
			// if modification has not started yet, save current data in an XML string,
			// which can be saved in the RecordMaintenance table once the changes are committed
			if (!Modified)
			{
				recordmaintenance = GetDataAsXmlString();
				modifieddate = DateTime.Now;
			}
			base.MarkModified();
		}

		internal void Update(bool enforcepriv, bool forcemodified)
		{
			if (enforcepriv)
			{
				if (forcemodified)
					this.MarkModified();
				this.Update();
			}
			else
			{
				if (forcemodified || Modified)
				{
					CommitChanges();
					ResetModified();
				}
			}
		}

		/// <summary>
		/// Write record maintenance entry to database
		/// </summary>
		/// <remarks>
		/// This method writes a record maintenance entry to the database.  It keeps track
		/// of the image identifier, the user performing the change, the current date and time,
		/// and a snapshot of the data before the change.
		/// </remarks>
		/// <param name="maintenancetype">The type of record maintenance being performed
		/// on the image record</param>
		protected virtual void WriteRecordMaintenance(RecordMaintenanceType maintenancetype)
		{
			// Don't write record maintenance for remote images
			if (recordmaintenance != null && remoteid == null)
			{
				User current = User.CurrentUser();
				char updatetype = ' ';
				switch (maintenancetype)
				{
					case RecordMaintenanceType.Create:
						updatetype = 'C';
						break;
					case RecordMaintenanceType.Delete:
						updatetype = 'D';
						break;
					case RecordMaintenanceType.Update:
						updatetype = 'U';
						break;
				}
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"INSERT INTO RecordMaintenance 
						(ImageID,CollectionID,UserID,ModificationDate,Modification,ImageData) 
						VALUES ({imageid},{collectionid},{userid},
						{modificationdate},{modification},{imagedata})");
					query.AddParam("imageid", id.ID);
					query.AddParam("collectionid", id.CollectionID);
					query.AddParam("userid", (current == null ? 0 : current.ID));
					query.AddParam("modificationdate", DateTime.Now);
					query.AddParam("modification", updatetype);
					query.AddParam("imagedata", recordmaintenance);
					conn.ExecQuery(query);
				}
				recordmaintenance = null;
			}
		}

		/// <summary>
		/// Data in XML format
		/// </summary>
		/// <remarks>
		/// An image record in XML format has the following structure:
		/// <code>
		/// &lt;image resource="..."&gt;
		///		&lt;TextField1&gt;...&lt;/TextField1&gt;
		///		&lt;TextField2&gt;...&lt;/TextField2&gt;
		///		&lt;DateField1 date="....-...." format="YearOnly"&gt;...&lt;/DateField1&gt;
		///		[...]
		/// &lt;/image&gt;
		/// </code>
		/// </remarks>
		/// <returns>An XmlElement holding the image resource and data.</returns>
		public XmlElement GetDataAsXml()
		{
			return GetDataAsXml(new XmlDocument());
		}

        /// <summary>
        /// Data in XML format
        /// </summary>
        /// <remarks>
        /// An image record in XML format has the following structure:
        /// <code>
        /// &lt;image resource="..."&gt;
        ///		&lt;TextField1&gt;...&lt;/TextField1&gt;
        ///		&lt;TextField2&gt;...&lt;/TextField2&gt;
        ///		&lt;DateField1 date="....-...." format="YearOnly"&gt;...&lt;/DateField1&gt;
        ///		[...]
        /// &lt;/image&gt;
        /// </code>
        /// </remarks>
        /// <returns>A string holding the image resource and data.</returns>
		public string GetDataAsXmlString()
		{
			// This call can throw an exception in ASP.NET 1.1
			// When moving to ASP.NET 2.0, the try block should probably be removed
			// see FogBugz case 921
			try
			{
				return GetDataAsXml().OuterXml;
			}
			catch (System.ArgumentException)
			{
				return null;
			}
		}

		/// <summary>
		/// Data in XML format
		/// </summary>
		/// <remarks>
		/// An image record in XML format has the following structure:
		/// <code>
		/// &lt;image resource="..."&gt;
		///		&lt;TextField1&gt;...&lt;/TextField1&gt;
		///		&lt;TextField2&gt;...&lt;/TextField2&gt;
		///		&lt;DateField1 date="....-...." format="YearOnly"&gt;...&lt;/DateField1&gt;
		///		[...]
		/// &lt;/image&gt;
		/// </code>
		/// </remarks>
		/// <param name="doc">The XML parent document</param>
		/// <returns>An XmlElement holding the image resource and data.</returns>
		public XmlElement GetDataAsXml(XmlDocument doc)
		{
			XmlElement e = doc.CreateElement("record");
			XmlAttribute a = doc.CreateAttribute("resource");
			a.Value = resource;
			e.Attributes.Append(a);
			foreach (FieldData data in fielddata.Values)
				data.AppendToXmlNode(e);
			return e;
		}
	}

	/// <summary>
	/// Record Maintenance Entry type
	/// </summary>
	/// <remarks>
	/// Every recorded image record maintenance entry is assigned one of the possible
	/// values of this enumeration.
	/// </remarks>
	public enum RecordMaintenanceType
	{
		/// <summary>
		/// Record created
		/// </summary>
		/// <remarks>
		/// The data associated with this entry is the data available upon committing
		/// the newly created record.
		/// </remarks>
		Create,
		/// <summary>
		/// Record updated
		/// </summary>
		/// <remarks>
		/// The data associated with this entry is the last version before this update.
		/// </remarks>
		Update,
		/// <summary>
		/// Record deleted
		/// </summary>
		/// <remarks>
		/// The data associated with this entry is the last version before deletion.
		/// </remarks>
		Delete
	}

	/// <summary>
	/// Image size enumeration
	/// </summary>
	/// <remarks>
	/// This enumeration defines several constants to be used with differently sized versions
	/// of images.
	/// </remarks>
	public enum ImageSize:
		int
	{
		/// <summary>
		/// Thumbnail size (usually 96x72 pixels)
		/// </summary>
		Thumbnail,
		/// <summary>
		/// Medium size 
		/// </summary>
		Medium,
		/// <summary>
		/// Full size 
		/// </summary>
		Full,
		/// <summary>
		/// Unknown size
		/// </summary>
		Unknown
	}

	/// <summary>
	/// Image Flags
	/// </summary>
	/// <remarks>
	/// This enumeration defines the different flags that apply to individual images.
	/// </remarks>
	[Flags]
	public enum ImageFlag:
		int
	{
		/// <summary>
		/// No flag set
		/// </summary>
		None					= 0,
		/// <summary>
		/// Image is shared
		/// </summary>
		Shared					= 1 << 0,
		/// <summary>
		/// Image is suggested for inclusion into regular collection
		/// </summary>
		InclusionSuggested		= 1 << 1,
		/// <summary>
		/// Image has been refused for inclusion into regular collection
		/// </summary>
		InclusionRefused		= 1 << 2,
	}

	/// <summary>
	/// Image Record Status
	/// </summary>
	/// <remarks>
	/// This enumeration defines the different flags that apply to the status of an individual
	/// image record.
	/// </remarks>
	[Flags]
	public enum ImageRecordStatus:
		int
	{
		/// <summary>
		/// Record status is normal
		/// </summary>
		Ok				= 0,
		/// <summary>
		/// Record is only a stub, not all field values are available.
		/// </summary>
		StubOnly		= 1 << 0,
	}

}
