using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Services.Client;
using System.Windows.Browser;

using NorthwindBackend.NorthwindServiceReference;

namespace NorthwindBackend
{
	/// <summary>
	/// The data related logic of the application is located here. This class is 
	/// responsible for loading/updating/deleting products.
	/// </summary>
	public class NorthwindDataSource : INotifyPropertyChanged
	{
		private Product selectedProduct;

		/// <summary>
		/// Initializes a new instance of the NorthwindDataSource class.
		/// </summary>
		public NorthwindDataSource()
		{
			Uri appUri = HtmlPage.Document.DocumentUri;

			// The web service is located at the same folder of the page, containing this application.
			this.Service = new Northwind_Entities(new Uri(appUri.AbsoluteUri.Replace("Default.aspx", "NorthwindBackEnd.svc"), UriKind.Absolute));

			this.BeginLoadCategories();
			this.BeginLoadSuppliers();
		}

		/// <summary>
		/// A reference to the Northwind_Entities web service
		/// </summary>
		private Northwind_Entities Service { get; set; }

		/// <summary>
		/// All categories in the database. This property is loaded asynchronously
		/// </summary>
		public ObservableCollection<Category> Categories { get; private set; }

		/// <summary>
		/// All suppliers in the database. This property is loaded asynchronously
		/// </summary>
		public ObservableCollection<Supplier> Suppliers { get; private set; }

		/// <summary>
		/// The currently selected product in the treeview
		/// </summary>
		public Product SelectedProduct
		{
			get
			{
				return this.selectedProduct;
			}
			set
			{
				if (this.selectedProduct != value)
				{
					this.selectedProduct = value;
					this.OnPropertyChanged("SelectedProduct");
				}
			}
		}

		/// <summary>
		/// Deletes the selected product
		/// </summary>
		public void DeleteSelectedProduct()
		{
			Service.DeleteObject(this.SelectedProduct);
			Service.BeginSaveChanges((IAsyncResult result) =>
			{
				SelectedProduct.Category.Products.Remove(SelectedProduct);
				SelectedProduct = null;
			}, null);
		}

		/// <summary>
		/// Saves the changes of the selected product in the database
		/// </summary>
		public void UpdateSelectedProduct()
		{
			if (this.SelectedProduct != null)
			{
				Service.UpdateObject(this.SelectedProduct);
				Service.SetLink(this.SelectedProduct, "Supplier", this.SelectedProduct.Supplier);
				Service.BeginSaveChanges(null, null);
			}
		}

		/// <summary>
		/// Initiates the loading of all products from the given category
		/// </summary>
		/// <param name="category"></param>
		public void BeginLoadingProducts(Category category)
		{
			DataServiceQuery<Product> categoryProducts = this.Service
				.CreateQuery<Product>(string.Format("CategorySet({0})/Products", category.CategoryID))
				.Expand("Supplier")
				.Expand("Category");

			categoryProducts.BeginExecute(
				(IAsyncResult result) => EntitiesLoaded<Product>(result, category.Products),
				categoryProducts);
		}

		/// <summary>
		/// Initiates loading of all categories.
		/// </summary>
		private void BeginLoadCategories()
		{
			this.Categories = new ObservableCollection<Category>();

			Service.CategorySet.BeginExecute(
				(IAsyncResult result) => EntitiesLoaded<Category>(result, this.Categories),
				Service.CategorySet);
		}

		/// <summary>
		/// Initiates loading of all suppliers.
		/// </summary>
		private void BeginLoadSuppliers()
		{
			this.Suppliers = new ObservableCollection<Supplier>();

			Service.SupplierSet.BeginExecute(
				(IAsyncResult result) => EntitiesLoaded<Supplier>(result, this.Suppliers),
				Service.SupplierSet);
		}

		/// <summary>
		/// Generic method that is used to add the items, received from an 
		/// async call to the web service to a collection.
		/// </summary>
		private static void EntitiesLoaded<T>(IAsyncResult result, Collection<T> entities)
		{
			DataServiceQuery<T> query = result.AsyncState as DataServiceQuery<T>;

			IEnumerable<T> categories = query.EndExecute(result);
			foreach (T e in categories)
			{
				entities.Add(e);
			}
		}

		#region INotifyPropertyChanged
		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged(string propertyName)
		{
			if (this.PropertyChanged != null)
			{
				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}
		#endregion
	}
}