﻿navigator.opacityProperty = (navigator.isIE) ? 'filter' : 'opacity';
navigator.bgPositionProperty = (navigator.isIE) ? 'backgroundPositionY' : 'backgroundPosition';

var SectionMenuAnimator = {
	fps: 15, // Frames per second
	duration: 0.5, // Length of animation
	menuID: 'sections', // The menu being animated
	playEvents: ['mouseover'],
	rewindEvents: ['mouseout'],
	triggerElements: [],
	
	elementInit: function(element) {
		var hover = document.createElement('span');
		hover.style[navigator.opacityProperty] = (navigator.isIE) ? 'alpha(opacity=0)' : 0;
		var bgX = (element.currentStyle) ? element.currentStyle["backgroundPositionX"] :
			(window.getComputedStyle) ? document.defaultView.getComputedStyle(element, null).getPropertyValue('background-position').replace(/^([\w-]+) [\w-]+$/, "$1") : 0;
		SectionMenuAnimator.setBGY(element, bgX, "-22px");
		SectionMenuAnimator.setBGY(hover, bgX, "18px");
		if (navigator.isIE) hover.style.backgroundPositionX = bgX;
		element.appendChild(hover);
		return {
			link: element,
			hover: hover,
			'bgX': bgX
		};
	},
	
	propertiesAtFrame: function(frame, totalFrames) {
		var magnitude = Math.easeInOutQuadratic(frame, 0, 1, totalFrames - 1);
		var offset = parseInt(6 * magnitude);
		return {
			opacity: (navigator.isIE) ? 'alpha(opacity=' + magnitude * 100 + ')' : magnitude,
			linkBGY: (-22 - offset) + "px",
			hoverBGY: (18 - offset) + "px"
		};
	},
	
	setProperties: function(properties) {
		var bgPos = this.elements.link.style.backgroundPosition;
		SectionMenuAnimator.setBGY(this.elements.link, this.elements.bgX, properties.linkBGY);
		SectionMenuAnimator.setBGY(this.elements.hover, this.elements.bgX, properties.hoverBGY);
		this.elements.hover.style[navigator.opacityProperty] = properties.opacity;
	},
	
	setBGY: function(el, x, y) {
		if (navigator.isIE)
			el.style.backgroundPositionY = y;
		else
			el.style.backgroundPosition = x + " " + y;
	},
	
	init: function() {
		var menu = (document.getElementById) ? document.getElementById(this.menuID) : null;
		if (!menu || window.opera) return;
		menu.className = 'js';
		var els = menu.getElementsByTagName("a");
		for (var el, i = 0, imax = els.length; i < imax; i++) {
			el = els[i];
			if (el.parentNode.className != "current")
				this.triggerElements[this.triggerElements.length] = el;
		}
		this.animationController = new AnimationController(this.duration, this.triggerElements, this.playEvents, this.rewindEvents, this.elementInit, this.setProperties, this.propertiesAtFrame, this.fps, this.menuID);
	}
};

Event.add(window, 'load', SectionMenuAnimator.init.bind(SectionMenuAnimator));

var SubsectionMenuAnimator = {
	fps: 15,
	duration: 0.4,
	menuID: 'sub',
	playEvents: ['mouseover'],
	rewindEvents: ['mouseout'],
	triggerElements: [],
	
	elementInit: function(element) {
		var baseMask = document.createElement('span'); // Hides extended base image
		baseMask.className = 'base-mask';
		var fade = document.createElement('span');
		fade.className = 'fade';
		var after = document.createElement('span'); // Final hover image
		after.className = 'after';
		after.style.backgroundImage = (element.currentStyle) ? element.currentStyle["backgroundImage"] :
			(window.getComputedStyle) ? document.defaultView.getComputedStyle(element, null).getPropertyValue('background-image') : '';
		var mask = document.createElement('span'); // Mask for hover image while expanding
		mask.className = 'mask';
		fade.appendChild(after);
		fade.appendChild(mask);
		fade.style[navigator.opacityProperty] = (navigator.isIE) ? 'alpha(opacity=0)' : 0;
		element.appendChild(baseMask);
		element.appendChild(fade);
		return {
			link: element,
			fade: fade
		};
	},
	
	propertiesAtFrame: function(frame, totalFrames) {
		var magnitude = Math.easeInOutQuadratic(frame, 0, 1, totalFrames - 1);
		return {
			opacity: (navigator.isIE) ? 'alpha(opacity=' + magnitude * 100 + ')' : magnitude,
			width: parseInt(173 + (10 * magnitude)) + "px"
		};
	},
	
	setProperties: function(properties) {
		this.elements.link.style.width = properties.width;
		this.elements.fade.style[navigator.opacityProperty] = properties.opacity;
	},
	
	init: function() {
		var menu = (document.getElementById) ? document.getElementById(this.menuID) : null;
		if (!menu || window.opera) return;
		menu.className = 'js';
		var els = menu.getElementsByTagName("a");
		for (var el, i = 0, imax = els.length; i < imax; i++) {
			el = els[i];
			if (el.parentNode.className != "current")
				this.triggerElements[this.triggerElements.length] = el;
		}
		this.animationController = new AnimationController(this.duration, this.triggerElements, this.playEvents, this.rewindEvents, this.elementInit, this.setProperties, this.propertiesAtFrame, this.fps, this.menuID);
	}
};

Event.add(window, 'load', SubsectionMenuAnimator.init.bind(SubsectionMenuAnimator));

function AnimationController(duration, triggerElements, playEvents, rewindEvents, elementInit, setProperties, propertiesAtFrame, fps, id) {
	this.duration = duration;
	this.triggerElements = triggerElements;
	this.playEvents = playEvents;
	this.rewindEvents = rewindEvents;
	this.elementInit = elementInit;
	this.setProperties = setProperties;
	this.propertiesAtFrame = propertiesAtFrame;
	this.fps = fps || 30;
	this.id = id || ''; // For testing
	this.animations = [];
	this.frames = [];
	this.totalFrames = 0;
	this.init();
}

AnimationController.prototype = {
	init: function() {
		this.totalFrames = this.duration * this.fps;
		for (var i = 0; i < this.totalFrames; i++) // Set up frame buffer
			this.frames[i] = this.propertiesAtFrame(i, this.totalFrames);
		for (var element, elements, animation, i = 0, len = this.triggerElements.length; i < len; i++) { // Add animations
			element = this.triggerElements[i];
			elements = this.elementInit(element);
			animation = new Animation(elements, this.setProperties, this.frames, (this.duration / this.fps) * 1000, this.id + this.animations.length);
			for (var j = 0; j < this.playEvents.length; j++)
				Event.add(element, this.playEvents[j], animation.play.bind(animation));
			for (var j = 0; j < this.rewindEvents.length; j++)
				Event.add(element, this.rewindEvents[j], animation.rewind.bind(animation));
			this.animations[i] = animation;
		}
	}
};

function Animation(elements, setProperties, frames, frameDuration, id) {
	this.elements = elements;
	this.setProperties = setProperties;
	this.frames = frames;
	this.frameDuration = frameDuration;
	this.id = id;
	this.currentFrame = 0;
	this.currentAnimation = [];
	this.rewinding = false;
}

Animation.prototype = {
	play: function() {
		this.rewinding = false;
		this.clearCurrentAnimation();
		for (var i = 0, len = this.frames.length; i < len - this.currentFrame; i++) {
			this.currentAnimation = [];
			this.currentAnimation[i] = setTimeout(this.setFrame.bind(this, i + this.currentFrame), this.frameDuration * i);
		}
	},

	rewind: function() {
		this.rewinding = true;
		this.clearCurrentAnimation();
		for (var i = 0; i < this.currentFrame; i++)
			this.currentAnimation[i] = setTimeout(this.setFrame.bind(this, this.currentFrame - (i + 1)), this.frameDuration * i);
	},

	setFrame: function(frame, reverse) {
		if ((this.rewinding && this.currentFrame > frame) || (!this.rewinding && this.currentFrame < frame)) {
			this.setProperties(this.frames[frame]);
			this.currentFrame = frame;
		}
	},

	clearCurrentAnimation: function() {
		if (this.currentAnimation.length)
			for (var i = this.currentAnimation.length; i > 0; i--)
				clearTimeout(this.currentAnimation[i - 1]);
		this.currentAnimation = [];
	}
};


// Easing functions for convenience.
// t: current time, b: beginning value, c: change in value, d: duration

Math.easeLinear = function(t, b, c, d) {
	return t * c / d + b;
}

Math.easeInQuadratic = function(t, b, c, d) {
	return c * t * t / (d * d) + b;
}

Math.easeOutQuadratic = function(t, b, c, d) {
	return -c * t * t / (d * d) + 2 * c * t / d + b;
}

Math.easeInOutQuadratic = function(t, b, c, d) {
	if (t < d / 2) return 2 * c * t * t / (d * d) + b;
	var ts = t - d / 2;
	return -2 * c * ts * ts / (d * d) + 2 * c * ts / d + c / 2 + b;
}