using System;
using System.Xml;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;
using System.Windows.Xps;
using System.Windows.Xps.Packaging;
using Microsoft.Win32;
using System.IO;
using System.IO.Packaging;
using System.Printing;
using System.Diagnostics;

namespace OPlanner {

	public partial class MainWindow : Window {

		public MainWindow() {
			InitializeComponent();
			Init();
			if (App.CommandLineArgs.Length > 0) {
				OpenFile(App.CommandLineArgs[0]);
			}
		}

		private void Init() {
			info = new OCurrentInfo();
			info.DataChanging += new SenderStringEventHandler(DataChangingHandler);
			info.MapUpdated += new SenderEventHandler(MapUpdatedHandler);
			info.DataLoaded += new BasicEventHandler(DataLoadedHandler);
			info.ObjectsSelected += new SenderEventHandler(ObjectsSelectedHandler);
			info.ObjectsDeselected += new SenderEventHandler(ObjectsSelectedHandler);
			toolsPanel.SetInfo(info);
			objectsPanel.SetInfo(info);
			objectPropertiesPanel.SetInfo(info);
			coursesPanel.SetInfo(info);
			mainCanvas.SetInfo(info);
			mainCanvas.ScrollContainer = mainView;
			mainCanvas.ParentWindow = this;
			currentFile = "";
			fileOpen = false;
			needsSaving = false;
			undoRedo = new UndoRedo();
			descriptionManager = new DescriptionManager();
			descriptionManager.Load("DescriptionSymbols.xml");
			objectPropertiesPanel.LoadDescriptions(descriptionManager);
			info.DescriptionManager = descriptionManager;
			UpdateUI();
			descriptionsPrinter = new PrintDialog();
			coursesPrinter = new PrintDialog();
			CreateCommandBindings();
		}

		void CreateCommandBindings() {
			CommandBinding command;
			command = new CommandBinding(ApplicationCommands.Copy, new ExecutedRoutedEventHandler(CopyHandler), new CanExecuteRoutedEventHandler(CanCopyHandler));
			CommandBindings.Add(command);
			command = new CommandBinding(ApplicationCommands.Paste, new ExecutedRoutedEventHandler(PasteHandler), new CanExecuteRoutedEventHandler(CanPasteHandler));
			CommandBindings.Add(command);
			command = new CommandBinding(ApplicationCommands.Undo, new ExecutedRoutedEventHandler(UndoHandler), new CanExecuteRoutedEventHandler(CanUndoHandler));
			CommandBindings.Add(command);
			command = new CommandBinding(ApplicationCommands.Redo, new ExecutedRoutedEventHandler(RedoHandler), new CanExecuteRoutedEventHandler(CanRedoHandler));
			CommandBindings.Add(command);
		}

		void CanCopyHandler(object sender, CanExecuteRoutedEventArgs e) {
			if (info.SelectedObjects.Count > 0) {
				e.CanExecute = true;
			} else {
				e.CanExecute = false;
			}
		}

		void CopyHandler(object sender, ExecutedRoutedEventArgs e) {
			info.CopiedObjects.Clear();
			for (int i = 0; i < info.SelectedObjects.Count; i++) {
				OBaseObject obj = info.SelectedObjects[i];
				if (obj is IObjectRef) {
					obj = ((IObjectRef)obj).TargetObject;
				}
				Course course = obj.GetCourse();
				if (course == null) {
					info.CopiedObjects.Add(new CopyPasteObject(CopyParentType.OPlannerData, obj));
				} else {
					if (course.OrderedObjects.Contains(obj)) {
						info.CopiedObjects.Add(new CopyPasteObject(CopyParentType.CourseOrdered, obj));
					} else if (course.FixedObjects.Contains(obj)) {
						info.CopiedObjects.Add(new CopyPasteObject(CopyParentType.CourseFixed, obj));
					}
				}
			}
		}

		void CanPasteHandler(object sender, CanExecuteRoutedEventArgs e) {
			if (info.CopiedObjects.Count > 0) {
				e.CanExecute = true;
			} else {
				e.CanExecute = false;
			}
		}

		void PasteHandler(object sender, ExecutedRoutedEventArgs e) {
			if (info.CopiedObjects.Count == 0) {
				return;
			}
			info.InvokeDataChanging(this, "Paste Objects");
			info.SelectedObjects.Clear();
			for (int i = 0; i < info.CopiedObjects.Count; i++) {
				OBaseObject obj = null;
				if (info.CopiedObjects[i].CopyParentType == CopyParentType.OPlannerData) {
					obj = info.CopiedObjects[i].CopiedObject.Clone(info.Data);
					obj.ObjectCode = obj.ObjectCodePrefix + Convert.ToString(info.Data.GetNextCodeNum(obj.GetType()));
					info.Data.AddObject(obj);
				} else if (info.CopiedObjects[i].CopyParentType == CopyParentType.CourseOrdered && info.SelectedCourse != null) {
					obj = info.CopiedObjects[i].CopiedObject.Clone(info.SelectedCourse);
					obj.ObjectCode = obj.ObjectCodePrefix + Convert.ToString(info.SelectedCourse.GetNextCodeNum(obj.GetType()));
					info.SelectedCourse.AddOrderedObject(obj);
				} else if (info.CopiedObjects[i].CopyParentType == CopyParentType.CourseFixed && info.SelectedCourse != null) {
					obj = info.CopiedObjects[i].CopiedObject.Clone(info.SelectedCourse);
					obj.ObjectCode = obj.ObjectCodePrefix + Convert.ToString(info.SelectedCourse.GetNextCodeNum(obj.GetType()));
					info.SelectedCourse.AddFixedObject(obj);
				}
				if (obj != null) {
					info.SelectedObjects.Add(obj);
				}
			}
			info.InvokeDataChanged(this, "Paste Objects");
			info.InvokeObjectsCreated(this);
			info.InvokeObjectsSelected(this);
		}

		void CanUndoHandler(object sender, CanExecuteRoutedEventArgs e) {
			e.CanExecute = undoRedo.CanUndo;
		}

		void UndoHandler(object sender, ExecutedRoutedEventArgs e) {
			info.LoadInfo(undoRedo.Undo(info));
			needsSaving = true;
			UpdateUndoRedoItems();
			UpdateRemoveMapItem();
		}

		void CanRedoHandler(object sender, CanExecuteRoutedEventArgs e) {
			e.CanExecute = undoRedo.CanRedo;
		}

		void RedoHandler(object sender, ExecutedRoutedEventArgs e) {
			info.LoadInfo(undoRedo.Redo(info));
			needsSaving = true;
			UpdateUndoRedoItems();
			UpdateRemoveMapItem();
		}

		void ObjectsSelectedHandler(object sender) {
			UpdateDeselectItem();
			UpdateDeleteItem();
		}

		void DataLoadedHandler() {
			if (info.Data == null) {
				return;
			}
			if (info.Data.CoursesPageMediaSize != null) {
				coursesPrinter.PrintTicket.PageMediaSize = new PageMediaSize(info.Data.CoursesPageMediaSize.Value);
			}
			if (info.Data.CoursesPageOrientation != null) {
				coursesPrinter.PrintTicket.PageOrientation = info.Data.CoursesPageOrientation;
			}
			if (info.Data.DescriptionsPageMediaSize != null) {
				descriptionsPrinter.PrintTicket.PageMediaSize = new PageMediaSize(info.Data.DescriptionsPageMediaSize.Value);
			}
			if (info.Data.DescriptionsPageOrientation != null) {
				descriptionsPrinter.PrintTicket.PageOrientation = info.Data.DescriptionsPageOrientation;
			}
		}

		void MapUpdatedHandler(object sender) {
			UpdateRemoveMapItem();
		}

		void DataChangingHandler(object sender, string changeInfo) {
			undoRedo.SaveData(info, changeInfo);
			UpdateUndoRedoItems();
			needsSaving = true;
			UpdateSaveItems();
		}

		private OCurrentInfo info;
		private string currentFile;
		private bool fileOpen;
		private bool needsSaving;
		private UndoRedo undoRedo;
		private DescriptionManager descriptionManager;
		private PrintDialog descriptionsPrinter;
		private PrintDialog coursesPrinter;

		private void loadMapItem_Click(object sender, RoutedEventArgs e) {
			OpenFileDialog dialog = new OpenFileDialog();
			dialog.FileName = "";
			dialog.Filter = "Supported Files|*.jpg;*.jpeg;*.png;*.gif;*.tif;*.tiff;*.xaml;*.xps;*.ocd";
			Nullable<bool> result = dialog.ShowDialog();
			if (result == true) {
				CustomResult customResult = mainCanvas.LoadMapFile(dialog.FileName);
				if (customResult.Success == true) {
					info.InvokeDataChanging(this, "Load Map");
					info.Data.MapFileAbsolute = dialog.FileName;
					info.InvokeDataChanged(this, "Load Map");
					info.InvokeMapUpdated(this);
				} else {
					ErrorManager errorManager = new ErrorManager();
					errorManager.AddError(customResult.Message);
					errorManager.ShowErrors(this);
				}
			}
		}

		private void removeMapItem_Click(object sender, RoutedEventArgs e) {
			info.InvokeDataChanging(this, "Remove Map");
			info.Data.MapFileAbsolute = "";
			if (info.Data.CanvasSizeMode == AutoManual.Auto) {
				info.Data.CanvasSize = mainCanvas.GetMapSize();
			}
			info.InvokeDataChanged(this, "Remove Map");
			info.InvokeMapUpdated(this);
		}

		private void openFileItem_Click(object sender, RoutedEventArgs e) {
			if (PromptSave() == false) {
				return;
			}
			OpenFileDialog dialog = new OpenFileDialog();
			dialog.FileName = "";
			dialog.DefaultExt = ".oplan";
			dialog.Filter = "OPlanner Files|*.oplan;*.xml";
			Nullable<bool> result = dialog.ShowDialog();
			if (result == true) {
				string filename = dialog.FileName;
				OpenFile(filename);
			}
		}

		private void OpenFile(string fileName) {
			CloseFile();
			fileOpen = true;
			OPlannerData data = new OPlannerData();
			data.LoadFromFile(fileName);
			info.LoadData(data);
			currentFile = fileName;
			Title = "OPlanner - " + currentFile;
			mainCanvas.Visibility = Visibility.Visible;
			needsSaving = false;
			UpdateUI();
		}

		void UpdateDeselectItem() {
			if (!fileOpen) {
				deselectItem.IsEnabled = false;
				return;
			}
			if (info.SelectedObjects.Count > 0) {
				deselectItem.IsEnabled = true;
			} else {
				deselectItem.IsEnabled = false;
			}
		}

		void UpdateDeleteItem() {
			if (!fileOpen) {
				deleteItem.IsEnabled = false;
				return;
			}
			if (info.SelectedObjects.Count > 0) {
				deleteItem.IsEnabled = true;
			} else {
				deleteItem.IsEnabled = false;
			}
		}

		private void UpdateRemoveMapItem() {
			bool enable = fileOpen;
			if (enable) {
				if (info.Data.MapFileAbsolute != "" && info.Data.MapFileAbsolute != null) {
					removeMapItem.IsEnabled = true;
					return;
				}
			}
			removeMapItem.IsEnabled = false;
		}

		private void UpdateUI() {
			bool enable = fileOpen;
			loadMapItem.IsEnabled = enable;
			UpdateRemoveMapItem();
			UpdateSaveItems();
			UpdateDeselectItem();
			UpdateDeleteItem();
			closeFileItem.IsEnabled = enable;
			exportPrintCoursesItem.IsEnabled = enable;
			canvasSizeItem.IsEnabled = enable;
			mapSettingsItem.IsEnabled = enable;
			mapInfoItem.IsEnabled = enable;
			renumberControlsItem.IsEnabled = enable;
			scaleCoordinatesItem.IsEnabled = enable;
			exportPrintDescriptionsItem.IsEnabled = enable;
			UpdateUndoRedoItems();
			zoomSlider.Value = 0;
			zoomText.Text = "100";
			mainCanvas.LayoutTransform = null;
			objectsPanel.IsEnabled = enable;
			coursesPanel.IsEnabled = enable;
			objectPropertiesPanel.IsEnabled = enable;
			toolsPanel.IsEnabled = enable;
			zoomSlider.IsEnabled = enable;
			zoomText.IsEnabled = enable;
			fullSizeButton.IsEnabled = enable;
			saveAsFileItem.IsEnabled = enable;
			exportCourseDataItem.IsEnabled = enable;
			selectUnusedControlsItem.IsEnabled = enable;
		}

		private void UpdateSaveItems() {
			bool enable = fileOpen;
			if (enable && needsSaving) {
				saveFileItem.IsEnabled = true;
				saveButton.IsEnabled = true;
			} else {
				saveFileItem.IsEnabled = false;
				saveButton.IsEnabled = false;
			}
		}

		public void CloseFile() {
			info.LoadData(null);
			currentFile = "";
			Title = "OPlanner";
			mainCanvas.Visibility = Visibility.Collapsed;
			fileOpen = false;
			needsSaving = false;
			undoRedo.Clear();
			UpdateUI();
		}

		public bool CloseFile(bool promptSave) {
			if (fileOpen == false) {
				return true;
			}
			if (promptSave == true) {
				if (PromptSave() == false) {
					return false;
				}
			}
			CloseFile();
			return true;
		}

		private void closeFileItem_Click(object sender, RoutedEventArgs e) {
			CloseFile(true);
		}

		public void NewFile() {
			if (CloseFile(true) == false) {
				return;
			}
			OPlannerData data = new OPlannerData();
			info.LoadData(data);
			mainCanvas.Visibility = Visibility.Visible;
			fileOpen = true;
			Title = "OPlanner - New File";
			needsSaving = false;
			UpdateUI();
		}

		private void newFileItem_Click(object sender, RoutedEventArgs e) {
			NewFile();
		}

		public void SaveFile(string fileName) {
			info.Data.WriteToFile(fileName);
			currentFile = fileName;
			Title = "OPlanner - " + currentFile;
			needsSaving = false;
			UpdateSaveItems();
		}

		private void SaveFile(bool saveAs) {
			if (fileOpen == false) {
				return;
			}
			if (currentFile != "" && saveAs == false) {
				SaveFile(currentFile);
				return;
			}
			SaveFileDialog dialog = new SaveFileDialog();
			if (currentFile == "") {
				dialog.FileName = "Untitled.oplan";
			} else {
				dialog.FileName = currentFile;
			}
			dialog.DefaultExt = ".oplan";
			dialog.Filter = "OPlanner Files|*.oplan";
			Nullable<bool> result = dialog.ShowDialog();
			if (result == true) {
				string filename = dialog.FileName;
				SaveFile(filename);
			}
		}

		private void saveFileItem_Click(object sender, RoutedEventArgs e) {
			SaveFile(false);
		}

		private void saveAsFileItem_Click(object sender, RoutedEventArgs e) {
			SaveFile(true);
		}

		private bool PromptSave() {
			if (fileOpen == false || needsSaving == false) {
				return true;
			}
			MessageBoxResult result;
			result = MessageBox.Show(this, "Save Changes?", "Close File", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
			if (result == MessageBoxResult.Cancel) {
				return false;
			}
			if (result == MessageBoxResult.Yes) {
				SaveFile(false);
			}
			return true;
		}

		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
			if (PromptSave() == false) {
				e.Cancel = true;
			}
		}

		private void exitItem_Click(object sender, RoutedEventArgs e) {
			Close();
		}

		private void mainView_KeyDown(object sender, KeyEventArgs e) {
			if (!fileOpen) {
				return;
			}
			mainCanvas.HandleKeyDownEvent(sender, e);
		}

		private void UpdateUndoRedoItems() {
			undoItem.IsEnabled = undoRedo.CanUndo;
			if (undoRedo.CanUndo) {
				undoItem.Header = "Undo " + undoRedo.UndoText;
			} else {
				undoItem.Header = "Undo";
			}
			redoItem.IsEnabled = undoRedo.CanRedo;
			if (undoRedo.CanRedo) {
				redoItem.Header = "Redo " + undoRedo.RedoText;
			} else {
				redoItem.Header = "Redo";
			}
		}

		private void exportPrintCoursesItem_Click(object sender, RoutedEventArgs e) {
			ExportPrintCourses wnd = new ExportPrintCourses();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.SetPrintDialog(coursesPrinter);
			wnd.ShowDialog();
		}

		private void zoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
			if (!fileOpen) {
				return;
			}
			Point pos = new Point();
			pos.X = mainView.HorizontalOffset + mainView.ViewportWidth / 2;
			pos.Y = mainView.VerticalOffset + mainView.ViewportHeight / 2;
			double oldScale = Math.Exp(e.OldValue);
			ScaleTransform oldTransform = new ScaleTransform(oldScale, oldScale, 0, 0);
			GeneralTransform inverse;
			inverse = oldTransform.Inverse;
			double scale = Math.Exp(e.NewValue);
			ScaleTransform transform = new ScaleTransform(scale, scale, 0, 0);
			Point scaledPos = pos;
			if (inverse != null) {
				scaledPos = inverse.Transform(scaledPos);
			}
			scaledPos = transform.Transform(scaledPos);
			mainCanvas.LayoutTransform = transform;
			mainView.ScrollToHorizontalOffset(scaledPos.X - mainView.ViewportWidth / 2);
			mainView.ScrollToVerticalOffset(scaledPos.Y - mainView.ViewportHeight / 2);
			zoomText.Text = Math.Round(scale * 100, 1).ToString();
			info.ZoomLevel = scale;
		}

		private void zoomSlider_ThumbDoubleClick(object sender, MouseButtonEventArgs e) {
			zoomSlider.Value = 0;
		}

		private void canvasSizeItem_Click(object sender, RoutedEventArgs e) {
			CanvasSizeProperties wnd = new CanvasSizeProperties();
			wnd.Owner = this;
			wnd.DefaultSize = new Size(mainCanvas.CanvasWidth, mainCanvas.CanvasHeight);
			wnd.SetInfo(info);
			wnd.ShowDialog();
		}

		private void exportPrintDescriptions_Click(object sender, RoutedEventArgs e) {
			ExportPrintDescriptions wnd = new ExportPrintDescriptions();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.SetPrintDialog(descriptionsPrinter);
			wnd.SetDescriptionManager(descriptionManager);
			wnd.ShowDialog();
		}

		private void mapSettingsItem_Click(object sender, RoutedEventArgs e) {
			MapSettings wnd = new MapSettings();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.ShowDialog();
		}

		private void mapInfoItem_Click(object sender, RoutedEventArgs e) {
			MapInfoWindow wnd = new MapInfoWindow();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.ShowDialog();
		}

		private void renumberControlsItem_Click(object sender, RoutedEventArgs e) {
			RenumberControls wnd = new RenumberControls();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.ShowDialog();
		}

		private void zoomText_LostFocus(object sender, RoutedEventArgs e) {
			double oldScale = Math.Exp(zoomSlider.Value);
			double newValue;
			if(!double.TryParse(zoomText.Text,out newValue)){
				zoomText.Text = Math.Round(oldScale * 100, 1).ToString();
			}
			if (newValue > Math.Exp(zoomSlider.Maximum) * 100) {
				newValue = Math.Exp(zoomSlider.Maximum) * 100;
			}
			if (newValue < Math.Exp(zoomSlider.Minimum) * 100) {
				newValue = Math.Exp(zoomSlider.Minimum) * 100;
			}
			double scale = newValue/100;
			zoomSlider.Value = Math.Log(scale);
		}

		private void fullSizeButton_Click(object sender, RoutedEventArgs e) {
			zoomSlider.Value = 0;
		}

		private void exportCourseDataItem_Click(object sender, RoutedEventArgs e) {
			if (fileOpen == false) {
				return;
			}
			SaveFileDialog dialog = new SaveFileDialog();
			dialog.FileName = "Courses.xml";
			dialog.DefaultExt = ".xml";
			dialog.Filter = "Xml Files|*.xml";
			Nullable<bool> result = dialog.ShowDialog();
			if (result == true) {
				string fileName = dialog.FileName;
				ExportCourses(fileName);
			}
		}

		private void ExportCourses(string fileName) {
			XmlDocument doc = ExportCoursesHelper.GetCoursesXml(info.Data, new Size(mainCanvas.CanvasWidth, mainCanvas.CanvasHeight));
			try {
				doc.Save(fileName);
			} catch (Exception) {
			}
		}

		private void scaleCoordinatesItem_Click(object sender, RoutedEventArgs e) {
			ScaleCoordinates wnd = new ScaleCoordinates();
			wnd.Owner = this;
			wnd.SetInfo(info);
			wnd.ShowDialog();
		}

		private void mainView_PreviewKeyDown(object sender, KeyEventArgs e) {
			if (!fileOpen) {
				return;
			}
			mainCanvas.HandlePreviewKeyDownEvent(sender, e);
		}

		private void mainView_PreviewKeyUp(object sender, KeyEventArgs e) {
			if (!fileOpen) {
				return;
			}
			mainCanvas.HandlePreviewKeyUpEvent(sender, e);
		}

		private void deselectItem_Click(object sender, RoutedEventArgs e) {
			if (info.SelectedObjects.Count == 0) {
				return;
			}
			info.SelectedObjects.Clear();
			info.InvokeObjectsDeselected(this);
		}

		private void selectUnusedControlsItem_Click(object sender, RoutedEventArgs e) {
			if (!fileOpen) {
				return;
			}
			info.SelectedObjects.Clear();
			info.SelectedObjects.AddRange(info.Data.GetUnusedControls());
			info.InvokeObjectsSelected(this);
		}

		private void onlineHelpItem_Click(object sender, RoutedEventArgs e) {
			try {
				Process.Start(OPlannerAbout.OnlineHelpUrl);
			} catch (Exception) {
				ErrorManager errorManager = new ErrorManager();
				errorManager.AddError("Cannot connect to oplanner.org.uk");
				errorManager.ShowErrors(this);
			}
		}

		private void aboutItem_Click(object sender, RoutedEventArgs e) {
			About wnd = new About();
			wnd.Owner = this;
			wnd.ShowDialog();
		}

		private void updateCheckItem_Click(object sender, RoutedEventArgs e) {
			UpdateInfo wnd = new UpdateInfo();
			wnd.Owner = this;
			wnd.ShowDialog();
		}

		private void deleteItem_Click(object sender, RoutedEventArgs e) {
			if (!fileOpen) {
				return;
			}
			mainCanvas.DeleteSelectedObjects();
		}

	}

}