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

namespace Orciid.Core
{
	/// <summary>
	/// Enumeration of all possible data field types
	/// </summary>
	/// <remarks>
	/// A <see cref="Field"/> in a <see cref="Collection"/> can have one of three possible types:
	/// <list type="bullet">
	/// <item>Text (no value validation)</item>
	/// <item>Date (no value validation, if value can be interpreted as a date, the record becomes
	/// searchable by date) <seealso cref="DateInterpreter"/></item>
	/// <item>Controlled List (value must exist in an associated <see cref="ControlledList"/>,
	/// although this is not strictly enforced)</item>
	/// </list>
	/// All field types are based on text fields and can hold any information or <c>null</c> or empty
	/// values.
	/// <seealso cref="Field.Type"/>
	/// </remarks>
	public enum FieldType: 
		int
	{
		/// <summary>
		/// Text field
		/// </summary>
		/// <remarks>
		/// No validation is performed on text fields. To allow compatibility with different databases,
		/// text values should never exceed 64K characters.
		/// </remarks>
		Text = 1,
		/// <summary>
		/// Date field
		/// </summary>
		/// <remarks>
		/// Date fields have two values assigned to them: a display value and an internal/actual value.
		/// The display value is the value always shown to the user. The original value is the value
		/// used to interpret the date for date searches. If only one value is given, it is used as both
		/// the display and original value. Obviously it is impossible to enforce that both values represent
		/// the same date. For date searches, only the year part of a date is relevant. Dates can either
		/// be points in time or time periods, with a start and an end point. 
		/// </remarks>
		Date = 2,
		/// <summary>
		/// Controlled list field
		/// </summary>
		/// <remarks>
		/// A controlled list field should only hold values that appear in
		/// the associated <see cref="ControlledList"/>. It is possible to assign other values to the field.
		/// </remarks>
		ControlledList = 3,
		/// <summary>
		/// Text field
		/// </summary>
		/// <remarks>
		/// This field type is similar to a plain <see cref="Text"/> field, the only difference being
		/// the way the field is searched.  When performing a keyword search on this field, the
		/// keyword is compared literally to the field value, instead of using the databases
		/// full-text search feature, which usually can detect word forms.  This field type is
		/// useful for values representing identifiers, file names, etc.
		/// </remarks>
		ExactText = 4
	}

	/// <summary>
	/// Field display options
	/// </summary>
	/// <remarks>
	/// Every <see cref="Field"/> can have different display modes depending on the context where its
	/// value is displayed. For example, in a search result screen, only the most important fields should
	/// be shown to the user, whereas in a detailed view of an individual record, all fields should be shown
	/// in their entirety.
	/// <seealso cref="Image.GetDisplayValues(Field, ViewMode, string)"/>
	/// <seealso cref="ViewMode"/>
	/// <seealso cref="Field.ShortView"/>
	/// <seealso cref="Field.MediumView"/>
	/// <seealso cref="Field.LongView"/>
	/// </remarks>
	public enum DisplayMode: 
		int
	{
		/// <summary>
		/// Do not display this field.
		/// </summary>
		None = 0,
		/// <summary>
		/// Display the complete value of this field.
		/// </summary>
		All = 1,
		/// <summary>
		/// Display the first 20 characters of this field. If whitespace appears before the 20th character,
		/// the string is truncated at that point.
		/// </summary>
		First20Char = 2,
		/// <summary>
		/// Display the first 50 characters of this field. If whitespace appears before the 50th character,
		/// the string is truncated at that point.
		/// </summary>
		First50Char = 3,
		/// <summary>
		/// Display the first sentence of this field (if it is a text field). A sentence is delimited by
		/// a full stop, an exclamation or question mark.
		/// </summary>
		FirstSentence = 4,
		/// <summary>
		/// Display the first paragraph of this field (if it is a text field). A paragraph is all text up
		/// to the first line break.
		/// </summary>
		FirstParagraph = 5
	}

	/// <summary>
	/// Record display options
	/// </summary>
	/// <remarks>
	/// The ViewMode determines which field values are displayed for an image, and if and how the values
	/// will be truncated. Every Field has a display mode for every existing view mode. Example: the 
	/// display mode for a field in <c>ViewMode.Short</c> could be <c>DisplayMode.First20Char</c>, 
	/// whereas the display mode for the same field in <c>ViewMode.Long</c> could be <c>DisplayMode.All</c>.
	/// <seealso cref="Image.GetDisplayValues(Field, ViewMode, string)"/>
	/// <seealso cref="DisplayMode"/>
	/// <seealso cref="Field.ShortView"/>
	/// <seealso cref="Field.MediumView"/>
	/// <seealso cref="Field.LongView"/>
	/// </remarks>
	public enum ViewMode: 
		int
	{
		/// <summary>
		/// Short view
		/// </summary>
		/// <remarks>
		/// This view mode should be used in overview, result screens or lists. Only the most relevant
		/// data for an image should be displayed, probably with sensible truncating of long values.
		/// </remarks>
		Short = 0,
		/// <summary>
		/// Medium view
		/// </summary>
		/// <remarks>
		/// This view should be used in screens where more, but not full, detail is required.
		/// </remarks>
		Medium = 1,
		/// <summary>
		/// This view should be used in detailed views for an image. All fields should be assigned the
		/// <c>DisplayMode.All</c> value for this view mode.
		/// </summary>
		Long = 2
	}

	/// <summary>
	/// Meta-data information for individual fields in a collection.
	/// </summary>
	/// <remarks>
	/// The field meta data information is stored in this class.
	/// The actual data is stored in the <see cref="FieldData"/> class.
	/// Different from some classes, modifications to the Field class are written
	/// to the database immediately and may have an effect on the actual data (e.g. 
	/// changes to the type of a field will drop all the data for that field from
	/// the database).
	/// </remarks>
	public class Field:
		ModifiableObject
	{
		/// <summary>
		/// The internal field identifier
		/// </summary>
		/// <remarks>
		/// This is also the primary key of the field record in the database, therefore
		/// no two fields in the system can have the same id, even if they are assigned
		/// to different collections.
		/// </remarks>
		private int id;
		/// <summary>
		/// The internal identifier of the associated collection
		/// </summary>
		/// <remarks>
		/// This is also the primary key of the collection record in the database.
		/// </remarks>
		private int collectionid;
		/// <summary>
		/// Field label
		/// </summary>
		/// <remarks>
		/// The label of the field which is shown to the user. The value of this field has no
		/// effect on the system. Duplicate labels for different fields are valid.
		/// </remarks>
		private string label;
		/// <summary>
		/// Internal field name
		/// </summary>
		/// <remarks>
		/// The internal name of the field.
		/// </remarks>
		private string name;
		/// <summary>
		/// Field type
		/// </summary>
		/// <remarks>
		/// <seealso cref="FieldType"/>
		/// </remarks>
		private FieldType type;
		/// <summary>
		/// Previous field type
		/// </summary>
		/// <remarks>
		/// This field is used to determine the required action if the field type is changed.
		/// </remarks>
		private FieldType previoustype;
		/// <summary>
		/// Dublin Core Element
		/// </summary>
		/// <remarks>
		/// The Dublin Core Element this field represents.
		/// </remarks>
		private string dcelement;
		/// <summary>
		/// Dublin Core qualifier
		/// </summary>
		/// <remarks>
		/// The Dublin Core Refinement for this field.
		/// </remarks>
		private string dcrefinement;
		/// <summary>
		/// Searchable flag
		/// </summary>
		/// <remarks>
		/// Specifies if the field is searchable. If this is set to <c>true</c>, the user interface
		/// should provide a means to do a search on this particular field.
		/// <seealso cref="keywordsearchable"/>
		/// </remarks>
		private bool searchable;
		/// <summary>
		/// Keyword searchable flag
		/// </summary>
		/// <remarks>
		/// Specifies if the field is searched when a keyword search is performed (in which the user
		/// does not select fields in particular, but does a search on the complete catalog).
		/// <seealso cref="searchable"/>
		/// </remarks>
		private bool keywordsearchable;
		/// <summary>
		/// Sortable flag
		/// </summary>
		/// <remarks>
		/// Specifies if the result of a search can be sorted by this field. This is not a restriction,
		/// the result of a search can be sorted by any field, this flag only indicates if the option
		/// to sort by this field should be presented to the user.
		/// </remarks>
		private bool sortable;
		private bool browsable;
		/// <summary>
		/// Controlled list
		/// </summary>
		/// <remarks>
		/// If the field <see cref="type"/> is <see cref="FieldType.ControlledList"/>, this property
		/// contains the internal identifier of the corresponding <see cref="ControlledList"/>.
		/// </remarks>
		private int controlledlistid;
		/// <summary>
		/// Previously assigned Controlled list
		/// </summary>
		/// <remarks>
		/// This field is used to determine the required action if the associated 
		/// controlled list is changed.
		/// </remarks>
		private int previouscontrolledlistid;
		/// <summary>
		/// Short view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in short view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// </remarks>
		private DisplayMode shortview;
		/// <summary>
		/// Medium view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in medium view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// </remarks>
		private DisplayMode mediumview;
		/// <summary>
		/// Long view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in long view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// </remarks>
		private DisplayMode longview;
		
		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// Creates a new Field object. 
		/// After all properties are set, the field must be added to a <see cref="Collection"/>
		/// using the <see cref="Collection.AddField(Field)"/> method.
		/// </remarks>
		/// <example>
		/// For an example, see the <see cref="Collection.AddField(Field)"/> method.
		/// </example>
		public Field():
			base()
		{
		}

		/// <summary>
		/// Internal constructor
		/// </summary>
		/// <remarks>
		/// This constructor is for internal use only, e.g. to create Field objects to
		/// be initialized with data from the database. 
		/// Does not initialize or check for privileges.
		/// </remarks>
		/// <param name="init">dummy parameter, should be <c>false</c></param>
		private Field(bool init):
			base(init)
		{
		}

		/// <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. Currently, every user can create a field, since this does not actually
		/// change the system. Attaching a field to a collection is restricted.
		/// </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 modification privilege
		/// </summary>
		/// <remarks>
		/// This method checks if a specified user has sufficient privileges to modify this object.
		/// For fields attached to collections, only users who can modify that collection can modify a field.
		/// All users can modify unattached fields.
		/// </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)
		{
			Collection parent = Collection.GetByID(collectionid);
			if (parent == null)
				return true;
			else
				return User.HasPrivilege(Privilege.ManageCollection, parent, user);
		}

		/// <summary>
		/// Returns a list of Field objects for the specified collection. 
		/// </summary>
		/// <remarks>
		/// This method provides an efficient way to read all the field meta data for a collection
		/// from the database. Since collections are read in only once when the system starts up, this
		/// method should not be called after that.
		/// </remarks>
		/// <param name="collectionid">The collection for which the fields need to be retrieved</param>
		/// <returns>An array containing all Field objects for the specified collecion</returns>
		internal static ArrayList GetForCollection(int collectionid)
		{
			ArrayList fields;
				
			// detect and fix invalid DisplayOrders (FogBugz case 385)
			bool fixDisplayOrder = false;
			Hashtable usedDisplayOrder = new Hashtable();

			using (DBConnection conn = DBConnector.GetConnection())
			{
				Query query = new Query(conn,
					@"SELECT ID,Label,Name,Type,DCElement,DCRefinement,Searchable,KeywordSearchable,
					Sortable,Browsable,ControlledListID,ShortView,MediumView,LongView,DisplayOrder
					FROM FieldDefinitions WHERE CollectionID={collectionid} ORDER BY DisplayOrder");
				query.AddParam("collectionid", collectionid);
				DataTable table = conn.SelectQuery(query);
				
				fields = new ArrayList(table.Rows.Count);
				foreach (DataRow row in table.Rows)
				{
					Field f = new Field(false);
					f.id = conn.DataToInt(row["ID"], 0);
					f.collectionid = collectionid;
					f.label = conn.DataToString(row["Label"]);
					f.name = conn.DataToString(row["Name"]);
					f.type = (FieldType)conn.DataToInt(row["Type"], 0);
					f.previoustype = f.type;
					f.dcelement = conn.DataToString(row["DCElement"]);
					f.dcrefinement = conn.DataToString(row["DCRefinement"]);
					f.searchable = conn.DataToBool(row["Searchable"], false);
					f.keywordsearchable = conn.DataToBool(row["KeywordSearchable"], false);
					f.sortable = conn.DataToBool(row["Sortable"], false);
					f.browsable = conn.DataToBool(row["Browsable"], false);
					f.controlledlistid = conn.DataToInt(row["ControlledListID"], 0);
					f.previouscontrolledlistid = f.controlledlistid;
					f.shortview = (DisplayMode)conn.DataToInt(row["ShortView"], 0);
					f.mediumview = (DisplayMode)conn.DataToInt(row["MediumView"], 0);
					f.longview = (DisplayMode)conn.DataToInt(row["LongView"], 0);
					fields.Add(f);
					
					// detect and fix invalid DisplayOrders (FogBugz case 385)
					if (!fixDisplayOrder)
					{
						int displayorder = conn.DataToInt(row["DisplayOrder"], 0);
						if (displayorder == 0 || usedDisplayOrder.ContainsKey(displayorder))
							fixDisplayOrder = true;
						else
							usedDisplayOrder.Add(displayorder, null);
					}
				}

				// detect and fix invalid DisplayOrders (FogBugz case 385)
				if (fixDisplayOrder)
					FixDisplayOrder(conn, fields, collectionid);

				conn.Close();
			}

			return fields;
		}

		// detect and fix invalid DisplayOrders (FogBugz case 385)
		private static void FixDisplayOrder(DBConnection conn, ArrayList fields, int cid)
		{
			for (int i = 0; i < fields.Count; i++)
			{
				Query query = new Query(conn, 
					@"UPDATE FieldDefinitions SET DisplayOrder={displayorder}
					WHERE CollectionID={cid} AND ID={fieldid}");
				query.AddParam("displayorder", i + 1);
				query.AddParam("cid", cid);
				query.AddParam("fieldid", ((Field)fields[i]).ID);
				conn.ExecQuery(query);
			}

			TransactionLog.Instance.Add("Fixed DisplayOrder",
				String.Format("Fixed invalid DisplayOrder values for collection {0}\n" +
					"Please check to make sure collection fields are in the desired order",
					cid));
		}

		/// <summary>
		/// Remove a field
		/// </summary>
		/// <remarks>
		/// Deletes a Field from the database. 
		/// WARNING: All data contained in this field is dropped from the database. This method is internal,
		/// to remove a field from outside the core package, use <see cref="Collection.RemoveField"/>.
		/// </remarks>
		internal void Delete()
		{
			Collection parent = Collection.GetByID(collectionid);
			if (parent != null)
				User.RequirePrivilege(Privilege.ManageCollection, parent);
			if (id > 0)
			{
				using (DBConnection conn = DBConnector.GetConnection())
				{
					Query query = new Query(conn,
						@"DELETE FROM FieldData WHERE FieldID={fieldid}");
					query.AddParam("fieldid", id);
					conn.ExecQuery(query);

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

		/// <summary>
		/// Commits modified field
		/// </summary>
		/// <remarks>
		/// Writes modified field 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>
		protected override void CommitChanges()
		{
			if (id < 0)
				throw new CoreException("Cannot store field with negative identifier in database");
			int result;
			Query query;
			using (DBConnection conn = DBConnector.GetConnection())
			{
				if (id == 0) 
				{
					query = new Query(conn,
						@"INSERT INTO FieldDefinitions (CollectionID,Label,Name,Type,
						DCElement,DCRefinement,Searchable,KeywordSearchable,Sortable,
						Browsable,ControlledListID,ShortView,MediumView,LongView) 
						VALUES ({collectionid},{label},{name},{type},
						{dcelement},{dcrefinement},{searchable},{keywordsearchable},{sortable},
						{browsable},{controlledlistid},{shortview},{mediumview},{longview})");
					query.AddParam("collectionid", collectionid);
					query.AddParam("label", label);
					query.AddParam("name", name);
					query.AddParam("type", type);
					query.AddParam("dcelement", dcelement);
					query.AddParam("dcrefinement", dcrefinement);
					query.AddParam("searchable", searchable);
					query.AddParam("keywordsearchable", keywordsearchable);
					query.AddParam("sortable", sortable);
					query.AddParam("browsable", browsable);
					query.AddParam("controlledlistid", controlledlistid);
					query.AddParam("shortview", shortview);
					query.AddParam("mediumview", mediumview);
					query.AddParam("longview", longview);
					result = conn.ExecQuery(query);

					if (result == 1)
						id = conn.LastIdentity("FieldDefinitions");
					else
						throw new CoreException("Could not write new Field object to database");
				}
				else
				{
					// handle type changes 
					if (type != previoustype)
					{
						// remove excess data of previous field type
						switch (previoustype)
						{
							case FieldType.Date:
								query = new Query(conn,
									@"UPDATE FieldData 
									SET StartDate=NULL,EndDate=NULL,OriginalValue=NULL
									WHERE FieldID={fieldid}");
								query.AddParam("fieldid", id);
                                conn.ExecQuery(query);
								break;
							case FieldType.ControlledList:
								query = new Query(conn,
									@"UPDATE FieldData SET ControlledListValue=NULL WHERE FieldID={fieldid}");
								query.AddParam("fieldid", id);
								conn.ExecQuery(query);
								controlledlistid = 0;
								break;
						}
						// add data for new field type
						switch (type)
						{
							case FieldType.Date:
								// create records in DateFieldsIndex initialized with field values
								query = new Query(conn,
									@"UPDATE FieldData SET OriginalValue=FieldValue
									WHERE FieldID={fieldid}");
								query.AddParam("fieldid", id);
								conn.ExecQuery(query);
								break;
							case FieldType.ControlledList:
								// set previous controlled list to -1 to force handling
								// of a controlled list change
								previouscontrolledlistid = -1;
								break;
						}
					}
					// handle controlled list changes
					if (type == FieldType.ControlledList && controlledlistid != previouscontrolledlistid)
					{
						query = new Query(conn, 
							@"UPDATE FieldData SET ControlledListValue=0 WHERE FieldID={fieldid}");
						query.AddParam("fieldid", id);
						conn.ExecQuery(query);

						query = new Query(conn,
							@"UPDATE FieldData SET ControlledListValue=ControlledListValues.ID 
							FROM ControlledListValues WHERE FieldValue LIKE ItemValue 
							AND ControlledListID={controlledlistid} AND FieldID={fieldid}");
						query.AddParam("controlledlistid", controlledlistid);
						query.AddParam("fieldid", id);
						conn.ExecQuery(query);
					}

					query = new Query(conn,
						@"UPDATE FieldDefinitions SET Label={label},Name={name},DCElement={dcelement},
						DCRefinement={dcrefinement},Searchable={searchable},
						KeywordSearchable={keywordsearchable},Sortable={sortable},Browsable={browsable},
						ShortView={shortview},MediumView={mediumview},LongView={longview},Type={type},
						ControlledListID={controlledlistid} WHERE ID={id}");
					query.AddParam("label", label);
					query.AddParam("name", name);
					query.AddParam("dcelement", dcelement);
					query.AddParam("dcrefinement", dcrefinement);
					query.AddParam("searchable", searchable);
					query.AddParam("keywordsearchable", keywordsearchable);
					query.AddParam("sortable", sortable);
					query.AddParam("browsable", browsable);
					query.AddParam("shortview", shortview);
					query.AddParam("mediumview", mediumview);
					query.AddParam("longview", longview);
					query.AddParam("type", type);
					query.AddParam("controlledlistid", controlledlistid);
					query.AddParam("id", id);
                    result = conn.ExecQuery(query);
				}
			}
			if (result != 1) 
				throw new CoreException("Could not write modified Field object to database");
		}

		/// <summary>
		/// The internal identifier of this field.
		/// </summary>
		/// <remarks>
		/// This property is read-only. For newly created and deleted field objects,
		/// this is <c>0</c>.
		/// </remarks>
		/// <value>The internal identifier of this field.</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 field.
		/// </returns>
		public override int GetID()
		{
			return id;
		}

		/// <summary>
		/// Set dummy internal identifier
		/// </summary>
		/// <remarks>
		/// Sometimes a field is "virtual", i.e. not stored in the database.  In those
		/// cases a field identifier may have to be assigned manually.  Only negative
		/// values can be used to avoid confusion with identifiers for regular fields.
		/// Once the field identifier is set to a negative value, the CommitChanges() method
		/// can no longer be called, since it would attempt to store the field to the
		/// database.
		/// </remarks>
		/// <param name="i">A negative integer to be used as the field identifier</param>
		internal void SetID(int i)
		{
			if (i >= 0)
				throw new CoreException("Cannot set positive field identifier");
			this.id = i;
		}

		/// <summary>
		/// The collection this field belongs to
		/// </summary>
		/// <remarks>
		/// Returns the internal identifier of the collection the field belongs to. This is
		/// read-only. To add a newly created field to a collection, use <see cref="Collection.AddField(Field)"/>.
		/// </remarks>
		/// <value>The collection this field belongs to</value>
		public int CollectionID
		{
			get
			{
				return collectionid;
			}
		}

		/// <summary>
		/// Connects the field to a collection. 
		/// </summary>
		/// <remarks>
		/// This can only be done to newly created fields. This method is internal; from outside 
		/// the system Core, use <see cref="Collection.AddField(Field)"/>.
		/// </remarks>
		/// <param name="cid">The internal identifier of the collection the field belongs to.</param>
		/// <exception cref="CoreException">Thrown if the field is not modifiable or has
		/// already been written to the database.</exception>
		internal void SetCollectionID(int cid)
		{
			if (id == 0)
			{
				MarkModified();
				collectionid = cid;
			}
			else
				throw new CoreException("Cannot assign Field to a Collection.");
		}

		/// <summary>
		/// Sets or returns the <see cref="FieldType"/>.
		/// </summary>
		/// <remarks>
		/// If this field is set on a not newly created field, the next call to Update
		/// will force a type change for this field. Data may be lost in this process,
		/// for example when a date field is converted to a text field, as the internal
		/// date information will be removed from the database.
		/// </remarks>
		/// <value>The <see cref="FieldType"/> of the field.</value>
		public FieldType Type
		{
			get
			{
				return type;
			}
			set
			{
				MarkModified();
				type = value;
			}
		}

		/// <summary>
		/// Sets or returns the label for this field.
		/// </summary>
		/// <remarks>
		/// The label is the name of the field used in the user interface and has no meaning within the system.
		/// It cannot be <c>null</c> or empty.
		/// </remarks>
		/// <value>The field label</value>
		/// <exception cref="CoreException">Thrown if the label is <c>null</c> or empty.</exception>
		public string Label
		{
			get
			{
				return label;
			}
			set
			{
				if (value != null & value.Length > 0)
				{
					MarkModified();
					label = (value.Length > 255 ? value.Substring(0, 255) : value);
				}
				else
					throw new CoreException("Label cannot be null or empty.");
			}
		}

		/// <summary>
		/// Sets or returns the Name of the Field.
		/// </summary>
		/// <remarks> 
		/// This is the internal field name, which might carry
		/// additional meaning, e.g. if it is a Dublin Core field name.
		/// The name cannot be <c>null</c> or empty.
		/// </remarks>
		/// <value>The field name</value>
		/// <exception cref="CoreException">Thrown if the name is <c>null</c> or empty.</exception>
		public string Name
		{
			get
			{
				return name;
			}
			set
			{
				if (value == null)
					throw new CoreException("Name cannot be null.");
				string n = value.Trim();
				if (n.Length == 0)
					throw new CoreException("Name cannot be empty.");
				MarkModified();
				name = (n.Length > 255 ? n.Substring(0, 255) : n);
			}
		}

		/// <summary>
		/// Sets or returns the Dublin Core Element.
		/// </summary>
		/// <remarks>
		/// The Dublin Core Element representing this field.
		/// </remarks>
		/// <value>Dublin Core Element</value>
		public string DCElement
		{
			get
			{
				return dcelement;
			}
			set
			{
				MarkModified();
				if (value != null && value.Length > 0)
					dcelement = (value.Length > 255 ? value.Substring(0, 255) : value);
				else
					dcelement = null;
			}
		}

		/// <summary>
		/// Sets or returns the Dublin Core Refinement.
		/// </summary>
		/// <remarks>
		/// The Dublin Core Refinement representing this field.
		/// </remarks>
		/// <value>Dublin Core Refinement</value>
		public string DCRefinement
		{
			get
			{
				return dcrefinement;
			}
			set
			{
				MarkModified();
				if (value != null && value.Length > 0)
					dcrefinement = (value.Length > 255 ? value.Substring(0, 255) : value);
				else
					dcrefinement = null;
			}
		}

		/// <summary>
		/// The combined Dublin Core element and refinement names
		/// </summary>
		/// <remarks>
		/// This read-only property contains the combined Dublin Core element and
		/// refinement names, separated by a period.  If no element name is set, the
		/// value is <c>null</c>.  If no refinement name is set, the value is just
		/// the element name, without a period.
		/// </remarks>
		/// <value>A string containing the DC Element and Refinement names (if available),
		/// separated by a period. <c>null</c> if no DC Element is set.</value>
		public string DCName
		{
			get
			{
				if (dcelement == null)
					return null;
				else
					return dcelement + (dcrefinement != null ? "." + dcrefinement : "");
			}
		}

		/// <summary>
		/// Sets or returns the searchable flag.
		/// </summary>
		/// <remarks>
		/// This flag determines if a field can be searched.
		/// <seealso cref="KeywordSearchable"/>
		/// </remarks>
		/// <value>The searchable flag</value>
		public bool Searchable
		{
			get
			{
				return searchable;
			}
			set
			{
				MarkModified();
				searchable = value;
			}
		}

		/// <summary>
		/// Sets or returns the keyword searchable flag
		/// </summary>
		/// <remarks>
		/// This flag determines if the field is included in keyword searches.
		/// <seealso cref="Searchable"/>
		/// </remarks>
		/// <value>The keyword searchable flag</value>
		public bool KeywordSearchable
		{
			get
			{
				return keywordsearchable;
			}
			set
			{
				MarkModified();
				keywordsearchable = value;
			}
		}

		/// <summary>
		/// Sets or returns the sortable flag
		/// </summary>
		/// <remarks>
		/// This flag indicates if the user should be able to sort search results
		/// by this field.
		/// </remarks>
		/// <value>
		/// The sortable flag
		/// </value>
		public bool Sortable
		{
			get
			{
				return sortable;
			}
			set
			{
				MarkModified();
				sortable = value;
			}
		}

		/// <summary>
		/// Sets or returns the browsable flag
		/// </summary>
		/// <remarks>
		/// This flag indicates if the user can browse a collection by this field.
		/// </remarks>
		/// <value>
		/// The browsable flag
		/// </value>
		public bool Browsable
		{
			get
			{
				return browsable;
			}
			set
			{
				MarkModified();
				browsable = value;
			}
		}

		/// <summary>
		/// Sets or returns the associated <see cref="ControlledList"/>.
		/// </summary>
		/// <remarks>
		/// This property holds the internal identifier of the controlled list which restricts the
		/// values this field can hold. 
		/// </remarks>
		/// <value>The internal identifier of the associated <see cref="ControlledList"/></value>
		public int ControlledListID
		{
			get
			{
				return controlledlistid;
			}
			set
			{
				MarkModified();
				controlledlistid = value;
			}
		}

		/// <summary>
		/// Short view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in short view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// <seealso cref="MediumView"/>
		/// <seealso cref="LongView"/>
		/// </remarks>
		/// <value>The short view display mode</value>
		public DisplayMode ShortView
		{
			get
			{
				return shortview;
			}
			set
			{
				MarkModified();
				shortview = value;
			}
		}

		/// <summary>
		/// Medium view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in medium view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// <seealso cref="ShortView"/>
		/// <seealso cref="LongView"/>
		/// </remarks>
		/// <value>The medium view display mode</value>
		public DisplayMode MediumView
		{
			get
			{
				return mediumview;
			}
			set
			{
				MarkModified();
				mediumview = value;
			}
		}
	
		/// <summary>
		/// Long view display mode
		/// </summary>
		/// <remarks>
		/// Specifies if and how the field is displayed in long view.
		/// <seealso cref="DisplayMode"/>
		/// <seealso cref="ViewMode"/>
		/// <seealso cref="ShortView"/>
		/// <seealso cref="MediumView"/>
		/// </remarks>
		/// <value>The long view display mode</value>
		public DisplayMode LongView
		{
			get
			{
				return longview;
			}
			set
			{
				MarkModified();
				longview = value;
			}
		}

		/// <summary>
		/// Returns the <see cref="DisplayMode"/> to be used for this field
		/// </summary>
		/// <remarks>
		/// This method returns the <see cref="DisplayMode"/> that should be used
		/// when the field value is displayed in a given <see cref="ViewMode"/>.
		/// </remarks>
		/// <param name="mode">The <see cref="ViewMode"/> in which field values will
		/// be displayed</param>
		/// <returns>The <see cref="DisplayMode"/> to be used to format field values.</returns>
		public DisplayMode GetDisplayMode(ViewMode mode)
		{
			switch (mode)
			{
				case ViewMode.Short:
					return ShortView;
				case ViewMode.Medium:
					return MediumView;
				case ViewMode.Long:
					return LongView;
				default:
					return LongView;
			}
		}

		/// <summary>
		/// Compare field names
		/// </summary>
		/// <remarks>
		/// This method compares two field names and returns <c>true</c> if the names
		/// match with respect to the given parameters. The comparison can be based on the internal
		/// name or on dublin core element and refinement values. Upper/lowercase spelling can
		/// be ignored or enforced.
		/// </remarks>
		/// <param name="n">The name to compare with this fields' name</param>
		/// <param name="ignorecase"><c>true</c> to ignore differences in upper/lowercase spelling, 
		/// <c>false</c> to do an exact match</param>
		/// <param name="dc"><c>true</c> to compare the dublin core element and refinement names,
		/// <c>false to compare the internal field names</c></param>
		/// <returns><c>true</c> if the field names match, <c>false</c> otherwise</returns>
		public bool IsNameMatch(string n, bool ignorecase, bool dc)
		{
			if (dc)
			{
				if (dcelement != null)
				{
					if (n.IndexOf(".") > 0)
						return (dcrefinement != null) &&
							(String.Compare(n, dcelement + "." + dcrefinement, ignorecase) == 0);
					else
						return (String.Compare(n, dcelement, ignorecase) == 0);
				}
				else
					return false;
			}
			else
				return (String.Compare(n, name, ignorecase) == 0);
		}
	}

	/// <summary>
	/// The basic field data class
	/// </summary>
	/// <remarks>
	/// FieldData represents the actual data for an individual field in an image
	/// record. Every field can hold several values of the same type. This is the
	/// class for the text data type, and the base class for other data types.
	/// </remarks>
	public class FieldData:
		ModifiableObject
	{
		/// <summary>
		/// The <see cref="Field"/> this data is associated with.
		/// </summary>
		/// <remarks>
		/// The FieldData objects actually hold links to their associated Field and Image objects
		/// and not just the internal identifiers to prevent unnecessary lookups.
		/// </remarks>
		protected Field field;
		/// <summary>
		/// The <see cref="Image"/> this data is associated with.
		/// </summary>
		/// <remarks>
		/// The FieldData objects actually hold links to their associated Field and Image objects
		/// and not just the internal identifiers to prevent unnecessary lookups.
		/// </remarks>
		protected Image image;
		/// <summary>
		/// The actual field values.
		/// </summary>
		/// <remarks>
		/// While multiple field values for one field are presented in a certain, fixed order,
		/// the order itself is not meant to have any significance.
		/// </remarks>
		protected ArrayList values = new ArrayList();

		/// <summary>
		/// Create a new FieldData object.
		/// </summary>
		/// <remarks>Depending on the type of the field passed
		/// to this method, an instance of the correct data class is returned.
		/// <seealso cref="FieldDataControlledList"/>
		/// <seealso cref="FieldDataDate"/>
		/// </remarks>
		/// <param name="image">The image associated with this FieldData.</param>
		/// <param name="field">The field associated with this FieldData.</param>
		/// <exception cref="CoreException">Thrown if an unknown field type is encountered.</exception>
		/// <returns>
		/// An instance of a class derived from FieldData matching the type of the <see cref="Field"/>.
		/// </returns>
		public static FieldData CreateFieldData(Image image, Field field)
		{
			switch (field.Type)
			{
				case FieldType.Text:
				case FieldType.ExactText:
					return new FieldData(image, field);
				case FieldType.Date:
					return new FieldDataDate(image, field);
				case FieldType.ControlledList:
					return new FieldDataControlledList(image, field);
				default:
					throw new CoreException("Unknown field type.");
			}
		}

		internal virtual FieldData DuplicateForImage(Image dupimage)
		{
			FieldData dupdata = CreateFieldData(dupimage, this.field);
			dupdata.MarkModified();
			dupdata.values.AddRange(this.values);
			return dupdata;
		}
		
		/// <summary>
		/// Default constructor
		/// </summary>
		/// <remarks>
		/// The default constructor for the FieldData object. Every field data object is
		/// associated with an <see cref="Image"/> and a <see cref="Field"/>.
		/// </remarks>
		/// <param name="image">The <see cref="Image"/> associated with this field data.</param>
		/// <param name="field">The <see cref="Field"/> associated with this field data.</param>
		public FieldData(Image image, Field field):
			base()
		{
			this.field = field;
			this.image = image;
		}

		internal void LinkToImage(Image img)
		{
			this.image = img;
		}

		/// <summary>
		/// Check for field data creation privileges
		/// </summary>
		/// <remarks>
		/// Every user can create new instances of field data. Privileges are checked when field data
		/// is added to an image field.
		/// </remarks>
		/// <param name="user">The user to check required privileges for</param>
		/// <returns><c>true</c></returns>
		public override bool CanCreate(User user)
		{
			return true;
		}

		/// <summary>
		/// Check for field data modification privileges
		/// </summary>
		/// <remarks>
		/// Only a user with the required privileges can modify the associated field data. This is checked against
		/// the image the field data is associated with. Any user can change the field data if it is not associated
		/// with an image.
		/// </remarks>
		/// <param name="user">The user to check required privileges for</param>
		/// <returns><c>true</c> if the user is allowed to change the field data, <c>false</c> otherwise</returns>
		public override bool CanModify(User user)
		{
			return (image == null || image.CanModify(user));
		}

		/// <summary>
		/// Number of values for this field
		/// </summary>
		/// <remarks>
		/// Every field can hold multiple values for an image record.
		/// </remarks>
		/// <value>Number of available values for this field</value>
		public int Count
		{
			get
			{
				return values.Count;
			}
		}

		/// <summary>
		/// Individual display value for this field
		/// </summary>
		/// <remarks>
		/// This property allows access to an individual field value.
		/// New field data types must override this method.
		/// </remarks>
		/// <value>An individual indexed field value</value>
		/// <param name="instance">The index of the display value to retrieve</param>
		public virtual string this[int instance]
		{
			get
			{
				return (string)values[instance];
			}
			set
			{
				MarkModified();
				if (instance >= values.Count)
					Add(value);
				else
					values[instance] = value;
			}
		}

		/// <summary>
		/// All display values for this field
		/// </summary>
		/// <remarks>
		/// This method returns all display values for this field
		/// </remarks>
		/// <returns>An array of string values</returns>
		public virtual string[] GetAll()
		{
			return (string[])values.ToArray(typeof(string));
		}

		/// <summary>
		/// Mark the field data as modified
		/// </summary>
		/// <remarks>
		/// This method marks both this field data as well as the associated image as modified.
		/// </remarks>
		public override void MarkModified()
		{
			base.MarkModified();
			if (image != null)
				image.MarkModified();
		}

		/// <summary>
		/// Add a field value
		/// </summary>
		/// <remarks>
		/// Adds an additional value to the field. Although the order of multiple values is not
		/// relevant, new values are added at the end of the value list. 
		/// New field data types must override this method.
		/// </remarks>
		/// <param name="val">The value to add</param>
		public virtual void Add(string val)
		{
			MarkModified();
			values.Add(val);
		}

		/// <summary>
		/// Removes a field value
		/// </summary>
		/// <remarks>
		/// Removes the indicated field value. All following values are moved up by
		/// one position.</remarks>
		/// <param name="instance">The index of the value to remove</param>
		public void RemoveAt(int instance)
		{
			MarkModified();
			values.RemoveAt(instance);
		}

		/// <summary>
		/// Adds a field value directly from a DataRow
		/// </summary>
		/// <remarks>
		/// This method adds a value directly from a DataRow resulting from a joined query
		/// on the FieldData and DateFieldsIndex tables. The modified flag is not set in this
		/// case, as the data is already in the database. New field data types must override this method.
		/// </remarks>
		/// <param name="conn">The DBConnection object used to originally retrieved the DataRow.
		/// Used to convert the database data types</param>
		/// <param name="row">A DataRow with at least a FieldValue column</param>
		protected internal virtual void Add(DBConnection conn, DataRow row)
		{
			values.Add(conn.DataToString(row["FieldValue"], ""));
		}

		/// <summary>
		/// Removes all field values
		/// </summary>
		/// <remarks>
		/// This method removes all field values
		/// </remarks>
		public void RemoveAll()
		{
			MarkModified();
			values.Clear();
		}

		/// <summary>
		/// Commit values to the database
		/// </summary>
		/// <remarks>
		/// This method writes all field values to the database if any modification
		/// has taken place. New field data types must override this method.
		/// </remarks>
		/// <param name="conn">An open connection object</param>
		protected internal virtual void WriteToDB(DBConnection conn)
		{
			if (Modified)
			{
				Query query = new Query(conn,
					@"DELETE FROM FieldData 
					WHERE ImageID={imageid} AND CollectionID={collectionid} AND FieldID={fieldid}");
				query.AddParam("imageid", image.ID.ID);
				query.AddParam("collectionid", image.ID.CollectionID);
				query.AddParam("fieldid", field.ID);
                conn.ExecQuery(query);

				for (int idx = 0; idx < values.Count; idx++)
				{
					query = new Query(conn,
						@"INSERT INTO FieldData 
						(ImageID,FieldID,FieldInstance,CollectionID,FieldValue) 
						VALUES ({imageid},{fieldid},{fieldinstance},{collectionid},{fieldvalue})");
					query.AddParam("imageid", image.ID.ID);
					query.AddParam("fieldid", field.ID);
					query.AddParam("fieldinstance", idx);
					query.AddParam("collectionid", image.ID.CollectionID);
					query.AddParam("fieldvalue", values[idx]);
					conn.ExecQuery(query);
				}
				ResetModified();
			}
		}

		/// <summary>
		/// Write changes to database
		/// </summary>
		/// <remarks>
		/// This method is a wrapper for <see cref="WriteToDB"/>.
		/// </remarks>
		protected override void CommitChanges()
		{
			using (DBConnection conn = DBConnector.GetConnection())
				WriteToDB(conn);
		}


		/// <summary>
		/// Internal identifier
		/// </summary>
		/// <remarks>
		/// This method is implemented to satisfy the requirements of deriving from
		/// <see cref="CachableObject"/>. If it is actually called, it will throw an
		/// exception, since FieldData objects do not have an internal identifier.
		/// </remarks>
		/// <returns>
		/// n/a
		/// </returns>
		/// <exception cref="CoreException">Thrown if this method is called.</exception>
		public override int GetID()
		{
			throw new CoreException("Invalid method call FieldData.GetID()");
		}

		/// <summary>
		/// Append field data to XmlNode
		/// </summary>
		/// <remarks>
		/// This utility method appends all values to an existing XML node. See
		/// <see cref="Image.GetDataAsXml()"/>.
		/// </remarks>
		/// <param name="node">The XmlNode to append the values of this field data object to.</param>
		public virtual void AppendToXmlNode(XmlNode node)
		{
			foreach (string v in values)
			{
				XmlElement e = node.OwnerDocument.CreateElement(field.Name);
				XmlNode text = node.OwnerDocument.CreateTextNode(v);
				e.AppendChild(text);
				node.AppendChild(e);
			}
		}

	}

	/// <summary>
	/// FieldDataDate represents date field values
	/// </summary>
	/// <remarks>
	/// Date fields are more specialized than text fields. In addition to the regular (display) value,
	/// they hold an original value and an integer, representing the year portion of the date, derived from
	/// the original value.
	/// </remarks>
	public class FieldDataDate:
		FieldData
	{

		/// <summary>
		/// Individual date values
		/// </summary>
		/// <remarks>
		/// This helper class holds individual date values:
		/// <list type="bullet">
		/// <item>Display Value</item>
		/// <item>original value</item>
		/// <item>Numeric representation of year portion of start date</item>
		/// <item>Numeric representation of year portion of end date (same as start date if no end
		/// date is specified)</item>
		/// </list>
		/// </remarks>
		private class Date
		{
			/// <summary>
			/// Display value
			/// </summary>
			/// <remarks>
			/// The display value is presented to the user. It is not used within the system.
			/// </remarks>
			private string datevalue;
			/// <summary>
			/// original value
			/// </summary>
			/// <remarks>
			/// The original value is never shown to the end user (except for catalog/content editors). It
			/// is used to determine the numeric year portions used in date searches. This string must
			/// be in one of the supported date formats.
			/// <seealso cref="DateInterpreter"/>
			/// </remarks>
			private string originalvalue;
			/// <summary>
			/// Start date
			/// </summary>
			/// <remarks>
			/// The year portion of the start date given by the original value.
			/// </remarks>
			private int startdate = int.MinValue;
			/// <summary>
			/// End date
			/// </summary>
			/// <remarks>
			/// The year portion of the end date given by the original value. This will be the same
			/// as the start date if only one date (and not a period) is specified in the original value.
			/// </remarks>
			private int enddate = int.MinValue;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Sets the display value. The original value is left blank, causing this
			/// record not to be searchable by date.
			/// </remarks>
			/// <param name="datevalue">The date value</param>
			public Date(string datevalue)
			{
				this.datevalue = datevalue;
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Sets the display value and the original value to the same value.
			/// Parses the value to determine the start and end dates.
			/// </remarks>
			/// <param name="datevalue">The date value</param>
			/// <param name="format">The format the date value is in</param>
			public Date(string datevalue, DateInterpreter.Format format)
			{
				this.datevalue = datevalue;
				this.originalvalue = datevalue;
				ParseDate(format);
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Sets the display value and the original value. Parses the original value
			/// to determine the start and end dates.
			/// </remarks>
			/// <param name="datevalue">The display value of the field</param>
			/// <param name="originalvalue">The original value of the field</param>
			/// <param name="format">The format the original value is in</param>
			public Date(string datevalue, string originalvalue, DateInterpreter.Format format)
			{
				this.datevalue = datevalue;
				this.originalvalue = originalvalue;
				ParseDate(format);
			}
			
			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Sets the display value and the original value and explicitly sets the
			/// start and end date without parsing the original value.
			/// </remarks>
			/// <param name="datevalue">The display value of the field</param>
			/// <param name="originalvalue">The original value of the field</param>
			/// <param name="startdate">The start date represented by the field</param>
			/// <param name="enddate">The end date represented by the field</param>
			public Date(string datevalue, string originalvalue, int startdate, int enddate)
			{
				this.datevalue = datevalue;
				this.originalvalue = originalvalue;
				this.startdate = startdate;
				this.enddate = enddate;
			}

			/// <summary>
			/// Parses the original date value
			/// </summary>
			/// <remarks>
			/// This method calls the <see cref="DateInterpreter"/> to extract the year portions of the 
			/// start (and, if available, end) date. The <see cref="DateInterpreter.Format"/> in 
			/// which the original value is stored must be specified. 
			/// If the parsing fails, both start and end date are reset to the default values.
			/// </remarks>
			/// <param name="format">determines the format the string date value is in</param>
			private void ParseDate(DateInterpreter.Format format)
			{
				if (!DateInterpreter.Interpret(originalvalue, format, out startdate, out enddate))
					throw new CoreException("Date is not in specified format");
			}

			/// <summary>
			/// Display value
			/// </summary>
			/// <remarks>
			/// The display value is presented to the user. It is not used for anything else in the
			/// system.
			/// </remarks>
			/// <value>
			/// The display value of the date.
			/// </value>
			public string DateValue
			{
				get
				{
					return datevalue;
				}
				set
				{
					datevalue = value;
				}
			}

			/// <summary>
			/// original value, read only.
			/// </summary>
			/// <remarks>
			/// This property is read only. To set the original value, use <see cref="SetOriginalValue"/>.
			/// </remarks>
			/// <value>
			/// The original value of the date.
			/// </value>
			public string OriginalValue
			{
				get
				{
					return originalvalue;
				}
			}

			/// <summary>
			/// Year portion of the start date
			/// </summary>
			/// <remarks>
			/// This integer is the year portion of the start date, extracted from the original value.
			/// If the original value could not be interpreted, this is <see cref="int.MinValue"/>.
			/// </remarks>
			/// <value>
			/// The year portion of the start date, or <see cref="int.MinValue"/> if not available.
			/// </value>
			public int StartDate
			{
				get
				{
					return startdate;
				}
			}

			/// <summary>
			/// Year portion of the end date
			/// </summary>
			/// <remarks>
			/// This integer is the year portion of the end date, extracted from the original value.
			/// If the original value could not be interpreted, this is <see cref="int.MinValue"/>.
			/// </remarks>
			/// <value>
			/// The year portion of the end date, or <see cref="int.MinValue"/> if not available.
			/// </value>
			public int EndDate
			{
				get
				{
					return enddate;
				}
			}

			/// <summary>
			/// Set the original date value
			/// </summary>
			/// <remarks>
			/// When setting the original date value, the <see cref="DateInterpreter.Format"/> the string
			/// is in has to be specified.
			/// </remarks>
			/// <param name="date">the date value</param>
			/// <param name="format">the format the date value is in</param>
			public void SetOriginalValue(string date, DateInterpreter.Format format)
			{
				originalvalue = date;
				ParseDate(format);
			}
		}
		
		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Initializes the date field data.
		/// </remarks>
		/// <param name="image">The image object this data is connected to</param>
		/// <param name="field">The field object this data is connected to</param>
		public FieldDataDate(Image image, Field field):
			base(image, field)
		{
		}

		/// <summary>
		/// Indexer
		/// </summary>
		/// <remarks>
		/// When writing, if a different original value is required, use the <see cref="Add(string, string, int, int)"/> or 
		/// <see cref="Set"/> methods.
		/// </remarks>
		/// <value>
		/// The display values of this field.
		/// </value>
		/// <param name="instance">The instance of the display value (<c>0</c> represents the first
		/// display value, <c>1</c> the second etc.)</param>
		public override string this[int instance]
		{
			get
			{
				return ((Date)values[instance]).DateValue;
			}
			set
			{
				MarkModified();
				if (instance >= values.Count)
					Add(value);
				else
					values[instance] = new Date(value);
			}
		}

		/// <summary>
		/// All display values for this field
		/// </summary>
		/// <remarks>
		/// This method returns all display values for this field
		/// </remarks>
		/// <returns>An array of string values</returns>
		public override string[] GetAll()
		{
			string[] v = new string[values.Count];
			for (int i = 0; i < values.Count; i++)
				v[i] = ((Date)values[i]).DateValue;
			return v;
		}


		/// <summary>
		/// Adds a value to this field.
		/// </summary>
		/// <remarks>
		/// Only the display value can be specified, so the same will
		/// be used as the original value.
		/// </remarks>
		/// <param name="val">The display value to add</param>
		public override void Add(string val)
		{
			MarkModified();
			values.Add(new Date(val));
		}

		/// <summary>
		/// Adds a value to this field.
		/// </summary>
		/// <remarks>
		/// This method adds both the display and original values. To interpret
		/// the original value, the <see cref="DateInterpreter.Format"/> has to
		/// be specified.
		/// </remarks>
		/// <param name="val">The display value to add</param>
		/// <param name="orig">The original value to add</param>
		/// <param name="format">The format the original value is in</param>
		public void Add(string val, string orig, DateInterpreter.Format format)
		{
			MarkModified();
			values.Add(new Date(val, orig, format));
		}

		/// <summary>
		/// Adds a value to this field.
		/// </summary>
		/// <remarks>
		/// This method adds the display and start and end values.  No date interpretation
		/// is performed.  Both the display and original values are set to <c>val</c>.
		/// </remarks>
		/// <param name="val">The value used for the display and original values.</param>
		/// <param name="start">The start date (year)</param>
		/// <param name="end">The end date (year)</param>
		public void Add(string val, int start, int end)
		{
			MarkModified();
			values.Add(new Date(val, val, start, end));
		}

		/// <summary>
		/// Adds a value to this field.
		/// </summary>
		/// <remarks>
		/// This method adds the display value, internal value, and start and end values.
		/// No date interpretation is performed.
		/// </remarks>
		/// <param name="val">Display value</param>
		/// <param name="orig">Original value</param>
		/// <param name="start">The start date (year)</param>
		/// <param name="end">The end date (year)</param>
		public void Add(string val, string orig, int start, int end)
		{
			MarkModified();
			values.Add(new Date(val, orig, start, end));
		}

		/// <summary>
		/// Add a date value directly from a DataRow
		/// </summary>
		/// <remarks>
		/// This method adds a value directly from a DataRow resulting from a joined query
		/// on the FieldData and DateFieldsIndex tables. The modified flag is not set in this
		/// case, as the data is already in the database.
		/// </remarks>
		/// <param name="conn">The DBConnection object used to originally retrieved the DataRow.
		/// Used to convert the database data types</param>
		/// <param name="row">A DataRow with at least FieldValue,StartDate,EndDate 
		/// and OriginalValue columns</param>
		protected internal override void Add(DBConnection conn, DataRow row)
		{
			string fieldvalue = conn.DataToString(row["FieldValue"]);
			int startdate = conn.DataToInt(row["StartDate"], int.MinValue);
			int enddate = conn.DataToInt(row["EndDate"], int.MinValue);
			string originalvalue = conn.DataToString(row["OriginalValue"]);
			values.Add(new Date(fieldvalue, originalvalue, startdate, enddate));
		}

		/// <summary>
		/// Modifies an existing value.
		/// </summary>
		/// <remarks>
		/// This method sets both the display and original values for an existing date value. 
		/// To interpret the original value, the <see cref="DateInterpreter.Format"/> has to
		/// be specified.
		/// </remarks>
		/// <param name="index">The index of the value to set. If this index does not exist, 
		/// a new value is added.</param>
		/// <param name="val">The display value to add.</param>
		/// <param name="orig">The original value to add.</param>
		/// <param name="format">The format the original value is in.</param>
		public void Set(int index, string val, string orig, DateInterpreter.Format format)
		{
			if (index >= values.Count)
				Add(val, orig, format);
			else
			{
				MarkModified();
				values[index] = new Date(val, orig, format);
			}
		}

		/// <summary>
		/// Retrieves the original value
		/// </summary>
		/// <remarks>
		/// To retrieve the display value, use the indexed property.
		/// </remarks>
		/// <param name="index">The index of the value to return</param>
		/// <returns>The original value</returns>
		public string GetOriginalValue(int index)
		{
			return ((Date)values[index]).OriginalValue;
		}

		/// <summary>
		/// Retrieves the start date.
		/// </summary>
		/// <remarks>
		/// If the start date could not be determined for this date value, <see cref="int.MinValue"/>
		/// is returned.
		/// </remarks>
		/// <param name="index">The index of the start date to return</param>
		/// <returns>The start date</returns>
		public int GetStartDate(int index)
		{
			return ((Date)values[index]).StartDate;
		}

		/// <summary>
		/// Retrieves the end date
		/// </summary>
		/// <remarks>
		/// If the end date could not be determined for this date value, <see cref="int.MinValue"/>
		/// is returned.
		/// </remarks>
		/// <param name="index">The index of the end date to return</param>
		/// <returns>The end date</returns>
		public int GetEndDate(int index)
		{
			return ((Date)values[index]).EndDate;
		}

		/// <summary>
		/// Writes the modified values to the database.
		/// </summary>
		/// <remarks>
		/// This method writes the modified date values to the database.
		/// </remarks>
		/// <param name="conn">An open database connection object</param>
		protected internal override void WriteToDB(DBConnection conn)
		{
			if (Modified)
			{
				Query query = new Query(conn,
					@"DELETE FROM FieldData 
					WHERE ImageID={imageid} AND CollectionID={collectionid} AND FieldID={fieldid}");
				query.AddParam("imageid", image.ID.ID);
				query.AddParam("collectionid", image.ID.CollectionID);
				query.AddParam("fieldid", field.ID);
				conn.ExecQuery(query);

				for (int idx = 0; idx < values.Count; idx++)
				{
					Date date = (Date)values[idx];

					query = new Query(conn,
						@"INSERT INTO FieldData 
						(ImageID,FieldID,FieldInstance,CollectionID,FieldValue,
						StartDate,EndDate,OriginalValue)
						VALUES ({imageid},{fieldid},{fieldinstance},{collectionid},{fieldvalue},
						{startdate:n},{enddate:n},{originalvalue})");
					query.AddParam("imageid", image.ID.ID);
					query.AddParam("fieldid", field.ID);
					query.AddParam("fieldinstance", idx);
					query.AddParam("collectionid", image.ID.CollectionID);
					query.AddParam("fieldvalue", date.DateValue);
					query.AddParam("startdate", date.StartDate);
					query.AddParam("enddate", date.EndDate);
					query.AddParam("originalvalue", date.OriginalValue);
					conn.ExecQuery(query);
				}
				ResetModified();
			}
		}

		/// <summary>
		/// Append field data to XmlNode
		/// </summary>
		/// <remarks>
		/// This utility method appends all values to an existing XML node. See
		/// <see cref="Image.GetDataAsXml()"/>.
		/// </remarks>
		/// <param name="node">The XmlNode to append the values of this field data object to.</param>
		public override void AppendToXmlNode(XmlNode node)
		{
			foreach (Date d in values)
			{
				XmlElement e = node.OwnerDocument.CreateElement(field.Name);
				XmlNode text = node.OwnerDocument.CreateTextNode(d.DateValue);
				e.AppendChild(text);
				XmlAttribute date = node.OwnerDocument.CreateAttribute("date");
				date.Value = String.Format("{0}-{1}", d.StartDate, d.EndDate);
				e.Attributes.Append(date);
				XmlAttribute format = node.OwnerDocument.CreateAttribute("format");
				format.Value = "YearOnly";
				e.Attributes.Append(format);
			}
		}

	}

	/// <summary>
	/// Represents the values for a controlled list field
	/// </summary>
	/// <remarks>
	/// Controlled list fields are more specialized than text fields. They are supposed to hold
	/// values from a <see cref="ControlledList"/>.
	/// </remarks>
	public class FieldDataControlledList:
		FieldData
	{

		/// <summary>
		/// Individual value of a controlled list field
		/// </summary>
		/// <remarks>
		/// This class represents an individual value of a controlled list field.
		/// </remarks>
		private class ControlledListValue
		{
			/// <summary>
			/// Internal identifier of the represented controlled list entry
			/// </summary>
			/// <remarks>
			/// If the display value of this field value does not have a corresponding controlled
			/// list entry, the id should be <c>0</c>.
			/// </remarks>
			/// <value>
			/// Internal identifier of the represented controlled list entry
			/// </value>
			public int id;
			/// <summary>
			/// Value of the controlled list entry
			/// </summary>
			/// <remarks>
			/// The display value should be equal to one of the values in the associated controlled list.
			/// If it is not, the internal identifier should be set to <c>0</c>.
			/// </remarks>
			/// <value>
			/// Value of the controlled list entry
			/// </value>
			public string displayvalue;

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Initialized the controlled list field value with the internal identifier and the
			/// display value of a controlled list entry.
			/// </remarks>
			/// <param name="i">The internal identifier of the controlled list entry</param>
			/// <param name="d">The value of the controlled list entry</param>
			public ControlledListValue(int i, string d)
			{
				id = i;
				displayvalue = d;
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Initializes the controlled list field value with the internal identifier of a controlled
			/// list entry. The matching display value is then looked up in the controlled list specified
			/// by its internal identifier.
			/// </remarks>
			/// <param name="i">The internal identifier of the controlled list entry. The
			/// corresponding value will be looked up.</param>
			/// <param name="listid">The internal identifier of the controlled list assigned
			/// to the field</param>
			/// <exception cref="CoreException">Thrown if a the controlled list specified by the identifier
			/// could not be found.</exception>
			public ControlledListValue(int i, int listid)
			{
				id = i;
				ControlledList l = ControlledList.GetByID(listid);
				if (l != null)
					displayvalue = l[id];
				if (displayvalue == null)
					throw new CoreException("Invalid controlled list entry identifier.");
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <remarks>
			/// Initializes the controlled list field value with the display value of a controlled
			/// list entry. The matching internal identifier is then looked up in the controlled list specified
			/// by its internal identifier.
			/// </remarks>
			/// <param name="d">The display value of the controlled list entry. The corresponding
			/// identifier will be looked up or set to 0 if no corresponding entry exists.</param>
			/// <param name="listid">The internal identifier of the controlled list assigned.</param>
			public ControlledListValue(string d, int listid)
			{
				displayvalue = d;
				ControlledList l = ControlledList.GetByID(listid);
				if (l != null)
					id = l[displayvalue];
			}
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// Creates a new controlled list field data object
		/// </remarks>
		/// <param name="image">The image associated with this data</param>
		/// <param name="field">The field associated with this data</param>
		public FieldDataControlledList(Image image, Field field):
			base(image, field)
		{
		}

		/// <summary>
		/// Indexer
		/// </summary>
		/// <remarks>
		/// When setting a display value, the corresponding internal identifier of the
		/// controlled list entry is looked up.
		/// </remarks>
		/// <value>
		/// Display values of this field
		/// </value>
		/// <param name="instance">The instance of the display value (<c>0</c> represents the first
		/// display value, <c>1</c> the second etc.)</param>
		public override string this[int instance]
		{
			get
			{
				return ((ControlledListValue)values[instance]).displayvalue;
			}
			set
			{
				MarkModified();
				if (instance >= values.Count)
					Add(value);
				else
					values[instance] = new ControlledListValue(value, field.ControlledListID);
			}
		}

		/// <summary>
		/// All display values for this field
		/// </summary>
		/// <remarks>
		/// This method returns all display values for this field
		/// </remarks>
		/// <returns>An array of string values</returns>
		public override string[] GetAll()
		{
			string[] v = new string[values.Count];
			for (int i = 0; i < values.Count; i++)
				v[i] = ((ControlledListValue)values[i]).displayvalue;
			return v;
		}

		/// <summary>
		/// Add a display value
		/// </summary>
		/// <remarks>
		/// When a new display value is added, the internal identifier
		/// of the matching controlled list entry is looked up.</remarks>
		/// <param name="val">The display value to add</param>
		public override void Add(string val)
		{
			MarkModified();
			values.Add(new ControlledListValue(val, field.ControlledListID));
		}

		/// <summary>
		/// Add a display value
		/// </summary>
		/// <remarks>
		/// The display value will be looked up based on the identifier of the controlled list entry
		/// </remarks>
		/// <param name="id">The internal identifier of the controlled list entry to add</param>
		public void Add(int id)
		{
			MarkModified();
			values.Add(new ControlledListValue(id, field.ControlledListID));
		}

		/// <summary>
		/// Add a display value
		/// </summary>
		/// <remarks>
		/// The specified internal identifier and display value will be used
		/// without verification against any controlled list entry
		/// </remarks>
		/// <param name="id">The internal identifier of the controlled list entry to add</param>
		/// <param name="val">The value of the controlled list entry to add</param>
		public void Add(int id, string val)
		{
			MarkModified();
			values.Add(new ControlledListValue(id, val));
		}

		/// <summary>
		/// Add a display value directly from a DataRow 
		/// </summary>
		/// <remarks>
		/// Add a display value directly from a DataRow resulting from a joined query
		/// on the FieldData and DateFieldsIndex tables. The modified flag is not set in this
		/// case, as the data is already in the database.
		/// </remarks>
		/// <param name="conn">The DBConnection object used to originally retrieved the DataRow.
		/// Used to convert the database data types</param>
		/// <param name="row">A DataRow with at least FieldValue,StartDate,EndDate 
		/// and OriginalValue columns</param>
		protected internal override void Add(DBConnection conn, DataRow row)
		{
			string fieldvalue = conn.DataToString(row["FieldValue"]);
			int id = conn.DataToInt(row["ControlledListValue"], 0);
			values.Add(new ControlledListValue(id, fieldvalue));
		}

		/// <summary>
		/// Modify a display value
		/// </summary>
		/// <remarks>
		/// If the specified value index does not exist, a new value is added.
		/// The internal identifier of the matching controlled list entry is looked up.
		/// </remarks>
		/// <param name="index">The index of the value to set.</param>
		/// <param name="val">The display value to add. The system will try to look up the
		/// corresponding controlled list entry identifier</param>
		public void Set(int index, string val)
		{
			if (index >= values.Count)
				Add(val);
			else
			{
				MarkModified();
				values[index] = new ControlledListValue(val, field.ControlledListID);
			}
		}

		/// <summary>
		/// Modify a display value
		/// </summary>
		/// <remarks>
		/// If the specified value index does not exist, a new value is added.
		/// The display value of the matching controlled list entry is looked up.
		/// </remarks>
		/// <param name="index">The index of the value to set. If this index does not exist, 
		/// a new value is added.</param>
		/// <param name="id">The identifier of the controlled list entry to add. 
		/// The system will try to look up the corresponding value</param>
		public void Set(int index, int id)
		{
			if (index >= values.Count)
				Add(id);
			else
			{
				MarkModified();
				values[index] = new ControlledListValue(id, field.ControlledListID);
			}
		}

		/// <summary>
		/// Writes the modified values to the database.
		/// </summary>
		/// <remarks>
		/// This method writes the modified date values to the database.
		/// </remarks>
		/// <param name="conn">An open database connection object</param>
		protected internal override void WriteToDB(DBConnection conn)
		{
			if (Modified)
			{
				Query query = new Query(conn,
					@"DELETE FROM FieldData 
					WHERE ImageID={imageid} AND CollectionID={collectionid} AND FieldID={fieldid}");
				query.AddParam("imageid", image.ID.ID);
				query.AddParam("collectionid", image.ID.CollectionID);
				query.AddParam("fieldid", field.ID);
				conn.ExecQuery(query);

				for (int idx = 0; idx < values.Count; idx++)
				{
					ControlledListValue v = (ControlledListValue)values[idx];
					query = new Query(conn,
						@"INSERT INTO FieldData (ImageID,FieldID,FieldInstance,CollectionID,
						FieldValue,ControlledListValue) 
						VALUES ({imageid},{fieldid},{fieldinstance},{collectionid},
						{fieldvalue},{controlledlistvalue})");
					query.AddParam("imageid", image.ID.ID);
					query.AddParam("fieldid", field.ID);
					query.AddParam("fieldinstance", idx);
					query.AddParam("collectionid", image.ID.CollectionID);
					query.AddParam("fieldvalue", v.displayvalue);
					query.AddParam("controlledlistvalue", v.id);
					conn.ExecQuery(query);
				}
				ResetModified();
			}
		}
		
		/// <summary>
		/// Append field data to XmlNode
		/// </summary>
		/// <remarks>
		/// This utility method appends all values to an existing XML node. See
		/// <see cref="Image.GetDataAsXml()"/>.
		/// </remarks>
		/// <param name="node">The XmlNode to append the values of this field data object to.</param>
		public override void AppendToXmlNode(XmlNode node)
		{
			foreach (ControlledListValue v in values)
			{
				XmlElement e = node.OwnerDocument.CreateElement(field.Name);
				XmlNode text = node.OwnerDocument.CreateTextNode(v.displayvalue);
				e.AppendChild(text);
				node.AppendChild(e);
			}
		}
	}
}