var SL = SL || {};

/**
 * == Items ==
 * The Items Section provides VideoPlayer embedding, lights-off, ItemZooming, ...
 * 
 * ### Utility Functions
 * - [[SL.ElementNotify]] : Displays a short notifcation over another element
 * 
**/

/** section: Items
 * SL
 * The SL Namespace
**/


/** section: Items
 * class SL.VideoPlayer
 * Embedding the SL Video Player
**/

/**
 * new SL.VideoPlayer(settings)
 *     - settings (Object): See list below
 * 
 * Provides a small wrapper around [swfobject](http://code.google.com/p/swfobject/wiki/api)
 * where to hook in callbacks, magic (like adding class names to wrapper elements) or setting tracking vars.
 * 
 * ##### Settings
 * - Basics:
 *   - `flashPlayer` (`String` | `Elememt`): The ID of, or the Element which shall be replaced by the swfObject
 *   - `flashWrapper` (`String` | `Elememt`): The ID of, or the Element which surrounds the swfObject. To this Element, the `flashMinimalVersionClass` will be added, if `minimalFlashSupportVersion` does not match to trigger some layout-changes.
 * 
 * - Settings passed to swfobject:
 *   - `swfUrl` (`String`): The url to the swf
 *   - `width` (`String` | `Integer`): the width of your player (in pixels, __without__ trailing "px")
 *   - `height` (`String` | `Integer`): the height of your player (in pixels, __without__ trailing "px")
 *   - `params` (`Object`): A object with `"name":value` pairs of (flash) parametrs 
 *   - `flashvars` (`Object`): A object with `"name":value` pairs of flashvars 
 *   - `attributes` (`Object`): A object with `"name":value` pairs of (flash) attributes
 *   - `swfPlayerVersion` (String): _(Optional, default: `"9.0.0"`)_ The minimal flash player version the swf object need to play (format is: "major.minor.release" or "major")
 *   - `expressInstallSwfUrl` (`String`): The URL of your express install SWF
 * 
 * - Additional Settings:
 * - `minimalFlashSupportVersion`: _(Optional, default: `"5"`)_ The minimal flash player version to proceed any swfobject-handling. If not matched, `flashMinimalVersionClass` will be added to the `flashWrapper` element
 * - `flashMinimalVersionClass`: _(Optional, default: `"videoPlayerNotSupported"`)_ The css class to add to the `flashWrapper` element, if the `minimalFlashSupportVersion` is not matched
 * 
**/
SL.VideoPlayer = new Class({
	
	Implements: [Options, Events],
	
	options: {
		'flashMinimalVersionClass': 'videoPlayerNotSupported',
		'minimalFlashSupportVersion': '5',
		'swfPlayerVersion': '9.0.0',
		'expressInstallSwfUrl': null
	},
	
	flashPlayer: null,
	flashWrapper: null,
	
	initialize: function (options) {
		this.setOptions(options);
		
		this.flashPlayer = document.id(options.flashPlayer);
		this.flashWrapper = document.id(options.flashWrapper);
		
		if (false === swfobject.hasFlashPlayerVersion(this.options.minimalFlashSupportVersion)) {
			this.flashWrapper.addClass(this.options.flashMinimalVersionClass);
		}
	},
	
	/**
	 * SL.VideoPlayer#embed()
	 * 
	 * 
	 **/
	embed: function () {
		var o = this.options;
		swfobject.embedSWF(
			o.swfUrl,
			o.flashPlayer,
			o.width,
			o.height,
			o.swfPlayerVersion,
			o.expressInstallSwfUrl,
			o.flashvars,
			o.params,
			o.attributes,
			this.onEmbeded.bind(this));
	},
	
	onEmbeded: function (arg) {
		if (arg.success) {
			this.flashPlayer = document.id(arg.id);
			this.fireEvent('success', [arg.id, this.flashPlayer]);
		} else {
			this.fireEvent('failure', [arg.id]);
		}
	}
});


/** section: Items
 * class SL.ContentDarker
 *  includes Options, Events
 * 
**/

/**
 * new SL.ContentDarker(contentStage, options)
 *     - `contentStage` (`String`): The ID to the 'contentStage'
 *     - `options` (`Object`): _Required_: `darkenOverlayTrigger`, `lightenOverlayTrigger` when using as a self-working instance
 * 
 * ##### Options
 * - `darkenOverlayTrigger` (`Element` | `String`): Element or ID of the "Button" which triggers "lights off"/"dark mode"
 * - `lightenOverlayTrigger` (`Element` | `String`): Element or ID of the "Button" which triggers "lights on"/"light mode"
 * - `elementHideClass` (`String`): _(Optional, default: `'hide'`)_ The CSS-Class to set for elements to hide (e.g. the `darkenOverlayTrigger` button)
 * - `initialDarken` (`boolean`): _(Optional, default: `false`)_ Wheher to start dark
 * - `injectDarkenOverlay` (`Object`): _(Optional)_ config where to inject the darken layer (Mask) (also see [Element.inject](http://mootools.net/docs/core/Element/Element#Element:inject))
 *   - `relativeTo` (`Element` | `String`): Element or ID of the Element, where to inject the Mask (Element.inject() target). _Default:_ the `contentStage`
 *   - `position` (`String`): The place to inject the Mask. One of `'top'`, `'bottom'`, `'after'`, or `'before'`. _Default:_ `'before'`
 * - `darkenOverlayOptions` (`Object`): _(Optional)_ Aditional options for the created Mask:
 *   - `id` (`String`): The ID of the Maks. _Default:_ `'darkOverlay'`
 *   - `class` (`String`): The CSS-Class of the Mask. _Default:_ `'darkOverlay pngtrans'`
 * - `noneHideElements` (`Array`): _(Optional)_ Aditional options for areas on website which should not darkend by the mask
**/
SL.ContentDarker = new Class({
	
	Implements: [Options, Events],
	
	options: {
		'elementHideClass': 'hide',
		'initialDarken': false,
		'darkenOverlayTrigger': null,
		'lightenOverlayTrigger': null,
		'injectDarkenOverlay': {
			'relativeTo': null, // by default this.contentStage
			'position': 'bottom'
		},
		'darkenOverlayOptions': {
			'id': 'darkOverlay',
			'class': 'darkOverlay pngtrans'
		},
		'noneHideElements': []
	},
	
	contentStage: null,
	
	darkenLayer: null,
	
	initialize: function (contentStage, options) {
		
		this.setOptions(options);
		this.contentStage = document.id(contentStage);
		
		this.options.injectDarkenOverlay.relativeTo = 
			this.options.injectDarkenOverlay.relativeTo || this.contentStage;
			
		this.setupDarkenOverlay();
	},
	
	setupDarkenOverlay: function () {
		
		this.darkenTrigger = document.id(this.options.darkenOverlayTrigger);
		this.lightenTrigger = document.id(this.options.lightenOverlayTrigger);

		this.darkenLayer = new Mask(document.body, {
			'inject': {
				'where': this.options.injectDarkenOverlay.position,
				'target': this.options.injectDarkenOverlay.relativeTo
			},
			'id': this.options.darkenOverlayOptions.id,
			'class': this.options.darkenOverlayOptions['class'],
			'style': this.options.darkenOverlayOptions.style,
			'onShow': this.onDarken.bind(this),
			'onHide': this.onLighten.bind(this)
		});
		
		if (true === this.options.initialDarken) {
			this.darken();
		} else {
			this.lighten();
		}

		if (this.darkenTrigger && this.lightenTrigger) {
			
			this.darkenTrigger.addEvent('click', function (evt) {
				evt = new Event(evt).stop();
				this.darken();
			}.bind(this));
			
			this.lightenTrigger.addEvent('click', function (evt) {
				evt = new Event(evt).stop();
				this.lighten();
			}.bind(this));
		}
	},

	/**
	 * SL.ContentDarker#darken() -> undefined
	 * shows the darken layer (Mask)
	 **/
	darken: function () {
		this.options.noneHideElements.each(function(nonHideElementId) {
			$(nonHideElementId).setStyle('position', 'relative');
			$(nonHideElementId).setStyle('z-index', 25);
		});
		this.darkenLayer.show();
	},

	/**
	 * SL.ContentDarker#lighten() -> undefined
	 * hides the darken layer (Mask)
	 **/
	lighten: function () {
		this.options.noneHideElements.each(function(nonHideElementId) {
			$(nonHideElementId).setStyle('zIndex', 1);
		});
		this.darkenLayer.hide();
	},
	
	onDarken: function () {
		this.toggleDarkenTriggers();
	},
	
	onLighten: function () {
		this.toggleDarkenTriggers();
	},
	
	toggleDarkenTriggers: function () {
		if (this.darkenTrigger && this.lightenTrigger) {
			this.lightenTrigger.toggleClass(this.options.elementHideClass);
			this.darkenTrigger.toggleClass(this.options.elementHideClass);
		}
	},
	
	/**
	 * SL.ContentDarker#setLightenTriggerVisibility(enabled) -> undefined
	 *     - enabled (`Boolean`): `true` to make the trigger visible, `false` to make it hidden
	 * hides or shows the lighten button element by adding or removing the `options.elementHideClass`
	 **/
	setLightenTriggerVisibility: function (enabled) {
		if (this.darkenTrigger && this.lightenTrigger) {
			this.lightenTrigger[(enabled ? 'removeClass' : 'addClass')](this.options.elementHideClass);
		}
	},

	/**
	 * SL.ContentDarker#setDarkenTriggerVisibility(enabled) -> undefined
	 *     - enabled (`Boolean`): `true` to make the trigger visible, `false` to make it hidden
	 * hides or shows the darken button element by adding or removing the `options.elementHideClass`
	 **/	
	setDarkenTriggerVisibility: function (enabled) {
		if (this.darkenTrigger && this.lightenTrigger) {
			this.darkenTrigger[(enabled ? 'removeClass' : 'addClass')](this.options.elementHideClass);
		}
	}
});

/** section: UI
 * class SL.ContentZoomBase < SL.ContentDarker
 * Abstract class
 * 
 * This BaseClass handles the automated toggle for lighten/darken when zooming
 * it does _NOT_, however, zoom the content.
 * 
 * ##### Options
 * - toggleZoomTrigger (`String`): The id of the "button" which triggers the zoom toggle
 * - dimensions (`Object`): Set the dimenstion of the Content in it's zoomed 'in' or 'out' variants. 
 * Default is to autodetect this values to zoom on the size of the "contentStage"
 *   - in (`Object`): dimenstions when zoomed-in, in the format: `{'height': 160, 'width': 90}`
 *   - out (`Object`): dimenstions when zoomed-out, in the format: `{'height': 320, 'width': 180}`
 *   - scale (`Boolean`): whether to scale the zoomed-out-height by the "contentStage's" aspect ration _(Default: `false`)_
 * - itemWrapperSelector (`String`): The selector for the container element surrounding the real contnet item. Selector relative to the 'contentStage',
 * - zoomedClass (`String`): The css class to add (or remove) from the itemWrapper when zoomed-in (or out)
**/
SL.ContentZoomBase = new Class({
	
	Extends: SL.ContentDarker,
	
	options: {
		'toggleZoomTrigger': 'itemZoomLink',
		'itemWrapperSelector': 'div.itemWrapper',
		'zoomedClass': 'zoomed',
		'dimensions': {
			'in': null, // {'height': 160, 'width': 90}
			'out': null // {'height': 320, 'width': 180}
		},
		'scale': ['width'],
		'historyManagerKey': 'zoomed'
	},
	
	itemWrapper: null,

	zoomTrigger: null,
	
	isZoomed: false,
	
	initialize: function () {
		this.parent.apply(this, arguments);
		
		if (false === this.isZoomed) {
			this.setLightenTriggerVisibility(false);
			this.setDarkenTriggerVisibility(false);
		}
		
		this.zoomTrigger = document.id(this.options.toggleZoomTrigger);
		this.itemWrapper = this.contentStage.getElement(this.options.itemWrapperSelector);
		
		this.setupTriggers();
		this.setupDimensions();
		
		if (SL.HistoryManager) {
			this.listenToHistoryManager();
		}
	},
	
	listenToHistoryManager: function () {
		SL.HistoryManager.addEvent(this.options.historyManagerKey + '-added', this.zoomIn.bind(this));
		SL.HistoryManager.addEvent(this.options.historyManagerKey + '-removed', this.zoomOut.bind(this));
		SL.HistoryManager.addEvent(this.options.historyManagerKey + '-changed', this.zoomIn.bind(this));
	},
	
	setupTriggers: function () {
		if (this.zoomTrigger) {
			this.zoomTrigger.addEvent('click', function (evt) {
				evt = new Event(evt).stop();
				this.toggleZoom();
			}.bind(this));
		}
	},
	
	/**
	 * SL.ContentZoomBase#setupDimensions()
	 * 
	 * __This method must die__
	 * The `itemWrapper` container shall be resized using css and the `zoomedClass` option
	 * 
	 **/
	setupDimensions: function () {

		if (false === this.options.scale) {
			return;
		}
		
		var din = this.options.dimensions['in']; // "in" is a reserved word
		var dout = this.options.dimensions['out'];
		
		if (!dout) {
			// getDimensions() gets us the real size of an element
			// we just dont need the position
			dout = this.itemWrapper.getDimensions();
			
			if ('array' === $type(this.options.scale)) {
				$H(dout).getKeys().each(function (key) {
					if (!this.options.scale.contains(key)) {
						delete dout[key];
					}
				}.bind(this));
			}
			this.options.dimensions['out'] = dout;
		}
		
		if (!din) {
			// getDimensions() gets us the real size of an element
			// we need the height only
			din = this.contentStage.getDimensions();
			
			if ('array' === $type(this.options.scale)) {
				$H(din).getKeys().each(function (key) {
					if (!this.options.scale.contains(key)) {
						delete din[key];
					}
				}.bind(this));
			}
			this.options.dimensions['in'] = din;
		}
	},
	
	/**
	 * SL.ContentZoomBase#toggleZoom()
	 * 
	 * calls [[SL.ContentZoomBase#zoomIn]] or [[SL.ContentZoomBase#zoomOut]] according to the current zoom state
	 **/
	toggleZoom: function () {
		this["zoom" + (this.isZoomed ? 'Out' : 'In')]();
	},
	
	/**
	 * SL.ContentZoomBase#zoomIn()
	 * zoom into the content, makes the content larger.
	 * 
	 * zoomin in will:
	 * - enable the darken trigger
	 * - disable the lighten trigger
	 * - add the `zoomedClass` to the `itemWrapper`
	 * - sets the dimension styles on the `itemWrapper`. see `scale` option
	 * 
	 **/
	zoomIn: function () {
		if (true === this.isZoomed) {
			return;
		}
		this.isZoomed = true;
		
		this.setLightenTriggerVisibility(false);
		this.setDarkenTriggerVisibility(true);
		
		if (this.options.zoomedClass) {
			this.itemWrapper.addClass(this.options.zoomedClass);
		}
		
		if (false !== this.options.scale) {
			this.itemWrapper.setStyles(this.options.dimensions['in']);
		}
		
		SL.HistoryManager.set(this.options.historyManagerKey);
	},
	
	/**
	 * SL.ContentZoomBase#zoomIn()
	 * zoom out the content, makes the content smaller
	 * 
	 * zooming out will:
	 * - lighten the content. see [[SL.ContentDarker#lighten]]
	 * - disable the lighten and darken triggers
	 * - remove the `zoomedClass` from the `itemWrapper`
	 * - sets the dimension styles on the `itemWrapper`. see `scale` option
	 * 
	 **/
	zoomOut: function () {
		if (false === this.isZoomed) {
			return;
		}
		this.isZoomed = false;
		
		this.lighten();
		this.setLightenTriggerVisibility(false);
		this.setDarkenTriggerVisibility(false);
		
		if (this.options.zoomedClass) {
			this.itemWrapper.removeClass(this.options.zoomedClass);
		}
		
		if (false !== this.options.scale) {
			this.itemWrapper.setStyles(this.options.dimensions['out']);
		}
		
		SL.HistoryManager.remove(this.options.historyManagerKey);
	}
	
});


/** section: UI
 * class SL.ContentZoomImage < SL.ContentZoomBase
 * 
 **/

/**
 * new SL.ContentZoomImage(contentStage, zoomOptions, imageOptions)
 *     - `contentStage` (`String`): The ID to the 'contentStage'
 *     - `zoomOptions` (`Object`): all those of [[SL.ContentZoomBase]] plus:
 *
 * ##### Options
 * - `imageSelector` (`String`)
 * - `imageWrapperSelector` (`String`)
 * - `imageDimensions` (`String`)
 * - `imageWrapperDimensions` (`String`)
 * 
 *     - `imageOptions` (`Object`): some image options for the zoom  
 *     
* ##### Options
 * - `itemMediaZoomedHeight` (`String`): the max height we can zoom the image
 * - `itemMediaZoomedWidth` (`String`): the max width we can zoom the image
 * - `itemMediaZoomedSrc` (`String`): the image source url with bigger dimensions
**/
SL.ContentZoomImage = new Class({

	Extends: SL.ContentZoomBase,

	options: {
		'imageSelector': '#imageItem',
		'imageWrapperSelector': '#imageStage',
		'imageDimensions' : {
			'in': null,
			'out': null
		},
		'imageWrapperDimensions' : {
			'in': null,
			'out': null
		},
		'imageUrl': {
			'in': null,
			'out':null
		}
	},
	
	image: null,
	imageWrapper: null,
	
	initialize: function () {
		this.parent.apply(this, arguments);

		this.image = this.contentStage.getElement(this.options.imageSelector);
		this.imageWrapper = this.contentStage.getElement(this.options.imageWrapperSelector);
		if (!this.image) {
			this.image = this.imageWrapper;
		}

		var imageSettings = arguments[2];

		this.options['imageDimensions']['in']         = {'width': imageSettings.itemMediaZoomedWidth, 'height': imageSettings.itemMediaZoomedHeight};
		this.options['imageWrapperDimensions']['in']  = {'width': imageSettings.itemMediaZoomedWidth, 'height': imageSettings.itemMediaZoomedHeight};
		this.options['imageUrl']['in']                = imageSettings.itemMediaZoomedSrc;
		this.options['imageUrl']['out']               = this.image.getProperty('src');
		
		
		['image','imageWrapper'].each(this.setupItemDimensions.bind(this));
	},
	
	setupItemDimensions: function (container) {

		var din = this.options[container + "Dimensions"]['in'];
		var dout = this.options[container + "Dimensions"]['out'];
		
		if (!dout) {
			dout = this[container].getDimensions();
            if (dout['height'].toInt() == 0) dout['height'] = this[container].getStyle('height').toInt();
            if (dout['width'].toInt() == 0) dout['width'] = this[container].getStyle('width').toInt();
			delete dout['x'];
			delete dout['y'];
			this.options[container + "Dimensions"]['out'] = dout;
		}
		
		if (!din) {
			din = this.contentStage.getDimensions();
			din.height = (din.width / (dout.width / dout.height));
			delete din['x'];
			delete din['y'];
			this.options[container + "Dimensions"]['in'] = din;
		}
	},
		
	zoomIn: function () {
		this.parent();
		this.image.setStyles(this.options.imageDimensions['in']);
		this.imageWrapper.setStyles(this.options.imageWrapperDimensions['in']);
		this.image.setProperty('src', this.options['imageUrl']['in']);
	},
	
	zoomOut: function () {
		this.parent();
		this.image.setStyles(this.options.imageDimensions['out']);
		this.imageWrapper.setStyles(this.options.imageWrapperDimensions['out']);
		this.image.setProperty('src', this.options['imageUrl']['out']);
	}

});

/** section: UI
 * class SL.ContentZoomVideo < SL.ContentZoomBase
 * 
 **/

/**
 * new SL.ContentZoomVideo(contentStage, options)
 *     - `contentStage` (`String`): The ID to the 'contentStage'
 *     - `options` (`Object`): all those of [[SL.ContentZoomBase]] plus:
 *
 * ##### Options
 * - `playerSelector` (`String`): The [DOM-Selector](http://mootools.net/docs/core/Utilities/Selectors) to find the Video-Player `embed`/`object`. _Default:_ `'#flashVideoPlayer'`
 * - `playerWrapperSelector`
 * - `playerDimensions`
 * - `playerWrapperDimensions`
**/
SL.ContentZoomVideo = new Class({
	
	Extends: SL.ContentZoomBase,
	
	options: {
		'playerSelector': '#flashVideoPlayer',
		'playerWrapperSelector': 'div.videoPlayerWrapper',
		'playerDimensions' : {
			'in': null,
			'out': null
		},
		'playerWrapperDimensions' : {
			'in': null,
			'out': null
		}
	},
	
	player: null,
	playerWrapper: null,
	
	initialize: function () {
		this.parent.apply(this, arguments);

		this.player = this.contentStage.getElement(this.options.playerSelector);
		this.playerWrapper = this.contentStage.getElement(this.options.playerWrapperSelector);
		if (!this.player) {
			this.player = this.playerWrapper;
		}

		['player','playerWrapper'].each(this.setupItemDimensions.bind(this));
	},
	
	setupItemDimensions: function (container) {

		var din = this.options[container + "Dimensions"]['in'];
		var dout = this.options[container + "Dimensions"]['out'];
		
		if (!dout) {
			dout = this[container].getDimensions();
            if (dout['height'].toInt() == 0) dout['height'] = this[container].getStyle('height').toInt();
            if (dout['width'].toInt() == 0) dout['width'] = this[container].getStyle('width').toInt();
			delete dout['x'];
			delete dout['y'];
			this.options[container + "Dimensions"]['out'] = dout;
		}
		
		if (!din) {
			din = this.contentStage.getDimensions();
			din.height = (din.width / (dout.width / dout.height));
			delete din['x'];
			delete din['y'];
			this.options[container + "Dimensions"]['in'] = din;
		}
	},
		
	zoomIn: function () {
		this.parent();
		this.player.setStyles(this.options.playerDimensions['in']);
		this.playerWrapper.setStyles(this.options.playerWrapperDimensions['in']);
	},
	
	zoomOut: function () {
		this.parent();
		this.player.setStyles(this.options.playerDimensions['out']);
		this.playerWrapper.setStyles(this.options.playerWrapperDimensions['out']);
	}
	
});


/** section: SL.Registry
 * SL.VideoPlayer.startup() -> undefined
 * 
 * Uses [[SL.Registry]] namespaces:
 * - `sl.startups.mainplayer` for the VideoPlayer settings. A single registry value is
 *   - setting (`Object`): options as defined by [[SL.VideoPlayer]]
 * - `sl.startups.mainplayerzoom` for the VideoPlayer-Zoom settings
 *   - setting (Object): options object as defined by [[SL.ContentZoomVideo]].
 *
**/
SL.VideoPlayer.startup = function () {
	var playerSettings = SL.Registry.get('sl.startups.mainplayer')[0];
	var imageSettings = SL.Registry.get('sl.startups.imageStage')[0];
	var zoomSettings = SL.Registry.get('sl.startups.mainplayerzoom')[0];
	if (playerSettings) {
		var p = new SL.VideoPlayer(playerSettings);
		
		if (zoomSettings) {
			// we need to block the HistoryManager start, until the player was embeded and the ContentZoomer build
			if (SL.HistoryManager) {
				SL.HistoryManager.blockStart('sl.startups.mainplayerzoom');
			}
			
			p.addEvent('success', function (playerId, player) {
				new SL.ContentZoomVideo(zoomSettings.stage, zoomSettings.options);
				if (SL.HistoryManager) {
					SL.HistoryManager.unblockStart('sl.startups.mainplayerzoom');
				}
			});

			p.addEvent('failure', function (playerId) {
				new SL.ContentZoomVideo(zoomSettings.stage, zoomSettings.options);
				if (SL.HistoryManager) {
					SL.HistoryManager.unblockStart('sl.startups.mainplayerzoom');
				}
			});
		}
		p.embed();
	}

	if(imageSettings) {
		new SL.ContentZoomImage(zoomSettings.stage, zoomSettings.options, imageSettings);
	}
}
SL.Registry.add('sl.impl.startups', SL.VideoPlayer.startup);
