using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Caching;
using System.Web.Services;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.IO;
using Orciid.Core;

namespace Orciid.WebServices
{
	/// <summary>
	/// Web service class for the "ImageViewer" application
	/// </summary>
	/// <remarks>
	/// Facilitates all communication between the ImageViewer and the MDID. 
	/// </remarks>
	[WebService(Namespace="http://mdid.jmu.edu/webservices",
		 Description = "MDID ImageViewer Service")]
	public class ImageViewer : System.Web.Services.WebService
	{
		public ImageViewer()
		{
			//CODEGEN: This call is required by the ASP.NET Web Services Designer
			InitializeComponent();
		}
		public const int PROTOCOLVERSION = 1;
		public struct ImageViewerInfo 
		{
			public bool securelogin;
			public int protocolversion;
			public int displaybuild;
		}

		public struct SlideshowInListInfo
		{
			[XmlAttribute]
			public int id;
			[XmlAttribute]
			public bool archived;
			[XmlAttribute]
			public string createdondate;
			[XmlAttribute]
			public string modifiedondate;
			[XmlText]
			public string title;
		}

		public struct FolderInListInfo
		{
			[XmlAttribute]
			public string name;
			[XmlElement("slideshow", Type = typeof(SlideshowInListInfo))]
			public ArrayList slideshows;
		}

		public struct LoginResponseInfo 
		{
			public ResultTypes resultcode;
			public string errormessage;
			public string sessiontoken;
			[XmlArrayItem(ElementName = "folder", Type = typeof(FolderInListInfo))]
			public ArrayList slideshows;
		}

		public struct FieldInfo
		{
			[XmlAttribute]
			public string name;
			[XmlText]
			public string val;
		}

		public struct FieldsInfo
		{
			[XmlArrayItem(ElementName = "field", Type = typeof(FieldInfo))]
			public ArrayList fields;
		}

		public struct SlideInfo
		{
			public string imageresource; //Image.Resource
			public int slideid; //Slide.ID
			public int imageid; //Image.ID.ID
			public int collectionid;
			public string imagefilename;
			public string slideannotation;
			public string imagenotes;
			public FieldsInfo catalogdata;
		}

		[XmlRoot(ElementName = "SlideShowInfo", IsNullable = false)]
		public struct SlideshowInfo 
		{
			public ResultTypes resultcode;
			public string errormessage;
			[XmlArrayItem(ElementName = "Slide", Type = typeof(SlideInfo))]
			public ArrayList slides;
		}
		
		public enum ResultTypes
		{
			/// <summary>
			/// Login, GetSlideshow: No errors.
			/// </summary>
			SUCCESS,
			/// <summary>
			/// Login: Called with HTTP GET (must use POST)
			/// </summary>
			INVALIDHTTPREQUEST,
			/// <summary>
			/// Login: Called with invalid UserID/Password combination
			/// </summary>
			USERNOTFOUND,
			/// <summary>
			/// Login: Cannot find User in database
			/// </summary>
			INVALIDCREDENTIALS,
			/// <summary>
			/// GetSlideshow: Cannot find slideshow among user's slideshows
			/// </summary>
			SLIDESHOWNOTFOUND,
			/// <summary>
			/// GetSlideshow: Slideshow does not contain any slides
			/// </summary>
			EMPTYSLIDESHOW,
			/// <summary>
			/// GetSlideshow: Session token invalid or session expired
			/// </summary>
			INVALIDSESSION,
			/// <summary>
			/// Login, GetSlideshow: Server problem or some other problem
			/// </summary>
			UNKNOWN,

			IMAGEVIEWERACCESSDENIED
		}

		#region Component Designer generated code
		
		//Required by the Web Services Designer 
		private IContainer components = null;
				
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if(disposing && components != null)
			{
				components.Dispose();
			}
			base.Dispose(disposing);		
		}
		
		#endregion
/*
		#region XSD
		const string DISPLAYXSD = @"
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' elementFormDefault='qualified' attributeFormDefault='unqualified'>
  <xs:element name='display'>
    <xs:complexType>
      <xs:sequence>
        <xs:element name='build' type='xs:integer'/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
";
		#endregion
*/
		/// <summary>
		/// Get ImageViewer preferences and login security flag
		/// </summary>
		/// <remarks>
		/// Returns the default ImageViewer preferences and a security flag indicating
		/// whether login is performed over a secure connection.
		/// </remarks>
		/// <returns>The MDID System Adminitrator-configured default values for the 
		/// ImageViewer preferences (these values are located in "imageviewerdefaults.xml")
		/// and  a boolean value indicating whether login is performed over an SSL connection
		/// (this value is located in "appconfig.xml").</returns>
		[WebMethod]
		public ImageViewerInfo GetInfo()
		{
			ImageViewerInfo imageviewerinfo = new ImageViewerInfo();
			imageviewerinfo.securelogin = Configuration.Instance.GetBool("imageviewer.securelogin");
			imageviewerinfo.protocolversion = PROTOCOLVERSION;
/*			try 
			{
				XmlValidatingReader reader = null;
				string displayxml = Path.Combine(Configuration.GetString("content.downloads"),
					"internal/imageviewer2/display.xml");
				reader = new XmlValidatingReader(new XmlTextReader(new StreamReader(displayxml)));
				XmlSchemaCollection schemas = new XmlSchemaCollection();
				schemas.Add(null, new XmlTextReader(new StringReader(DISPLAYXSD)));
				reader.ValidationType = ValidationType.Schema;
				reader.Schemas.Add(schemas);
				XmlDocument doc = new XmlDocument();
				doc.Load(reader);
				XmlNode buildnode = doc.SelectSingleNode("/display/build");
				imageviewerinfo.displaybuild = Int32.Parse(buildnode.InnerText);
				reader.Close();
			} 
			catch (Exception ex) 
			{
				TransactionLog.AddException("Error reading display build file", ex);
*/				
			imageviewerinfo.displaybuild = -1;
/*			}*/
			return imageviewerinfo;
		}

		/// <summary>
		/// Authenticate user
		/// </summary>
		/// <remarks>
		/// This method is used to authenticate user/password pairs. If authentication is
		/// successful, user object is created and stored in cache. The login attempt will
		/// fail if this method is called using an HTTP Get (for example, this will not work:
		/// http://mdid.jmu.edu/webservices/imageviewer.asmx/Login?userID=heggkj&amp;password=test).
		/// If user is successfully authenticated, a user object is cached for use with 
		/// GetSlideshows. The LoginResponseInfo.sessiontoken must accompany all calls to
		/// GetSlideshows.</remarks>
		/// <param name="userID">The user to authenticate</param>
		/// <param name="password">The password to authenticate with</param>
		/// <returns>A LoginResponse structure containing: (i) a flag indicating whether the 
		/// login attempt succeeded or failed, (ii) an error code, (iii) a humanreadable error 
		/// message, (iv) a session token (if login was successful), and (v) a list of the 
		/// user's slideshows.</returns>
		[WebMethod]
		public LoginResponseInfo Login(string userID, string password)
		{
			if (Configuration.Instance.GetBool("imageviewer.securelogin") &&
				!Context.Request.IsSecureConnection)
				throw new Exception("SSL is required for this web service call.");
			LoginResponseInfo loginresponse = new LoginResponseInfo();
			int slidingexpiration = 60;
			if (Context.Request.HttpMethod.ToLower() == "get") 
			{
				loginresponse.resultcode = ResultTypes.INVALIDHTTPREQUEST;
				loginresponse.errormessage = "This method must be called using SOAP or HTTP POST.";
				return loginresponse;
			}
			Orciid.Core.User user = Orciid.Core.User.GetByLogin(userID, password);
			if (user == null) 
			{
				loginresponse.resultcode = ResultTypes.USERNOTFOUND;
				loginresponse.errormessage = "The specified user was not found.";
				return loginresponse;
			}
			if (!user.IsAuthenticated) 
			{
				loginresponse.resultcode = ResultTypes.INVALIDCREDENTIALS;
				loginresponse.errormessage = "The specified user/password combination is not valid.";
				return loginresponse;
			}
			user.Activate(Context.Request.UserHostAddress);

			if (!Orciid.Core.User.HasPrivilege(Privilege.ImageViewerAccess))
			{
				loginresponse.resultcode = ResultTypes.IMAGEVIEWERACCESSDENIED;
				loginresponse.errormessage = "You do not have permission to use the ImageViewer with this server.";
				return loginresponse;
			}

			loginresponse.resultcode = ResultTypes.SUCCESS;
			loginresponse.sessiontoken = "user_" + Guid.NewGuid().ToString();

			Context.Cache.Insert(loginresponse.sessiontoken, user, null, Cache.NoAbsoluteExpiration, new TimeSpan(0,slidingexpiration,0));

			loginresponse.slideshows = new ArrayList();
			FolderInListInfo folder = new FolderInListInfo();
			folder.name = "[MAIN]";
			folder.slideshows = new ArrayList();
			foreach (Slideshow show in Slideshow.GetByFolder(user.ID, 0))
			{
				SlideshowInListInfo info = new SlideshowInListInfo();
				info.id = show.ID;
				info.title = show.Title;
				info.archived = show.ArchiveFlag;
				info.createdondate = show.CreationDate;
				info.modifiedondate = show.ModificationDate;
				folder.slideshows.Add(info);
			}
			loginresponse.slideshows.Add(folder);
			SortedList folders = user.GetFolders();
			foreach (string name in folders.Keys)
			{
				folder = new FolderInListInfo();
				folder.name = name;
				folder.slideshows = new ArrayList();
				foreach (Slideshow show in Slideshow.GetByFolder(user.ID, (int)folders[name]))
				{
					SlideshowInListInfo info = new SlideshowInListInfo();
					info.id = show.ID;
					info.title = show.Title;
					info.archived = show.ArchiveFlag;
					info.createdondate = show.CreationDate;
					info.modifiedondate = show.ModificationDate;
					folder.slideshows.Add(info);
				}
				loginresponse.slideshows.Add(folder);
			}
			loginresponse.errormessage = "n/a";
			return loginresponse;
		}

		/// <summary>
		/// Refresh the user session to delay expiration
		/// </summary>
		/// <remarks>
		/// Gratuitously retrieves the user object identified by the supplied session token
		/// in order to delay expiration by the number of minutes specified by the
		/// slidingexpiration node in the appconfig.xml file.
		/// </remarks>
		[WebMethod]
		public void RefreshSession(string sessiontoken)
		{
			Context.Cache.Get(sessiontoken);
		}

		/// <summary>
		/// Get a slideshow
		/// </summary>
		/// <remarks>
		/// Returns the requested slideshow using the supplied session token.
		/// </remarks>
		/// <param name="sessiontoken">The session token used to identify and retrieve the
		/// correct user object from the cache. This is the LoginResponseInfo.sessiontoken
		/// that Login returns.</param>
		/// <param name="slideshowID">The ID used to identify a slideshow</param>
		/// <returns>If the session token is invalid or has expired, the method returns a
		/// result code indicating failure and a human-readable error message. If the session 
		/// token is valid, this method uses the slideshowID to retrieve the specified 
		/// slideshow. If the specified slideshow exists (within the context of the current 
		/// user, as determined by the sessiontoken), this method sets the result code to 
		/// success and returns for each slide all pertinent information.</returns>
		[WebMethod]
		public SlideshowInfo GetSlideshow(int slideshowID, string sessiontoken)
		{
			SlideshowInfo slideshowinfo = new SlideshowInfo();
			Orciid.Core.User user = (Orciid.Core.User)Context.Cache.Get(sessiontoken);
			if (user == null) 
			{
				slideshowinfo.resultcode = ResultTypes.INVALIDSESSION;
				slideshowinfo.errormessage = "Session is invalid or has expired.";
				return slideshowinfo;
			}
			user.Activate(Context.Request.UserHostAddress);
			Slideshow targetshow = Slideshow.GetByID(slideshowID);
			if (targetshow == null) 
			{
				slideshowinfo.resultcode = ResultTypes.SLIDESHOWNOTFOUND;
				slideshowinfo.errormessage = "Slideshow was not found.";
				return slideshowinfo;
			}
			Orciid.Core.Slide[] targetslides = targetshow.GetSlides();
			if (targetslides == null) 
			{
				slideshowinfo.resultcode = ResultTypes.EMPTYSLIDESHOW;
				slideshowinfo.errormessage = "Slideshow is empty.";
				return slideshowinfo;
			}
			ImageIdentifier[] imageIDs = new ImageIdentifier[targetslides.Length];
			for(int i=0; i<targetslides.Length; i++) 
			{
				imageIDs[i] = targetslides[i].ImageID;
			}
			Image[] images = Image.GetByID(imageIDs);
			if (images == null) 
			{
				slideshowinfo.resultcode = ResultTypes.EMPTYSLIDESHOW;
				slideshowinfo.errormessage = "Slideshow is empty.";
				return slideshowinfo;
			}
			
			slideshowinfo.slides = new ArrayList();
			//ArrayList data = new ArrayList();
			Collection coll;
			//XmlDocument doc = new XmlDocument();
			for(int i = 0; i < targetslides.Length; i++)
			{
				if (!targetslides[i].Scratch) 
				{
					SlideInfo info = new SlideInfo();
					if (images[i] != null)
					{
						info.imageresource = String.Format("getimage.aspx?id={0}&cid={1}&res={2}&format=F&token={3}&ssid={4}", 
							images[i].ID.ID, images[i].ID.CollectionID, images[i].Resource, sessiontoken,
							targetshow.ID);
						info.slideid = targetslides[i].ID;
						info.imageid = images[i].ID.ID;
						info.collectionid = images[i].ID.CollectionID;
						info.imagefilename =  info.collectionid.ToString() + "_" + info.imageid.ToString() + ".jpg";
						info.slideannotation = targetslides[i].Annotation;
						info.imagenotes = images[i].GetAnnotation(Orciid.Core.User.GetByID(targetshow.Owner));
						info.catalogdata.fields = new ArrayList();
						coll = Collection.GetByID(images[i].CollectionID);
						if (coll != null)
						{
							FieldInfo fi = new FieldInfo();
							fi.name = "Collection";
							fi.val = coll.Title;
							info.catalogdata.fields.Add(fi);
							foreach (Field f in coll.GetFields())
								foreach (string s in images[i].GetDisplayValues(f))
								{
									fi = new FieldInfo();
									fi.name = f.Label;
									fi.val = s;
									info.catalogdata.fields.Add(fi);
								}
						}
					}
					else
					{
						// image is not available						
					}
					slideshowinfo.slides.Add(info);
				}
			}
			return slideshowinfo;
		}
	}
}
