using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Text.RegularExpressions;
using Orciid.Core;
using Orciid.Media;
using Orciid.Media.Converters;
using ICSharpCode.SharpZipLib.Zip;

namespace Orciid
{
	public class package : PageTemplate.OrciidPage
	{
		protected System.Web.UI.WebControls.Label SlideshowTitleLabel;
		protected System.Web.UI.WebControls.HyperLink BackHyperLink;
		protected System.Web.UI.WebControls.Button CreatePackageButton;
		protected System.Web.UI.WebControls.RadioButton PackageRadioButton;
		protected System.Web.UI.WebControls.DropDownList OSDropDownList;
		protected System.Web.UI.WebControls.CheckBox ImageViewerCheckBox;
		protected System.Web.UI.WebControls.CheckBox FlashViewerCheckBox;
		protected System.Web.UI.WebControls.DropDownList ImageSizeDropDownList;
		protected System.Web.UI.WebControls.CheckBox DataSlidesCheckBox;
		protected System.Web.UI.WebControls.DropDownList TargetDeviceDropDownList;
		protected System.Web.UI.WebControls.RadioButton PortableRadioButton;
		protected System.Web.UI.WebControls.Repeater AgreementsRepeater;
		protected System.Web.UI.HtmlControls.HtmlGenericControl Heading;
		#region XSD
		const string PackageXSD = @"
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema' elementFormDefault='qualified' attributeFormDefault='unqualified'>
  <xs:element name='package'>
    <xs:complexType>
      <xs:sequence>
        <xs:element name='file' maxOccurs='unbounded'>
          <xs:complexType>
            <xs:simpleContent>
              <xs:extension base='xs:string'>
                <xs:attribute name='file' type='xs:string' use='required'/>
                <xs:attribute name='path' type='xs:string' use='required'/>
                <xs:attribute name='os' use='optional'>
                  <xs:simpleType>
                    <xs:restriction base='xs:string'>
                      <xs:enumeration value='osx'/>
                      <xs:enumeration value='win'/>
                    </xs:restriction>
                  </xs:simpleType>
                </xs:attribute>
                <xs:attribute name='viewer' use='optional'>
                  <xs:simpleType>
                    <xs:restriction base='xs:string'>
                      <xs:enumeration value='imageviewer'/>
                      <xs:enumeration value='flashviewer'/>
                    </xs:restriction>
                  </xs:simpleType>
                </xs:attribute>
                <xs:attribute name='compress' use='optional' default='9'>
                  <xs:simpleType>
                    <xs:restriction base='xs:int'>
                      <xs:minInclusive value='0'/>
                      <xs:maxInclusive value='9'/>
                    </xs:restriction>
                  </xs:simpleType>
                </xs:attribute>
              </xs:extension>
            </xs:simpleContent>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
";
		#endregion

		public void AddFileToZip(ZipOutputStream zos, String physicalpathname, String entry) 
		{
			FileStream fs = File.OpenRead(physicalpathname);
			ZipEntry ze = new ZipEntry(entry);
			zos.PutNextEntry(ze);
			CopyStream(fs, zos, null);
			fs.Close();
		}

		public void CopyStream(Stream instream, Stream outstream, HttpResponse response) 
		{
			byte[] buffer = new byte[65536];
			int bytesread = 0;
			do 
			{
				bytesread = instream.Read(buffer, 0, buffer.Length);
				if (bytesread > 0) 
					outstream.Write(buffer, 0, bytesread);
			} while (bytesread > 0 && (response == null || response.IsClientConnected));
		}

		public Stream GetImageViewerPrefsFile()
		{
			StreamReader sr = File.OpenText(
				Path.Combine(Configuration.Instance.GetString("content.templates"), "imageviewer2prefs.xml"));
			string content = String.Format(sr.ReadToEnd(), 
				Request.Url.GetLeftPart(UriPartial.Authority) + Request.ApplicationPath);
			byte[] b = new byte[content.Length];
			for (int i = 0; i < content.Length; i++)
				b[i] = (byte)content[i];
			return new MemoryStream(b);
		}


/*		protected override object LoadPageStateFromPersistenceMedium()
		{
			// overridden because of exceptions in the base method caused 
			// by certain versions of IE on the Mac, and since we don't
			// need a view state anyway
			return null;
		}
*/
		private Slideshow slideshow = null;

		private void LoadSlideshow()
		{
			if (slideshow != null)
				return;
			string slideshowid = Request.QueryString["ssid"];
			Orciid.Core.User user = Session["CurrentUser"] as Orciid.Core.User;
			if (user != null) 
				user.Activate(Request.UserHostAddress);
			try
			{
				slideshow = Slideshow.GetByID(Int32.Parse(slideshowid));
			}
			catch
			{
				slideshow = null;
			}
		}

		protected override bool CheckUserPrivileges(Orciid.Core.User user)
		{
			LoadSlideshow();
			return (slideshow == null) ||
				Orciid.Core.User.HasPrivilege(Privilege.ModifySlideshow, slideshow);
		}

		public override string GetHighlightedMenuItem()
		{
			return null;
		}

		private void Page_Load(object sender, System.EventArgs e)
		{
			LoadSlideshow();
			if (slideshow == null)
			{
				// no slideshow specified, so we probably just want to create a package
				// with the viewer software
				string os = (Request.QueryString["os"] == "osx" ? "osx" : "win");
				CreatePackage(os, true, false, ImageSize.Unknown);
				return;
			}
			SlideshowTitleLabel.Text = slideshow.Title;
			BackHyperLink.NavigateUrl = String.Format("slideshow.aspx?id={0}", slideshow.ID);			

			Hashtable collections = new Hashtable();
			foreach (Slide slide in slideshow.GetSlides())
				if (!collections.ContainsKey(slide.ImageID.CollectionID))
				{
					Collection coll = Collection.GetByID(slide.ImageID.CollectionID);
					if (coll != null)
						collections.Add(slide.ImageID.CollectionID, coll);
				}

			ArrayList list = new ArrayList();
			foreach (Collection coll in collections.Values)
				if (coll.UsageAgreement != null)
					list.Add(coll);

			AgreementsRepeater.DataSource = list;
			AgreementsRepeater.DataBind();
		}

		protected void DataBindAgreementTextBox(object sender, System.EventArgs e)
		{
			TextBox textbox = sender as TextBox;
			textbox.Text = ((Collection)((RepeaterItem)textbox.Parent).DataItem).UsageAgreement;
		}

		private void CreatePackage(string os, bool imgview, bool ssview, ImageSize imagesize)
		{
			Response.Clear();
			Response.Buffer = false;
			Response.Expires = -1;
			string tempfile = Path.GetTempFileName();
			
			// CREATE ZIP FILE

			string basedir = Path.Combine(
				Path.Combine(Configuration.Instance.GetString("content.downloads"), "internal"),
				"imageviewer2");

			Response.ContentType = "application/zip";
			Response.AppendHeader("Content-disposition", 
				"attachment; filename=" +
				(slideshow != null ? "slideshow_" + slideshow.ID + ".zip" : "imageviewer2.zip"));
			ZipOutputStream zos = new ZipOutputStream(File.Open(tempfile, FileMode.Open));

			// ADD FILES AS SPECIFIED IN "package.xml"

			XmlReader textreader = new XmlTextReader(Path.Combine(basedir, "package.xml"));
			XmlValidatingReader reader = new XmlValidatingReader(textreader);
			XmlSchemaCollection schemas = new XmlSchemaCollection();
			schemas.Add(null, new XmlTextReader(new StringReader(PackageXSD)));
			reader.ValidationType = ValidationType.Schema;
			reader.Schemas.Add(schemas);
			XmlDocument doc = new XmlDocument();
			doc.Load(reader);
			XmlNodeList nodes = doc.SelectNodes("/package/file");
			foreach (XmlNode node in nodes)
			{
				// check operating system
				if (node.Attributes["os"] != null && node.Attributes["os"].Value != os)
					continue;
				// check target package type (standalone show or viewer only)
				if (node.Attributes["viewer"] != null)
				{
					string viewer = node.Attributes["viewer"].Value;
					if ((slideshow == null && viewer == "flashviewer") ||
						(viewer == "flashviewer" && !ssview) ||
						(viewer == "imageviewer" && !imgview))
						continue;
				}
				// set compression level
				if (node.Attributes["compress"] != null)
					zos.SetLevel(Int32.Parse(node.Attributes["compress"].Value));
				else
					zos.SetLevel(9);
				// add files to ZIP
				AddFileToZip(zos, Path.Combine(basedir, node.Attributes["file"].Value),
					node.Attributes["path"].Value);
			}
			reader.Close();

			// ADD VIEWER PREFERENCE FILE

			if (slideshow == null && imgview)
			{
				zos.SetLevel(9);
				zos.PutNextEntry(new ZipEntry("prefs/prefs.xml"));
				CopyStream(GetImageViewerPrefsFile(), zos, null);
			}

			// ADD IMAGES

			if (slideshow != null)
			{
				zos.SetLevel(0);
				Orciid.Core.Image[] images = slideshow.GetImages();
				Orciid.Core.Slide[] slides = slideshow.GetSlides();
				Orciid.Core.User showowner = Orciid.Core.User.GetByID(slideshow.Owner);
				Hashtable filenames = new Hashtable(images.Length);
				for (int i = 0; i < images.Length; i++)  
				{
					if (images[i] != null && !slides[i].Scratch && 
						!filenames.ContainsKey(images[i].ID)) 
					{
						string filename = GetImageFileName(images[i], i);
						filenames.Add(images[i].ID, filename);
						ZipEntry ze1 = new ZipEntry("images/" + filename);
						zos.PutNextEntry(ze1);
						Stream stream = 
							images[i].AddCatalogingDataToStream(
							slideshow.GetResourceData(null, images[i].ID, imagesize),
							showowner, slides[i].Annotation);
						CopyStream(stream, zos, null);
					}
				}

			// ADD SLIDESHOW DATA

				zos.SetLevel(9);
				MemoryStream ms = new MemoryStream();
				XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
				writer.Formatting = Formatting.Indented;
				GetPackageXML(slideshow, false, filenames).WriteTo(writer);
				writer.Flush();
				ZipEntry ze = new ZipEntry("slideshow.xml");
				zos.PutNextEntry(ze);
				ms.Position = 0;
				CopyStream(ms, zos, null);
				ms.Close();
			}

			// CLOSE ZIP FILE

			zos.Finish();
			zos.Close();

			// SEND ZIP FILE TO CLIENT

			FileStream fs = File.OpenRead(tempfile);
			Response.AddHeader("Content-length", fs.Length.ToString());
			CopyStream(fs, Response.OutputStream, Response);
			fs.Close();

			if (File.Exists(tempfile)) 
				File.Delete(tempfile);
			Response.End();
		}

		private void CreatePortablePackage(int width, int height, bool dataslides)
		{
			if (slideshow == null)
			{
				ShowWarning(GetLocalString(this, "NoShowSelected", "No slideshow selected for packaging"));
				return;
			}

			Response.Clear();
			Response.Buffer = false;
			Response.Expires = -1;
			string tempfile = Path.GetTempFileName();
			
			// image parameters
			Parameters parameters = new Parameters(width, height, 80);

			// CREATE ZIP FILE

			Response.ContentType = "application/zip";
			Response.AppendHeader("Content-disposition", String.Format(
				"attachment; filename=slideshow_{0}_portable.zip",
				slideshow.ID));
			ZipOutputStream zos = new ZipOutputStream(File.Open(tempfile, FileMode.Open));
			zos.SetLevel(0); // don't compress anything, since it's already compressed images

			// ADD IMAGES

			SolidBrush whitebrush = new SolidBrush(Color.White);
			SolidBrush blackbrush = new SolidBrush(Color.Black);
			Font font = new Font(FontFamily.GenericSansSerif, 7);
	
			Orciid.Core.Image[] images = slideshow.GetImages();
			Orciid.Core.Slide[] slides = slideshow.GetSlides();
			Orciid.Core.User showowner = Orciid.Core.User.GetByID(slideshow.Owner);
			for (int i = 0; i < images.Length; i++)  
			{
				if (images[i] != null && !slides[i].Scratch) 
				{
					ZipEntry ze1 = new ZipEntry(GetImageFileName(images[i], i));
					zos.PutNextEntry(ze1);
					Collection coll = Collection.GetByID(images[i].ID.CollectionID);
					if (coll != null)
					{
						Stream stream = 
							images[i].AddCatalogingDataToStream(
							coll.GetResourceData(images[i].Resource, parameters),
							showowner, slides[i].Annotation);
						CopyStream(stream, zos, null);

						if (dataslides)
						{
							using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb))
							{
								using (Graphics g = Graphics.FromImage(bitmap))
								{
									g.FillRectangle(whitebrush, 0, 0, width, height);
									int y = 0;
									foreach (Field field in coll.GetFields())
									{
										foreach (string val in images[i][field].GetAll())
										{
											if (y > height)
												break;
											g.DrawString(String.Format("{0}: {1}", 
												field.Label, val), font, blackbrush, 0, y);
											y += 9;
										}
									}
								}
								ZipEntry ze2 = new ZipEntry(
									Path.GetFileNameWithoutExtension(GetImageFileName(images[i], i)) + "_data.png");
								zos.PutNextEntry(ze2);
								// save bitmap to temporary memory stream since saving directly to
								// ZIP stream is not supported
								MemoryStream temp = new MemoryStream();
								bitmap.Save(temp, ImageFormat.Png);
								temp.Position = 0;
								CopyStream(temp, zos, null);
							}
						}
					}
				}
			}

			// CLOSE ZIP FILE

			zos.Finish();
			zos.Close();

			// SEND ZIP FILE TO CLIENT

			FileStream fs = File.OpenRead(tempfile);
			Response.AddHeader("Content-length", fs.Length.ToString());
			CopyStream(fs, Response.OutputStream, Response);
			fs.Close();

			if (File.Exists(tempfile)) 
				File.Delete(tempfile);
			Response.End();
		}

		private static string GetImageFileName(Orciid.Core.Image image, int position)
		{			
			string name = String.Format("{0}_{1}_{2}", 
				position.ToString("0000"), 
				image.ID.ToString(), 
				Path.GetFileNameWithoutExtension(image.Resource));
			Regex regex = new Regex("[^a-zA-Z0-9]+");
			return regex.Replace(name, "_") + ".jpg";
		}

		/// <summary>
		/// Gets XML representation of a single slideshow.
		/// </summary>
		/// <remarks>The current user must have read access to the requested slideshow.</remarks>
		/// <param name="show">The slideshow to return to the client.</param>
		/// <param name="includeScratch">If true, slides marked "scratch" are included.</param>
		/// <param name="filenames">Hashtable of image identifiers and strings.  If an
		/// image identifier exists as a key, the corresponding value will be used as the
		/// filename of the JPEG file for the image.  This way, duplicate images will only
		/// have one image file in the package, and the linking will work (see case 1908)</param>
		/// <returns>An XML document with two nodes: The first node lists of the properties of
		/// the slideshow; the second node lists each slide in the slideshow. A slide is an image
		/// and its corresponding catalog information, slide annotation, and image notes.</returns>
		public static XmlDocument GetPackageXML(Slideshow show, bool includeScratch,
			Hashtable filenames)
		{
			if (show == null)
				return null;
			Slide[] slides = show.GetSlides();
			XmlDocument doc = new XmlDocument();
			if (slides == null || slides.Length == 0) 
			{
				return doc;
			}
			doc.Load(new StringReader("<MDID version='2'></MDID>"));
			XmlDeclaration declaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);
			doc.InsertBefore(declaration, doc.DocumentElement);

			XmlElement slideshowproperties = doc.CreateElement("SlideshowProperties", null);

			XmlAttribute slideshowid = doc.CreateAttribute("slideshowid");
			slideshowid.Value = show.ID.ToString();
			slideshowproperties.SetAttributeNode(slideshowid);
			
			XmlElement title = doc.CreateElement("title");
			title.InnerText = show.Title;
			slideshowproperties.AppendChild(title);

			XmlElement owner = doc.CreateElement("owner");
			Orciid.Core.User showowner = Orciid.Core.User.GetByID(show.Owner);
			owner.InnerText = showowner.FirstName + " " + showowner.LastName;
			slideshowproperties.AppendChild(owner);

			XmlElement creationdate = doc.CreateElement("creationdate");
			creationdate.InnerText = show.CreationDate;
			slideshowproperties.AppendChild(creationdate);

			XmlElement modificationdate = doc.CreateElement("modificationdate");
			modificationdate.InnerText = show.ModificationDate;
			slideshowproperties.AppendChild(modificationdate);
			doc.DocumentElement.AppendChild(slideshowproperties);

			XmlElement packagedondate = doc.CreateElement("packagedondate");
			packagedondate.InnerText = DateTime.Now.ToShortDateString();
			slideshowproperties.AppendChild(packagedondate);
			doc.DocumentElement.AppendChild(slideshowproperties);

			Orciid.Core.User user = Orciid.Core.User.CurrentUser();
			SortedList folderlist = user.GetFolders();
			int titleindex = folderlist.IndexOfValue(show.Folder);
			String foldertitle = "";
			if (titleindex > -1) foldertitle = (String)folderlist.GetKey(titleindex);
			XmlElement folder = doc.CreateElement("folder");
			folder.InnerText = foldertitle;
			slideshowproperties.AppendChild(folder);

			XmlElement slidesnode = doc.CreateElement("Slides", "");
			Int32 numslides = 0;
			Orciid.Core.Image[] images = show.GetImages();
			for(int i=0; i < slides.Length; i++) 
			{
				if (slides[i].Scratch != true || includeScratch == true) 
				{
					numslides += 1;
					XmlElement slidenode = doc.CreateElement("Slide");
					XmlElement imageresourcenode = doc.CreateElement("imageresource");
					if (images[i] != null)
					{
						string filename;
						if (filenames != null && filenames.ContainsKey(images[i].ID))
							filename = "images/" + (string)filenames[images[i].ID];
						else
							filename = "images/" + GetImageFileName(images[i], i);
						imageresourcenode.InnerText = filename;
					}					
					else
						imageresourcenode.InnerText = "viewerbin/imageunavailable.jpg";
					slidenode.AppendChild(imageresourcenode);

					XmlElement slideidnode = doc.CreateElement("slideid");
					slideidnode.InnerText = slides[i].ID.ToString();
					slidenode.AppendChild(slideidnode);

					string ann = slides[i].Annotation;
					if (ann != null && ann.Length > 0)
					{
						XmlElement slideannotationnode = doc.CreateElement("slideannotation");
						slideannotationnode.InnerText = ann;
						slidenode.AppendChild(slideannotationnode);
					}

					string note = (images[i] != null ? images[i].GetAnnotation(showowner) : null);
					if (note != null && note.Length > 0)
					{
						XmlElement imagenotesnode = doc.CreateElement("imagenotes");
						imagenotesnode.InnerText = note;
						slidenode.AppendChild(imagenotesnode);
					}

					XmlElement catalogdatanode = doc.CreateElement("catalogdata");
					XmlElement fieldsnode = doc.CreateElement("fields");
					Collection coll = Collection.GetByID(slides[i].ImageID.CollectionID);
					if (coll != null)
					{
						XmlElement fieldnode = doc.CreateElement("field");
						fieldnode.SetAttribute("name", "Collection");
						fieldnode.InnerText = coll.Title;
						fieldsnode.AppendChild(fieldnode);
						if (images[i] != null) 
						{
							foreach (Field f in coll.GetFields())
							{
								foreach (string s in images[i].GetDisplayValues(f))
								{
									XmlElement fe = doc.CreateElement("field");
									fe.SetAttribute("name", f.Label);
									fe.InnerText = s;
									fieldsnode.AppendChild(fe);
								}
							}
						}
					} 
					else 
					{
						XmlElement collfieldnode = doc.CreateElement("field");
						collfieldnode.SetAttribute("name", "Collection");
						collfieldnode.InnerText = "n/a";
						fieldsnode.AppendChild(collfieldnode);
					}
					if (images[i] == null) 
					{
						XmlElement titlefieldnode = doc.CreateElement("field");
						titlefieldnode.SetAttribute("name", "Title");
						titlefieldnode.InnerText = "n/a";
						fieldsnode.AppendChild(titlefieldnode);
					}
					catalogdatanode.AppendChild(fieldsnode);
					slidenode.AppendChild(catalogdatanode);

					slidesnode.AppendChild(slidenode);
				}
			}
			XmlAttribute slidecount = doc.CreateAttribute("slidecount");
			slidecount.Value = numslides.ToString();
			slidesnode.SetAttributeNode(slidecount);
			doc.DocumentElement.AppendChild(slidesnode);
			return doc;
		}

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{    
			this.CreatePackageButton.Click += new System.EventHandler(this.CreatePackageButton_Click);
			this.Load += new System.EventHandler(this.Page_Load);

		}
		#endregion

		private void CreatePackageButton_Click(object sender, System.EventArgs e)
		{
			if (PackageRadioButton.Checked)
			{
				string os = OSDropDownList.SelectedValue;
				bool imgview = ImageViewerCheckBox.Checked;
				bool ssview = FlashViewerCheckBox.Checked;
				ImageSize imagesize = (ImageSizeDropDownList.SelectedValue == "M" ?
					ImageSize.Medium : ImageSize.Full);
				CreatePackage(os, imgview, ssview, imagesize);
			}
			else if (PortableRadioButton.Checked)
			{
				string device = TargetDeviceDropDownList.SelectedValue;
				bool dataslides = DataSlidesCheckBox.Checked;
				Regex regex = new Regex(@"^(\d+)x(\d+)$");
				Match match = regex.Match(device);
				if (match.Success)
				{
					int width = Int32.Parse(match.Groups[1].Value);
					int height = Int32.Parse(match.Groups[2].Value);
					CreatePortablePackage(width, height, dataslides);
				}
				else
					ShowError("Could not extract target width and height for device");
			}
		}
	}
}
