﻿// Slide.Show, version 1.1
// Copyright © Vertigo Software, Inc.
// This source is subject to the Microsoft Public License (Ms-PL).
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx.
// All other rights reserved.

/// <reference path="SlideShow.js" />

/*******************************************
 * class: SlideShow.ThumbnailViewer
 *******************************************/
SlideShow.ThumbnailViewer = function(control, parent, options)
{
	/// <summary>Displays pages of thumbnails to choose from.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="parent">The parent control.</param>
	/// <param name="options">The options for the control.</param>
	
	var xaml =
		'<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ThumbnailViewer" Visibility="Collapsed">' +
		'	<Rectangle x:Name="viewerBackground" />' +
		'</Canvas>';
	
	SlideShow.ThumbnailViewer.base.constructor.call(this, control, parent, xaml);
	
	// FIXME: incorrect calculations require strange values for top, left, etc. in page container below
	SlideShow.merge(this.options,
	{
		top: 0,
		left: 0,
		bottom: 0,
		right: 0,
		enablePreview: true,
		viewerBackground: {},
		pageContainer:
		{
			top: 3,
			left: 19,
			right: 19,
			bottom: -5,
			padding: 0,
			spacing: 3,
			itemWidth: 30,
			itemHeight: 30,
			zIndex: 1,
			cursor: "Hand"
		},
		thumbnailNavigation:
		{
			height: 30
		},
		thumbnailPreview: {},
		thumbnailButton: {}			
	});
	
	this.setOptions(options);
	
	this.previewEnabled = this.options.enablePreview;
	this.viewerBackground = new SlideShow.ViewerBackground(this.control, this, this.options.viewerBackground);
	this.pageContainer = new SlideShow.PageContainer(this.control, this, this.options.pageContainer);
	this.thumbnailNavigation = new SlideShow.ThumbnailNavigation(this.control, this, this.options.thumbnailNavigation);
	this.thumbnailPreview = new SlideShow.ThumbnailPreview(this.control, this, this.options.thumbnailPreview);
	
	this.control.addEventListener("modulesLoad", SlideShow.createDelegate(this, this.onControlModulesLoad));
	this.control.addEventListener("dataLoad", SlideShow.createDelegate(this, this.onControlDataLoad));
};

SlideShow.extend(SlideShow.UserControl, SlideShow.ThumbnailViewer,
{
	getItems: function(fromIndex, count)
	{
		/// <summary>Retrieves a page of items from the overall collection of slides for the current album.</summary>
		/// <param name="fromIndex">The first index to retrieve from the current album's slides.</param>
		/// <param name="count">The number of items to retrieve overall.</param>
		
		var currentPageItems = [];
		this.currentItemListenerTokens = [];
		
		for (k = 0, l = this.currentItemListenerTokens.length; k < l; k++)
			this.slideViewer.removeEventListener(this.currentItemListenerTokens[k]);
		
		for (var i = fromIndex, j = fromIndex + count; i < j; i++)
		{
			if (this.control.data.album[this.slideViewer.currentAlbumIndex].slide[i])
			{
				var item = new SlideShow.ThumbnailButton(this.control, null, this.control.data.album[this.slideViewer.currentAlbumIndex].slide[i], this.options.thumbnailButton);
				
				item.addEventListener("click", SlideShow.createDelegate(this, this.onThumbnailClick));
				this.currentItemListenerTokens.push(this.slideViewer.addEventListener("slideLoading", SlideShow.createDelegate(item, item.onSlideLoading)));
				item.onSlideLoading(null, this.control.data.album[this.slideViewer.currentAlbumIndex].slide[this.slideViewer.currentSlideIndex]);
				
				if (this.options.enablePreview)
				{
					item.addEventListener("mouseEnter", SlideShow.createDelegate(this, this.onThumbnailEnter));
					item.addEventListener("mouseLeave", SlideShow.createDelegate(this, this.onThumbnailLeave));
				}
				currentPageItems.push(item);
			}
		}
		
		return currentPageItems;
	},
	
	getItemCount: function()
	{
		/// <summary>Retrieves the total number of items in the current album's slide collection.</summary>
	
		if (this.control.data && this.control.data.album && this.control.data.album[this.slideViewer.currentAlbumIndex].slide)
			return this.control.data.album[this.slideViewer.currentAlbumIndex].slide.length;
		
		return 0;
	},
	
	disablePreview: function()
	{
		/// <summary>Disables the thumbnail preview feature.</summary>
		
		this.previewEnabled = false;
	},
	
	resetPreview: function()
	{
		/// <summary>Resets the thumbnail preview feature according to originally specified options.</summary>
	
		this.previewEnabled = this.options.enablePreview;
	},
	
	onControlDataLoad: function(sender, e)
	{
		/// <summary>Handles the event fired when the configured data is loaded.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
	
		if (this.control.data)
			this.pageContainer.loadPageByOffset(0);
	},
	
	onControlModulesLoad: function(sender, e)
	{
		/// <summary>Completes initialization after all modules have loaded.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.slideViewer = this.control.modules["SlideViewer"];
		
		if (!this.slideViewer)
			throw new Error("Expected module missing: SlideViewer");
	},
	
	onThumbnailEnter: function(sender, isSelected)
	{
		/// <summary>Handles the event fired when the mouse enters a thumbnail.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="isSelected">Event arguments that indicate if the thumbnail corresponds to the current image.</param>
		
		if (!isSelected && this.previewEnabled)
		{
			var top = sender.root["Canvas.Top"] - this.thumbnailPreview.root.height - this.thumbnailPreview.arrow.height + 3;
			var left = this.pageContainer.root["Canvas.Left"] + this.pageContainer.currentPage["Canvas.Left"] + sender.root["Canvas.Left"] + sender.root.width / 2 - this.thumbnailPreview.root.width / 2;
			
			this.thumbnailPreview.setOptions({ top: top, left: left });
			this.thumbnailPreview.reposition();			
			this.thumbnailPreview.show(sender.getThumbnailImageSource());
		}
	},
	
	onThumbnailLeave: function(sender, isSelected)
	{
		/// <summary>Handles the event fired when the mouse leaves a thumbnail.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="isSelected">Event arguments that indicate if the thumbnail corresponds to the current image.</param>
		
		this.thumbnailPreview.hide();
	},
	
	onThumbnailClick: function(sender, e)
	{
		/// <summary>Changes the currently displayed slide to the selected image and bubbles up an event to indicate that a thumbnail has been selected.</summary>
		/// <param name="sender">The ThumbnailButton instance that was clicked.</param>
		/// <param name="e">The slide data associated with the selected instance.</param>
		
		for (var i = 0, j = this.control.data.album[this.slideViewer.currentAlbumIndex].slide.length; i < j; i++)
		{
			if (this.control.data.album[this.slideViewer.currentAlbumIndex].slide[i] == e && this.slideViewer.currentSlideIndex != i)
			{
				this.slideViewer.currentSlideIndex = i;
				this.slideViewer.loadImageByOffset(0, true);
			}
		}
		
		if (this.previewEnabled)
			this.thumbnailPreview.hide();
		
		this.fireEvent("thumbnailClick", e);
	}
});

/*******************************************
 * class: SlideShow.ThumbnailNavigation
 *******************************************/
SlideShow.ThumbnailNavigation = function(control, parent, options)
{
	/// <summary>Provides navigation for ThumbnailViewer's page container.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="parent">The parent control.</param>
	/// <param name="pageContainer">The page container.</param>
	/// <param name="options">The options for the control.</param>
	
	var xaml = '<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ThumbnailNavigation" Visibility="Collapsed" />';
	
	SlideShow.ThumbnailNavigation.base.constructor.call(this, control, parent, xaml);
	
	SlideShow.merge(this.options,
	{
		left: 0,
		right: 0,
		height: 20,
		foreground: "#777",
		enablePreviousSlide: false,
		enableNextSlide: false,
		previousPageButton: // FIXME: naming not consistent with AlbumNavigation
		{
			left: 0,
			height: 36,
			radius: 0,
			strokeThickness: 0,
			backgroundColor1: "Transparent",
			backgroundColor2: "Transparent",
			pathData: "M0.049999999,0.81200001 C0.049999999,0.39115903 0.39115902,0.049999999 0.81199999,0.049999999 L2.711,0.049999999 C3.1318409,0.049999999 3.473,0.39115903 3.473,0.81200001 L3.473,9.1880002 C3.473,9.6088412 3.1318409,9.9500002 2.711,9.9500002 L0.81199999,9.9500002 C0.39115902,9.9500002 0.049999999,9.6088412 0.049999999,9.1880002 z M-3.3603748,0.016 L-9.344,4.9998124 -3.3599998,9.9840002 Z",
			pathWidth: 10,
			pathHeight: 8,
			pathFill: "#777",
			pathFillDisabled: "#333"
		},
		nextPageButton:
		{
			right: 0,
			height: 36,
			radius: 0,
			strokeThickness: 0,
			backgroundColor1: "Transparent",
			backgroundColor2: "Transparent", 
			pathData: "M0.049999999,0.81200001 C0.049999999,0.39115903 0.39115902,0.049999999 0.81199999,0.049999999 L2.711,0.049999999 C3.1318409,0.049999999 3.473,0.39115903 3.473,0.81200001 L3.473,9.1880002 C3.473,9.6088412 3.1318409,9.9500002 2.711,9.9500002 L0.81199999,9.9500002 C0.39115902,9.9500002 0.049999999,9.6088412 0.049999999,9.1880002 z M6.9063742,0.016 L12.875,4.9998124 6.8910001,9.9840002 Z",
			pathWidth: 10,
			pathHeight: 8,
			pathFill: "#777",
			pathFillDisabled: "#333"
		},
		previousButton:
		{
			top: "50%",
			left: 22,
			radius: 0,
			strokeThickness: 0,
			backgroundColor1: "Transparent",
			backgroundColor2: "Transparent",
			pathData: "M256.9375,633.46875L256.9375,636.43725 267.2495,636.43725 267.2495,633.437255242651 Z",
			pathWidth: 10,
			pathHeight: 8,
			pathFill: "#777"
		},
		nextButton:
		{
			top: "50%",
			right: 22,
			radius: 0,
			strokeThickness: 0,
			backgroundColor1: "Transparent",
			backgroundColor2: "Transparent",
			pathData: "M612.21875,632.78125L612.21875,636.37525 608.56225,636.37525 608.56225,639.281489402504 612.21849996621,639.281489402504 612.21849996621,642.938131479117 615.093888547801,642.938131479117 615.093888547801,639.344555954334 618.8127229171,639.344555954334 618.8127229171,636.375554460764 615.093316965987,636.375554460764 615.12456845465,632.781500021178 Z",
			pathWidth: 10,
			pathHeight: 8,
			pathFill: "#777"
		}
	});
	
	this.setOptions(options);
	
	this.previousPageButton = new SlideShow.PathButton(this.control, this, this.options.previousPageButton);
	this.nextPageButton = new SlideShow.PathButton(this.control, this, this.options.nextPageButton);
	
	if (this.options.enablePreviousSlide)
	{
		this.previousButton = new SlideShow.PathButton(this.control, this, this.options.previousButton);
		this.previousButton.addEventListener("click", SlideShow.createDelegate(this, this.onPreviousClick));
	}
	
	if (this.options.enableNextSlide)
	{
		this.nextButton = new SlideShow.PathButton(this.control, this, this.options.nextButton);
		this.nextButton.addEventListener("click", SlideShow.createDelegate(this, this.onNextClick));
	}
	
	this.previousPageButton.addEventListener("click", SlideShow.createDelegate(this, this.onPreviousPageClick));
	this.nextPageButton.addEventListener("click", SlideShow.createDelegate(this, this.onNextPageClick));
	parent.pageContainer.addEventListener("pageLoad", SlideShow.createDelegate(this, this.onPageLoad));	
};

SlideShow.extend(SlideShow.PageNavigation, SlideShow.ThumbnailNavigation,
{
	onControlModulesLoad: function(sender, e)
	{
		/// <summary>Completes initialization after all modules have loaded.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		SlideShow.ThumbnailNavigation.base.onControlModulesLoad.call(this, sender, e);
		
		if (this.options.enablePreviousSlide || this.options.enableNextSlide)
			this.slideViewer.addEventListener("slideLoading", SlideShow.createDelegate(this, this.onSlideLoading));
	},
	
	onPreviousPageClick: function(sender, e) 
	{
		/// <summary>Handles the event fired when the previous page button is clicked.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.showPreviousPage();
	},
	
	onNextPageClick: function(sender, e) 
	{
		/// <summary>Handles the event fired when the next page button is clicked.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.showNextPage();
	},
	
	onPreviousClick: function(sender, e) 
	{
		/// <summary>Handles the event fired when the previous button is clicked.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.showPreviousSlide();
		this.fireEvent("previousClick");
	},
	
	onNextClick: function(sender, e) 
	{
		/// <summary>Handles the event fired when the next button is clicked.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		this.showNextSlide();
		this.fireEvent("nextClick");
	},
	
	onPageLoad: function(sender, e) 
	{
		/// <summary>Handles the event fired when a page is loaded in the page container.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		var pageIndex = this.parent.pageContainer.pageIndex;
		var pageCount = this.parent.pageContainer.pageCount;
		
		if (pageIndex > 0)
			this.previousPageButton.enable();
		else
			this.previousPageButton.disable();
		
		if (pageIndex < pageCount - 1)
			this.nextPageButton.enable();
		else
			this.nextPageButton.disable();
	},
	
	onSlideLoading: function(sender, e)
	{
		/// <summary>Handles the event fired when the next slide is loading.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>

		if (this.options.enablePreviousSlide)
		{
			if (this.slideExistsByOffset(-1))
				this.previousButton.enable();
			else
				this.previousButton.disable();
		}
		
		if (this.options.enableNextSlide)
		{
			if (this.slideExistsByOffset(1))
				this.nextButton.enable();
			else
				this.nextButton.disable();
		}
	}
});

/*******************************************
 * class: SlideShow.ThumbnailPreview
 *******************************************/
SlideShow.ThumbnailPreview = function(control, parent, options)
{
	/// <summary>Provides a preview image for thumbnails.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="parent">The parent control.</param>
	/// <param name="options">The options for the control.</param>
	
	var xaml =
		'<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ThumbnailPreview" Visibility="Collapsed">' +
		'	<Canvas.Resources>' +
		'		<Storyboard x:Name="HoverStoryboard">' +
		'			<DoubleAnimation Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleX" To="1" />' +
		'			<DoubleAnimation Storyboard.TargetName="ScaleTransform" Storyboard.TargetProperty="ScaleY" To="1" />' +
		'			<DoubleAnimation Storyboard.TargetName="TranslateTransform" Storyboard.TargetProperty="X" To="0" />' +
		'			<DoubleAnimation Storyboard.TargetName="TranslateTransform" Storyboard.TargetProperty="Y" To="0" />' +
		'		</Storyboard>' +
		'		<Storyboard x:Name="LoadStoryboard" Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">' +
		'			<DoubleAnimation x:Name="LoadAnimation" To="1" />' +
		'		</Storyboard>' +
		'	</Canvas.Resources>' +
		'	<Canvas.RenderTransform>' +
		'		<TransformGroup>' +
		'			<ScaleTransform x:Name="ScaleTransform" />' +
		'			<TranslateTransform x:Name="TranslateTransform" />' +
		'		</TransformGroup>' +
		'	</Canvas.RenderTransform>' +	
		'	<Rectangle x:Name="Background" />' +
		'	<Rectangle x:Name="Image" Opacity="0">' +
		'		<Rectangle.Fill>' +
		'			<ImageBrush x:Name="ImageBrush" />' +
		'		</Rectangle.Fill>' +
		'	</Rectangle>' +
		'	<Path x:Name="Arrow" Stretch="Fill" Data="M257.90625,640.65625 L264.31275,640.65625 261.10987,643.87525Z" />' +
		'</Canvas>';
	
	SlideShow.ThumbnailPreview.base.constructor.call(this, control, parent, xaml);
	
	SlideShow.merge(this.options,
	{
		width: 150,
		height: 120,
		radius: 4,
		stroke: "White",
		strokeThickness: 2,
		arrowWidth: 10,
		arrowHeight: 6,
		arrowFill: "White",
		imageStretch: "UniformToFill",
		imageBackground: "#333",
		hoverAnimationDuration: 0.1,
		loadAnimationDuration: 0.5,
		visibility: "Collapsed"
	});
	
	this.setOptions(options);
	
	this.hoverStoryboard = this.root.findName("HoverStoryboard");
	this.loadStoryboard = this.root.findName("LoadStoryboard");
	this.loadAnimation = this.root.findName("LoadAnimation");
	this.scaleTransform = this.root.findName("ScaleTransform");
	this.translateTransform = this.root.findName("TranslateTransform");
	this.background = this.root.findName("Background");
	this.image = this.root.findName("Image");
	this.imageBrush = this.root.findName("ImageBrush");
	this.arrow = this.root.findName("Arrow");
};

SlideShow.extend(SlideShow.UserControl, SlideShow.ThumbnailPreview,
{	 
	render: function()
	{
		/// <summary>Renders the preview using the current options.</summary>

		SlideShow.ThumbnailPreview.base.render.call(this);
		
		// arrow
		this.arrow.width = this.options.arrowWidth;
		this.arrow.height = this.options.arrowHeight;
		this.arrow.fill = this.options.arrowFill;
		this.arrow["Canvas.Top"] = this.root.height;
		this.arrow["Canvas.Left"] = this.root.width / 2 - this.arrow.width / 2;	
		
		// background
		this.background.width = this.options.width;
		this.background.height = this.options.height;
		this.background.radiusX = this.options.radius;
		this.background.radiusY = this.options.radius;
		this.background.fill = this.options.imageBackground;
		this.background.stroke = this.options.stroke;
		this.background.strokeThickness = this.options.strokeThickness;
		
		// image
		this.image.width = this.options.width;
		this.image.height = this.options.height;
		this.image.radiusX = this.options.radius;
		this.image.radiusY = this.options.radius;
		this.image.stroke = this.options.stroke;
		this.image.strokeThickness = this.options.strokeThickness;
		this.imageBrush.stretch = this.options.imageStretch;	
		
		// load animation
		if (this.imageBrush.downloadProgress == 1)
		{
			this.image.opacity = 1;
		}
		else
		{
			this.loadAnimation.duration = "0:0:" + this.options.loadAnimationDuration;
			this.imageBrush.addEventListener("DownloadProgressChanged", SlideShow.createDelegate(this, this.onImageDownloadProgressChanged));
			this.imageBrush.addEventListener("ImageFailed", SlideShow.createDelegate(this, this.onImageDownloadFailed));
		}
		
		// hover animation
		for (var i = 0, j = this.hoverStoryboard.children.count; i < j; i++)
			this.hoverStoryboard.children.getItem(i).duration = "0:0:" + this.options.hoverAnimationDuration;
	},
	
	show: function(image)
	{
		/// <summary>Displays the control with the specified image.</summary>
		/// <param name="image">The image to preview.</param>
		
		this.scaleTransform.scaleX = 0;
		this.scaleTransform.scaleY = 0;
		this.translateTransform.x = this.root.width / 2;
		this.translateTransform.y = this.root.height;
		this.root.visibility = "Visible";
		this.imageBrush.imageSource = image;
		this.hoverStoryboard.begin();
	},
	
	hide: function()
	{
		/// <summary>Hides the control.</summary>
	
		this.root.visibility = "Collapsed";
		this.imageBrush.imageSource = "";
	},
	
	onImageDownloadProgressChanged: function(sender, e)
	{
		/// <summary>Handles the event fired while the image is downloading.</summary>
		/// <param name="sender">The event source.</param>
		/// <param name="e">The event arguments.</param>
		
		if (sender.downloadProgress == 1)
			this.loadStoryboard.begin();
	},
	
	onImageDownloadFailed: function(sender, e)
	{
		/// <summary>Handles the event fired when the image failed to download.</summary>
		/// <param name="sender">The event soure.</param>
		/// <param name="e">The event arguments.</param>
		
		throw new Error("Image download failed: " + this.imageBrush.imageSource);
	}
});

/*******************************************
 * class: SlideShow.ViewerBackground
 *******************************************/
SlideShow.ViewerBackground = function(control, parent, options)
{
	/// <summary>Displays a background for the thumbnail viewer control.</summary>
	/// <param name="control">The Slide.Show control.</param>
	/// <param name="parent">The parent control.</param>
	/// <param name="options">The options for the control.</param>
	
	var xaml =
		'<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="ViewerBackground" Visibility="Collapsed">' +
		'	<Rectangle x:Name="background" />' +
		'</Canvas>';
	
	SlideShow.ViewerBackground.base.constructor.call(this, control, parent, xaml);

	SlideShow.merge(this.options,
	{
		top: 0,
		bottom: 0,
		left: 0,
		right: 0,
		radius: 4,
		fill: "Black",
		stroke: "Black",
		strokeThickness: 0	
	});
	
	this.setOptions(options);
	
	this.background = this.root.findName("background");
};

SlideShow.extend(SlideShow.UserControl, SlideShow.ViewerBackground,
{
	render: function()
	{
		/// <summary>Renders the viewer background using the current options.</summary>
		
		SlideShow.ViewerBackground.base.render.call(this);
		
		this.background.width = this.root.width;
		this.background.height = this.root.height;
		this.background.radiusX = this.options.radius;
		this.background.radiusY = this.options.radius;
		this.background.fill = this.options.fill;
		this.background.stroke = this.options.stroke;
		this.background.strokeThickness = this.options.strokeThickness;
	},
	
	onSizeChanged: function()
	{
		/// <summary>Handles the event fired when the control is resized.</summary>
		
		SlideShow.ViewerBackground.base.onSizeChanged.call(this);
		
		this.background.width = this.root.width;
		this.background.height = this.root.height;
	}
});