using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Web.Caching;
using System.Xml;
using System.Runtime.Remoting.Messaging;
using System.IO;
using System.Security.Principal;
using System.Text.RegularExpressions;
using Orciid.Core;

namespace Orciid
{
	public class import : PageTemplate.OrciidPage
	{
		protected System.Web.UI.WebControls.Label CollectionTitleLabel;
		protected System.Web.UI.WebControls.HyperLink PropertiesHyperLink;
		protected System.Web.UI.WebControls.Panel SelectFilePanel;
		protected System.Web.UI.WebControls.CheckBox ValidateOnlyCheckBox;
		protected System.Web.UI.WebControls.CheckBox ReplaceRecordsCheckBox;
		protected System.Web.UI.WebControls.Button ImportDataButton;
		protected System.Web.UI.HtmlControls.HtmlInputFile FileUpload;
		protected System.Web.UI.WebControls.Panel StatusPanel;
		protected System.Web.UI.WebControls.Label StatusLabel;
		protected System.Web.UI.WebControls.Button ClearStatusButton;
		protected System.Web.UI.WebControls.Button ShowSchemaButton;
		protected System.Web.UI.WebControls.Button AbortButton;
		protected System.Web.UI.HtmlControls.HtmlGenericControl Heading;
		protected System.Web.UI.HtmlControls.HtmlGenericControl StatusHeading;
		protected System.Web.UI.HtmlControls.HtmlGenericControl ImportHeading;
		protected System.Web.UI.HtmlControls.HtmlGenericControl FileLabelSpan;
		protected System.Web.UI.HtmlControls.HtmlGenericControl SchemaHeading;
		protected System.Web.UI.WebControls.Button ReadFileButton;
		protected System.Web.UI.WebControls.Panel ImportOptionsPanel;
		protected System.Web.UI.HtmlControls.HtmlGenericControl ImportOptionsHeading;
		protected System.Web.UI.WebControls.DropDownList IDFieldDropDownList;
		protected System.Web.UI.WebControls.DropDownList ResourceFieldDropDownList;
		protected Orciid.UserControls.FieldMappingControl FieldMappings;
		private string ImportFilename;
		protected System.Web.UI.WebControls.DropDownList DateFormatDropDownList;
		protected System.Web.UI.WebControls.DropDownList SplitModeDropDownList;
		protected System.Web.UI.WebControls.TextBox SplitAtTextBox;
		private Collection coll;

		private void Page_Load(object sender, System.EventArgs e)
		{
			if (!IsPostBack)
			{
				try
				{
					coll = Collection.GetByID(Int32.Parse(Request.QueryString["id"]));
				}
				catch
				{
					coll = null;
				}
				CheckAuthorization();
				PropertiesHyperLink.NavigateUrl = String.Format("collection.aspx?id={0}", coll.ID);
				CollectionTitleLabel.Text = coll.Title;
			}
		}

		public override string GetHighlightedMenuItem()
		{
			return "Management_Collections";
		}

		protected override bool CheckUserPrivileges(Orciid.Core.User user)
		{
			return true;
		}
 
		protected override void LoadViewState(object savedState) 
		{
			if (savedState != null)
			{
				base.LoadViewState(savedState);
				coll = null;
				if (ViewState["cid"] != null)
					coll = Collection.GetByID((int)ViewState["cid"]);
				ImportFilename = ViewState["ifn"] as String;
				CheckAuthorization();
			}
		}

		protected override object SaveViewState()
		{
			if (coll != null)
				ViewState["cid"] = coll.ID;
			ViewState["ifn"] = ImportFilename;
			return base.SaveViewState();
		}

		private void CheckAuthorization()
		{
			Privilege[] privs = new Privilege[] { Privilege.ModifyImages };
			bool authorized = false;
			if (coll != null)
			{
				foreach (Privilege p in privs)
					if (Orciid.Core.User.HasPrivilege(p, coll))
					{
						authorized = true;
						break;
					}
			}
			if (!authorized)
				Response.Redirect("insufficientprivileges.aspx"); 
		}

		#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.ClearStatusButton.Click += new System.EventHandler(this.ClearStatusButton_Click);
			this.AbortButton.Click += new System.EventHandler(this.AbortButton_Click);
			this.ReadFileButton.Click += new System.EventHandler(this.ReadFileButton_Click);
			this.ImportDataButton.Click += new System.EventHandler(this.ImportDataButton_Click);
			this.ShowSchemaButton.Click += new System.EventHandler(this.ShowSchemaButton_Click);
			this.Load += new System.EventHandler(this.Page_Load);
			this.PreRender += new System.EventHandler(this.import_PreRender);

		}
		#endregion

		private void ReadFileButton_Click(object sender, System.EventArgs e)
		{
			if (FileUpload.PostedFile != null &&
				FileUpload.PostedFile.InputStream != null &&
				FileUpload.PostedFile.InputStream.Length > 0)
			{
				string ext = Path.GetExtension(FileUpload.PostedFile.FileName).ToLower();
				ImportFilename = Path.Combine(Path.GetTempPath(), 
					GetImportTaskKey() + ext);
				// store file in temporary directory
				using (FileStream o = File.Create(ImportFilename))
					Orciid.Media.Util.Tools.CopyStream(
						FileUpload.PostedFile.InputStream, o);
				
				RegisterTempSessionFile(ImportFilename);

				ImportFileReader.FileType filetype = 
					ImportFileReader.GetFileTypeByExtension(ImportFilename);
				
				if (filetype == ImportFileReader.FileType.UNKNOWN)
				{
					ImportOptionsPanel.Visible = false;
					ShowWarning(GetLocalString(this, "UnknownDataFileUploaded", 
						"Please upload an XML, CSV, or TSV file with the appropriate file extension"));
					File.Delete(ImportFilename);
					return;
				}

				// set import option defaults				
				ImportFileReader reader = new ImportFileReader(ImportFilename, filetype);

				FieldMapping mapping = reader.GetMapping(coll);

				if (mapping.SourceFields.Length == 0 || reader.GetData().Rows.Count == 0)
				{
					ImportOptionsPanel.Visible = false;
					ShowWarning(GetLocalString(this, "EmptyDataFileUploaded", 
						"Please select a valid file that contains at least one record"));
					File.Delete(ImportFilename);
					return;
				}

				ImportOptionsPanel.Visible = true;

				IDFieldDropDownList.Items.Clear();
				ResourceFieldDropDownList.Items.Clear();
				string preselectid = mapping.GetSetting("idfield");
				string preselectresource = mapping.GetSetting("resourcefield");
				foreach (string f in mapping.SourceFields)
				{
					IDFieldDropDownList.Items.Add(f);
					if (f == preselectid)
						IDFieldDropDownList.SelectedValue = f;
					ResourceFieldDropDownList.Items.Add(f);
					if (f == preselectresource)
						ResourceFieldDropDownList.SelectedValue = f;
				}				

				FieldMappings.FieldMapping = mapping;
				FieldMappings.DataBind();

				string preselectdateformat = mapping.GetSetting("dateformat");
				DateFormatDropDownList.Items.Clear();
				DateFormatDropDownList.Items.Add(new ListItem("Default", "default"));
				foreach (DateInterpreter.Format format in DateInterpreter.Formats)
					DateFormatDropDownList.Items.Add(
						new ListItem(format.ToString(), ((int)format).ToString()));
				try
				{
					DateFormatDropDownList.SelectedValue = preselectdateformat;
				}
				catch
				{
				}

				SplitModeDropDownList.Items.Clear();
				SplitModeDropDownList.Items.Add(new ListItem(GetLocalString(this, "StaticSplitMode", "text"), "static"));
				SplitModeDropDownList.Items.Add(new ListItem(GetLocalString(this, "RegexSplitMode", "regular expression"), "regex"));				
				string preselectsplitmode = mapping.GetSetting("spliton");
				if (preselectsplitmode == "regex")
					SplitModeDropDownList.SelectedValue = "regex";
				string preselectsplittext = mapping.GetSetting("splittext");
				if (preselectsplittext != null)
					SplitAtTextBox.Text = Server.UrlDecode(preselectsplittext);
			}
			else
			{
				ImportOptionsPanel.Visible = false;
				ShowWarning(GetLocalString(this, "NoDataFileUploaded", 
					"Please browse to a valid data file"));
			}
		}
		
		private void ImportDataButton_Click(object sender, System.EventArgs e)
		{			
			if (File.Exists(ImportFilename))
			{
				string cachekey = GetImportTaskKey();
				ImportFileReader reader = new ImportFileReader(
					ImportFilename, 
					ImportFileReader.GetFileTypeByExtension(ImportFilename));
				
				try
				{
					reader.SplitAt = SplitAtTextBox.Text;
					if (SplitModeDropDownList.SelectedValue == "regex")
						reader.SplitMode = ImportFileReader.ValueSplitMode.Regex;
					else if (SplitModeDropDownList.SelectedValue == "static")
						reader.SplitMode = ImportFileReader.ValueSplitMode.Static;
				}
				catch (ArgumentException)
				{
					ShowError(GetLocalString(this, "InvalidRegex",
						"The regular expression you entered is not valid"));
					return;
				}

				DateInterpreter.Format dateformat = DateInterpreter.Format.YearOnly;
				try
				{
					dateformat = (DateInterpreter.Format)Int32.Parse(
						DateFormatDropDownList.SelectedValue);
				}
				catch
				{
				}

				FieldMapping mapping = FieldMappings.FieldMapping;

				mapping.StoreSetting("idfield", IDFieldDropDownList.SelectedItem.Text);
				mapping.StoreSetting("resourcefield", ResourceFieldDropDownList.SelectedItem.Text);
				mapping.StoreSetting("dateformat", DateFormatDropDownList.SelectedValue);
				mapping.StoreSetting("spliton", SplitModeDropDownList.SelectedValue);
				mapping.StoreSetting("splittext", Server.UrlEncode(SplitAtTextBox.Text));

				mapping.Remember();

				if (mapping.GetMapping(IDFieldDropDownList.SelectedItem.Text) == null)
				{
					ShowError(GetLocalString(this, "IDFieldNotMapped",
						"You must map the identifier field to a target field in the collection"));
					return;
				}

				ImportTask task = new ImportTask(
					this, 
					coll, 
					reader.GetData(), 
					mapping, 
					IDFieldDropDownList.SelectedItem.Text, 
					ResourceFieldDropDownList.SelectedItem.Text,
					ReplaceRecordsCheckBox.Checked, 
					ValidateOnlyCheckBox.Checked, 
					dateformat,
					Cache, 
					cachekey);
				Cache.Insert(cachekey, task, null, DateTime.MaxValue, TimeSpan.FromMinutes(20));
				File.Delete(ImportFilename);
				ImportFilename = null;
			}
			else
			{
				ShowWarning(GetLocalString(this, "NoXmlFileSelected", 
					"Please select an XML file before clicking the Import Data button"));
			}
		}

		private string GetImportTaskKey()
		{
			Orciid.Core.User user = Orciid.Core.User.CurrentUser();
			return String.Format("IMPORT_TASK_{0}_{1}", user.ID, coll.ID);
		}

		private const string AutoRefreshJavaScript = 
			@"<script language='JavaScript'><!--
setInterval('window.location.href=\'import.aspx?id={0}\';',10000);
//--></script>";

		private void import_PreRender(object sender, System.EventArgs e)
		{
			ImportTask import = Cache[GetImportTaskKey()] as ImportTask;
			if (import != null && import.Done)
			{
				Cache.Remove(GetImportTaskKey());
				import = null;
			}
			if (import != null)
			{
				SelectFilePanel.Visible = false;
				StatusPanel.Visible = true;
				StatusLabel.Text = String.Format(GetLocalString(this, "ProcessingDataMessage",
                    "Processing data...\n\n{0}\n\nThis page will automatically refresh every 10 seconds"),
					import.GetStatusMessage());
				ClearStatusButton.Visible = false;
				AbortButton.Visible = true;
				if (!this.IsClientScriptBlockRegistered("autorefreshpage"))
					this.RegisterClientScriptBlock("autorefreshpage", String.Format(AutoRefreshJavaScript, coll.ID));
			}
			else
			{
				Orciid.Core.User user = Orciid.Core.User.CurrentUser();
				Properties props = Properties.GetProperties(user);
				string message = props.Get(String.Format("import{0}", coll.ID), null);
				if (message != null)
				{
					StatusPanel.Visible = true;
					StatusLabel.Text = message;
					ClearStatusButton.Visible = true;
					AbortButton.Visible = false;
				}
				else
					StatusPanel.Visible = false;
			}
		}

		private void ClearStatusButton_Click(object sender, System.EventArgs e)
		{
			Orciid.Core.User user = Orciid.Core.User.CurrentUser();
			Properties props = Properties.GetProperties(user);
			props.Remove(String.Format("import{0}", coll.ID));
		}

		private void ShowSchemaButton_Click(object sender, System.EventArgs e)
		{
			Regex regex = new Regex("[^-a-z0-9_ ]+", RegexOptions.IgnoreCase);
			string filename = regex.Replace(coll.Title, "") + ".xsd";
			Response.Clear();
			Response.Buffer = false;
			Response.ContentType = "application/xml";
			Response.AddHeader("Content-Disposition", 
				String.Format("attachment; filename=\"{0}\"", filename));
			DataExchange.GetXmlSchema(coll).Write(Response.OutputStream);
			Response.End();
		}

		private void AbortButton_Click(object sender, System.EventArgs e)
		{
			ImportTask import = Cache[GetImportTaskKey()] as ImportTask;
			if (import != null)
				import.Abort();
		}

		private class ImportTask
		{
			public Import.ImportStatus Status = new Orciid.Core.Import.ImportStatus();

			private string colltitle;
			private int collid;
			private string cachekey;
			private Cache cache;
			private bool abort = false;

			private string msg_lastimportfailed;
			private string msg_lastimportsucceeded;
			private string msg_tasknotcompleted;
			private string msg_statusmessage;

			private bool done = false;
			public bool Done
			{
				get
				{
					return done;
				}
			}

			public string GetStatusMessage()
			{
				return String.Format(msg_statusmessage,
					Status.Read, 
					Status.Added, 
					Status.Replaced, 
					Status.Skipped);
			}

			public void Abort()
			{
				abort = true;
				done = true;
			}

			private void OnImportRecord(object sender, EventArgs ea)
			{
				Import.ImportEventArgs e = (Import.ImportEventArgs)ea;
				Status = e.Status;
				e.Abort = abort;
				cache.Get(cachekey); // keep cache entry alive
			}

			private void OnImportCompletion(IAsyncResult iasyncresult)
			{
				// explicitely clear cache for this thread
				CachableObject.ResetThreadCache();
				done = true;
				Orciid.Core.User user = Orciid.Core.User.CurrentUser();
				bool success = true;
				Exception exception = null;
				try
				{
					Status = Import.GetAsyncResult(iasyncresult);
				}
				// catch all exceptions, since this is running in another thread
				// they will never be caught or processed anywhere else
				catch (Exception e)
				{
					exception = e;
					success = false;
				}
				string message;
				if (success)
				{
					message = msg_lastimportsucceeded + "\n" + GetStatusMessage();
					TransactionLog.Instance.Add("Data Import Succeeded", 
						String.Format("{0} ({1})", colltitle, collid));
				}
				else
				{
					message = msg_lastimportfailed + "\n" + exception.Message;
					TransactionLog.Instance.AddException("Data Import failed", 
						String.Format("{0} ({1})", colltitle, collid), exception);
				}
				Properties props = Properties.GetProperties(user);
				props.Set(String.Format("import{0}", collid), message);
			}

			public ImportTask(
				PageTemplate.OrciidPage opage, 
				Collection coll, 
				DataTable data, 
				FieldMapping mapping,
				string idfield,
				string resourcefield,
				bool replaceexisting, 
				bool checkonly,
				DateInterpreter.Format dateformat,
				Cache c, 
				string key)
			{
				msg_lastimportfailed = opage.GetLocalString(opage, "LastImportFailed", 
					"The last data import for this collection failed:");
				msg_lastimportsucceeded = opage.GetLocalString(opage, "LastImportSucceeded", 
					"The last data import for this collection successfully finished.");
				msg_tasknotcompleted = opage.GetLocalString(opage, "TaskNotCompleted", 
					"Import task has not been completed");
				msg_statusmessage = opage.GetLocalString(opage, "ImportStatusMessage", 
					"{0} records processed:\n" +
					"{1} records added\n" +
					"{2} records replaced\n" +
					"{3} records already existed and were skipped\n");

				cachekey = key;
				cache = c;
				colltitle = coll.Title;
				collid = coll.ID;
				Orciid.Core.User user = Orciid.Core.User.CurrentUser();
				Properties props = Properties.GetProperties(user);
				props.Set(String.Format("import{0}", collid), msg_tasknotcompleted); 
				TransactionLog.Instance.Add("Data Import Started", 
					String.Format("{0} ({1})", colltitle, collid));
				Import import = new Import(coll, data, mapping, idfield, resourcefield, 
					replaceexisting, checkonly, dateformat);
				import.OnImportRecord += new EventHandler(OnImportRecord);
				import.RunAsync(new AsyncCallback(OnImportCompletion));
			}
		}

	}
}
