/**
*  Sesame Street Hide and Seek Game
*
*  @author William Malone (www.williammalone.com)
*/

/*global window, WM */

WM.hideSeek = function (GLOBAL, WM, options) {
	
	"use strict";
	
	var that = WM.game(GLOBAL, WM, options),
		hud,
		background,
		currentRound = 0,
		maxIdleTimeoutAmount = 7000,
		idleTimeoutAmount = maxIdleTimeoutAmount,
		lastUserInputTime,
		timeoutActive = false,
		timeoutStart,
		curState = "INTRO",
		masks,
		targets,
		activeTarget,
		payoff,
		lastWiggleTime,
		curWiggleItem,
		payoffIndex,
		payoffInterval = 60 * 0,
		roundEndIndex,
		roundEndInterval = 60 * 2,
		
		//  Loads the resources for the round specified via parameter
		//
		loadRoundResources = function (roundIndex) {
			var i,
				g,
				s,
				specs = {};

			// Load background image
			if (options.rounds[roundIndex].background.filename) {				
				that.resourcer.add(options.rounds[roundIndex].background.filename);
				options.rounds[roundIndex].background.imageResource = that.resourcer.get(options.rounds[roundIndex].background.filename);
			} else {
				WM.error({
					name: "ResourceMissing", 
					message: "Background not specified in configuration file for round " + roundIndex + "."
				});	
			}
			
			// Load items
			if (options.rounds[roundIndex].itemGroups && options.rounds[roundIndex].itemGroups.length) {
				for (g = 0; g < options.rounds[roundIndex].itemGroups.length; g += 1) {
					
					for (i = 0; i < options.rounds[roundIndex].itemGroups[g].items.length; i += 1) {
					
						// Add target state resources
						for (s = 0; s < options.rounds[roundIndex].itemGroups[g].items[i].states.length; s += 1) {
							that.resourcer.add(options.rounds[roundIndex].itemGroups[g].items[i].states[s].filename);
							options.rounds[roundIndex].itemGroups[g].items[i].states[s].imageResource = that.resourcer.get(options.rounds[roundIndex].itemGroups[g].items[i].states[s].filename);
							options.rounds[roundIndex].itemGroups[g].items[i].states[s].x += that.overallOffset.x;
							options.rounds[roundIndex].itemGroups[g].items[i].states[s].y += that.overallOffset.y;
						}
						
						// Add mask state resources (if a mask is specified)
						if (options.rounds[roundIndex].itemGroups[g].items[i].mask) {
							for (s = 0; s < options.rounds[roundIndex].itemGroups[g].items[i].mask.states.length; s += 1) {
								that.resourcer.add(options.rounds[roundIndex].itemGroups[g].items[i].mask.states[s].filename);
								options.rounds[roundIndex].itemGroups[g].items[i].mask.states[s].imageResource = that.resourcer.get(options.rounds[roundIndex].itemGroups[g].items[i].mask.states[s].filename);
								
							options.rounds[roundIndex].itemGroups[g].items[i].mask.states[s].x += that.overallOffset.x;
							options.rounds[roundIndex].itemGroups[g].items[i].mask.states[s].y += that.overallOffset.y;
							}
						}
					}
				}
			} else {
				WM.error({
					name: "ResourceMissing", 
					message: "No items specified in configuration file for round " + roundIndex + "."
				});	
			}
			
			// Load payoff images
			specs.payoff = options.rounds[roundIndex].payoff;
			if (specs.payoff) {		
				for (s = 0; s < specs.payoff.states.length; s += 1) {
					that.resourcer.add(specs.payoff.states[s].filename);
					specs.payoff.states[s].imageResource = that.resourcer.get(specs.payoff.states[s].filename);
				}
			}
		},
		
		// Returns true if all target items are in the found state
		//
		allItemsFound = function () {
			var i;
			
			// For each target
			for (i = 0; i < targets.length; i += 1) {
				// If target is not found
				if (!targets[i].found) {
					return false;
				}	
			}
			// Return that all items have been found
			return true;
		},
		
		// Returns all target items that are not in the found state
		//
		getRemainingTargetItems = function () {
			var i,
				targetItemsNotFound = [];
				
			// For each target
			for (i = 0; i < targets.length; i += 1) {
				// If target is not found
				if (!targets[i].found) {
					// Add target to array that will be returned
					targetItemsNotFound.push(targets[i]);
				}
			}
			return targetItemsNotFound;
		},
		
		// Invokes the wiggle method on a random unfound target item
		//
		randomWiggle = function () {
		
			var i,
				remainingTargetItems,
				randomIndex;
			
			if (curWiggleItem !== undefined && curWiggleItem !== null && !curWiggleItem.found) {
				
				// If the idle time is greater than one second then half the idle time
				if (idleTimeoutAmount > 1000) {
					idleTimeoutAmount /= 2;
					
					WM.logStats({
					    event: "timeout",
					    title: that.gameTitle,
					    id: that.gameId,
					    params : {
					    	description: "Wiggle object again.",
					    	object: curWiggleItem.id
					    }
					});
				}
				if (idleTimeoutAmount < 1000) {
					idleTimeoutAmount = 1000;
				}
				curWiggleItem.wiggle();
	
			} else {
				idleTimeoutAmount = maxIdleTimeoutAmount;
				
				// Get all targets that are not found
				remainingTargetItems = getRemainingTargetItems();
				
				// If there are no remaining target items then nothing to wiggle
				if (!remainingTargetItems.length) {
					return;
				}
				
				// Get random target and wiggle it
				randomIndex = parseInt(Math.random() * remainingTargetItems.length);
				
				curWiggleItem = remainingTargetItems[randomIndex];
				curWiggleItem.wiggle();
				
				WM.logStats({
				    event: "timeout",
				    title: that.gameTitle,
				    id: that.gameId,
				    params : {
				    	description: "Wiggle random object.",
					    object: curWiggleItem.id
				    }
				});
			}
			lastWiggleTime = + new Date();
		},
		
		// Set the target item to found
		//
		markTargetFound = function (target) {
		
			var i;
		
			lastWiggleTime = + new Date();
			idleTimeoutAmount = maxIdleTimeoutAmount;
			
			WM.logStats({
			    event: "answer.selected",
			    title: that.gameTitle,
			    id: that.gameId,
			    params : {
			    	object: target.id,
			    	correct: true
			    }
			});
			
			for (i = 0; i < targets.length; i += 1) {
				targets[i].stopWiggle();
			}

			target.found = true;
			
			if (!that.roundComplete && allItemsFound()) {
				target.addEventListener("ANIMATION_COMPLETE", function () {
					lastWiggleTime = + new Date();
					this.ctx = that.contexts.found;	
					
					// Check if all targets are found
					// If all targets found
					if (!that.roundComplete && allItemsFound()) {
					
						that.roundComplete = true;
			
						if (that.playSound("PAYOFF")) {
							that.audio.addEventListener("COMPLETE", payoffSoundComplete);
						} else {
							payoffSoundComplete();
						}
					}
				});
			} else {
				target.addEventListener("ANIMATION_COMPLETE", function () {
					lastWiggleTime = + new Date();
					this.ctx = that.contexts.found;
				});
			}
			target.ctx = that.contexts.found;										
			target.setState("FOUND");
			if (target.mask) {
				target.mask.setState("FOUND");
			}
			
			if (that.playSound("CORRECT_HIT")) {
				that.audio.addEventListener("COMPLETE", that.playMusic);
			}
		},
		
		roundComplete = function () {
		
			WM.logStats({
			    event: "round.end",
			    title: that.gameTitle,
			    id: that.gameId,
			    params : {
			    	round: currentRound
			    }
			});
			if (currentRound < options.rounds.length - 1) { 
				currentRound += 1;
			} else {
				currentRound = 0;
			}
			that.roundSuccess();
		},
		
		payoffSoundComplete = function () {
		
			if (that.audio && that.audioEnabled) {
				that.audio.removeEventListener("COMPLETE", payoffSoundComplete);
			}
			curState = "PAYOFF_SOUND_COMPLETE";
	
		},
		
		payoffAnimationComplete = function () {
		
			if (payoff) {
				payoff.removeEventListener("COMPLETE", payoffAnimationComplete);
			}
			curState = "PAYOFF_ANIMATION_COMPLETE";
	
		},
		
		introAudioComplete = function (point) {
		
			// If round is not completed before intro audio is complete
			if (!that.roundComplete) {
				if (that.audio) {
					that.audio.removeEventListener("COMPLETE", introAudioComplete);
					that.playMusic();
				}
				
				lastWiggleTime = + new Date();
				
				curState = "PLAY";
			}
		};

	// Initialization
	//
	that.load = function () {
		
		var i;

		for (i = 0; i < options.rounds.length; i += 1) {
			loadRoundResources(i);
		}

		that.resourcer.addEventListener("LOAD_PROGRESS", that.onResourceLoaded);
		that.resourcer.addEventListener("RESOURCES_LOADED", that.onResourcesLoaded);
		that.resourcer.loadAll();	
	};
	
	that.setup = function () {
		
		var view,
			i,
			g,
			s,
			specs = {},
			target,
			targetSpec,
			mask,
			maskSpec,
			state,
			ctx;
		
		// Create background
		background = WM.sprite(GLOBAL, WM, that.contexts.background, options.rounds[currentRound].background);
		background.type = "BACKGROUND";
		background.offsetX = that.overallOffset.x,
		background.offsetY = that.overallOffset.y
		that.addView(background);
		
		targets = [];
		masks = [];
		for (g = 0; g < options.rounds[currentRound].itemGroups.length; g += 1) {
			for (i = 0; i < options.rounds[currentRound].itemGroups[g].items.length; i += 1) {
				
				targetSpec = options.rounds[currentRound].itemGroups[g].items[i];
				target = WM.actor(GLOBAL, WM, targetSpec);
				for (s = 0; s < targetSpec.states.length; s += 1) {
					state = targetSpec.states[s];

					if (state.type.toUpperCase() === "FOUND") {
						state.ctx = that.contexts.found;
					} else if (state.type.toUpperCase() === "DRAG") {
						state.ctx = that.contexts.drag;
					} else {
						state.ctx = that.contexts.hidden;
					}
					//state.x += that.overallOffset.x;
					//state.y += that.overallOffset.y;
					target.addState(state);
				}
				// If no target is specified set it to the filename of the first state
				if (target.id == undefined) {
					target.id = targetSpec.states[0].filename;
				}
				target.name = "TARGET";
				target.setState("HIDDEN");
				target.found = false;
				targets.push(target);
				that.addView(target);
				
				// If a mask is assign to a target
				if (targetSpec.mask) {
					maskSpec = targetSpec.mask;
					mask = WM.actor(GLOBAL, WM, maskSpec);
					mask.foundOnTouch = maskSpec.foundOnTouch;
					mask.wiggleOnTouch = maskSpec.wiggleOnTouch;
					mask.touchDisabled = maskSpec.touchDisabled;
					
					for (s = 0; s < maskSpec.states.length; s += 1) {
						
						state = maskSpec.states[s];
						state.ctx = that.contexts.mask;
						//state.x += that.overallOffset.x;
						//state.y += that.overallOffset.y;
						mask.addState(state);
						
						if (state.foundOnState) {
							mask.addEventListener(state.type, function () {
								if (activeTarget.target) {
									markTargetFound(activeTarget.target);
								} else {
									markTargetFound(activeTarget);
								}
							});
						}
					}
					mask.name = "MASK";
					mask.setState("DEFAULT");					
					masks.push(mask);
					that.addView(mask);
					
					// Couple the target and its mask
					target.mask = mask;
					mask.target = target;
				} else {
					mask = null;
				}
			}
		}
		
		// Setup optional payoff animation
		specs.payoff = options.rounds[currentRound].payoff;
		if (specs.payoff) {
			// Create payoff
			payoff = WM.actor(GLOBAL, WM, options.rounds[currentRound].payoff);
			for (s = 0; s < options.rounds[currentRound].payoff.states.length; s += 1) {
				options.rounds[currentRound].payoff.states[s].ctx = that.contexts.payoff;
				payoff.addState(options.rounds[currentRound].payoff.states[s]);
			}
			payoff.type = "PAYOFF";
			payoff.x = options.rounds[currentRound].payoff.x + that.overallOffset.x;
			payoff.y = options.rounds[currentRound].payoff.y + that.overallOffset.y;
			payoff.dirty = false;
			payoff.setState("DEFAULT");
			payoff.visible = payoff.getState("DEFAULT");
			that.addView(payoff);
		} else {
			payoff = null;	
		}
		
		if (that.audioEnabled) {
			that.audio.clearAllSounds();
			// Add game audio if it exists
			if (options.audio) {
				for (i = 0; i < options.audio.states.length; i += 1) {
					that.audio.setSound(options.audio.states[i]);
				}
			}
			// Add round audio if it exists
			if (options.rounds[currentRound].audio) {
				for (i = 0; i < options.rounds[currentRound].audio.states.length; i += 1) {
					that.audio.setSound(options.rounds[currentRound].audio.states[i]);
				}
			}
		}
		
		// Clear canvases
		that.clearCxt(that.contexts.hidden);
		that.clearCxt(that.contexts.mask);
		that.clearCxt(that.contexts.found);
		that.clearCxt(that.contexts.drag);
		that.clearCxt(that.contexts.maskDrag);
		that.clearCxt(that.contexts.payoff);
		that.clearCxt(that.contexts.transition);
		
		// Add events
		that.controller.addEventListener("TOUCH_START", that.touchStart);
		that.controller.addEventListener("TOUCH_MOVE", that.touchMove);
		that.controller.addEventListener("TOUCH_END", that.touchEnd);
		that.controller.addEventListener("ORIENTATION_CHANGE", that.onOrientationChange);
		
		curWiggleItem = null;
		lastUserInputTime = + new Date();
		lastWiggleTime = + new Date();
		that.roundComplete = false;
		payoffIndex = 0;
		
		WM.logStats({
		    event: "round.start",
		    title: that.gameTitle,
		    id: that.gameId,
		    params : {
		    	round: currentRound
		    }
		});
		curState = "INTRO";
		if (that.playSound("INTRO")) {
			that.audio.addEventListener("COMPLETE", introAudioComplete);
		} else {
			introAudioComplete();
		}
	};
	
	that.breakdown = function () {
		var i,
			curView,
			viewList = [];
	
		for (i = 0; i < that.views.length; i += 1) {
			viewList.push(that.views[i])
		}

		for (i = 0; i < viewList.length; i += 1) {
			that.removeView(viewList[i]);
			viewList[i].destroy();
		}
	};

	// Update
	//
	that.update = function () {
		
		var i,
			curView,
			now = + new Date();
			
		if (curState === "START_PAYOFF") {
		
			payoffIndex = 0;
			curState = "PAYOFF";
			
		} else if (curState === "PAYOFF") {
			
			if (payoffIndex >= payoffInterval) {
				curState = "PAYOFF_COMPLETE"
			} else {
				payoffIndex += 1;
			}
			
		} else if (curState === "PAYOFF_COMPLETE") {
			roundEndIndex = 0;
			curState = "ROUND_END_TRANSITION";
		
		} else if (curState === "PAYOFF_SOUND_COMPLETE") {

			if (payoff && payoff.getState("SUCCESS")) {
				payoff.addEventListener("ANIMATION_COMPLETE", payoffAnimationComplete);
				payoff.setState("SUCCESS");
				payoff.visible = true;
				curState = "PAYOFF_ANIMATION";
			} else {
				curState = "START_PAYOFF";
			}
		
		} else if (curState === "PAYOFF_ANIMATION_COMPLETE") {
			
			curState = "START_PAYOFF";
				
		} else if (curState === "ROUND_END_TRANSITION") {
		
			if (roundEndIndex >= roundEndInterval) {
				roundComplete();
			} else {
				roundEndIndex += 1;
			}
			
		} else {	
			if ((now - lastWiggleTime) > idleTimeoutAmount) {
				randomWiggle();
			}
		}
		
		for (i = 0; i < that.views.length; i += 1) {
			
			curView = that.views[i];
			
			curView.update();
		}
		
	};
	
	that.render = function () {
		
		var i,
			maskDirty = false,
			targetDirty = false;
			
		// If any targets to be rendered
		for (i = 0; i < targets.length; i += 1) {
			if (targets[i].dirty) {
				targetDirty = true;
			}
		}
		if (targetDirty) {
			// Mark all targets dirty
			for (i = 0; i < targets.length; i += 1) {
				targets[i].dirty = true;
			}
			// Clear all the contexts that can contain targets
			that.clearCxt(that.contexts.hidden);
			that.clearCxt(that.contexts.found);
			that.clearCxt(that.contexts.drag);
		}
		
		// If any masks need to be rendered
		for (i = 0; i < masks.length; i += 1) {
			if (masks[i].dirty) {
				maskDirty = true;
			}
		}
		if (maskDirty) {
			// Mark all masks dirty
			for (i = 0; i < masks.length; i += 1) {
				masks[i].dirty = true;
			}
			// Clear the mask context
			that.clearCxt(that.contexts.mask);
			that.clearCxt(that.contexts.maskDrag);
		}
		
		if (curState === "ROUND_END_TRANSITION") {
			that.clearCxt(that.contexts.transition);
			that.contexts.transition.fillStyle = "rgba(255, 255, 255, " + (roundEndIndex / roundEndInterval) + ")";
			that.contexts.transition.fillRect(0, 0, that.viewportWidth, that.viewportHeight);
		}
		
		if (payoff && payoff.dirty) {
			that.clearCxt(that.contexts.payoff);
		}

		for (i = 0; i < that.views.length; i += 1) {
			that.views[i].render();
		}
	};
	
	// Fired when user touches
	//
	that.touchStart = function (point) {
		
		var i,
			now = + new Date();
	
		if (!that.paused) {
				
			lastUserInputTime = now;
		
			// For each target item
			for (i = 0; i < targets.length; i += 1) {
				
				// If the target has not been found
				if (!targets[i].found) {
					
					// Check if target has a mask and if that mask has touch enabled
					if (targets[i].mask && !targets[i].mask.touchDisabled) {
						
						// If touching the mask
						if (targets[i].mask.pointCollisionDetection(point)) {

							// Mark selected target as active
							activeTarget = targets[i].mask;
							
							// Notify the mask
							targets[i].mask.touchStart(point);
							
							// Don't check anymore targets
							break;
						}	
					}
					
					// If touching the target
					if (targets[i].pointCollisionDetection(point)) {

						// Mark selected target as active
						activeTarget = targets[i];
						
						// If the state is drag then reset state to hidden
						if (activeTarget.getState() === "DRAG") {
							activeTarget.touchEnd(point);
							activeTarget.setState("HIDDEN");
						}
						
						// Notify the target
						targets[i].touchStart(point);
						
						// Don't check anymore targets
						break;
					}
				}
			}
		}
		
		if (!activeTarget) {
			WM.logStats({
			    event: "na.click",
			    title: that.gameTitle,
			    id: that.gameId,
			    params : {
			    	coordinates: point.x + ", " + point.y
			    }
			});
		}
	};
	
	// Fired when user swipes
	//
	that.touchMove = function (point) {
		var i,
			now = + new Date();
			
		lastUserInputTime = now;
		
		if (!that.paused && that.controller.dragging) {
			
			timeoutActive = false;
		
			// If touch event started on a target
			if (activeTarget) {
			
				if (activeTarget.name.toUpperCase() === "MASK") {
				
					if (!activeTarget.target.found) {
				
						activeTarget.getStates()[activeTarget.getState()].ctx = that.contexts.maskDrag;
						activeTarget.dirty = true;
						
						// Notify the target
						activeTarget.touchMove(point);
	
					}
				} else if (activeTarget.name.toUpperCase() === "TARGET") {
			
					if (!activeTarget.found) {

						if (activeTarget.getState() === "HIDDEN") {
							activeTarget.setState("DRAG");
							activeTarget.touchStart(point);
							
							WM.logStats({
							    event: "object.pickup",
							    title: that.gameTitle,
							    id: that.gameId
							});
						}
						
						// Notify the target
						activeTarget.touchMove(point);
					}
				}
			}
		}
	};
	
	// Fired when user releases
	//
	that.touchEnd = function (point) {
		var i,
			now = + new Date();
		
		lastUserInputTime = now;

		
		if (activeTarget) {
		
			if (activeTarget.getState() === "DRAG") {
				WM.logStats({
				    event: "object.drop",
				    title: that.gameTitle,
				    id: that.gameId,
				    params: {
				    	coordinates: point.x.toFixed(0) + ", " + point.y.toFixed(0)
				    }
				});
			}
		
			if (activeTarget.name.toUpperCase() === "MASK") {
			
				activeTarget.getStates()[activeTarget.getState()].ctx = that.contexts.mask;
				activeTarget.dirty = true;

				// If the target has not been found
				if (!activeTarget.target.found) {

					// If mask has touch enabled
					if (!activeTarget.touchDisabled) {
						
						//// If touching the mask
						//if (activeTarget.pointCollisionDetection(point)) {
							
							// If the mask has been moved or found on touch property is true
							if (activeTarget.getDistanceDragged() || activeTarget.foundOnTouch) {
							
								// Notify the target
								activeTarget.touchEnd(point);
								
								// Set target to found
								markTargetFound(activeTarget.target);
								
							} else if (activeTarget.wiggleOnTouch) {
								
								// Wiggle the target
								activeTarget.target.wiggle();
								
								// Notify the target
								activeTarget.touchEnd(point);	
							}
						//}		
					}
				}
				// Notify the target
				activeTarget.touchEnd(point);
			
			} else if (activeTarget.name.toUpperCase() === "TARGET") {

				// If the target has not been found
				if (!activeTarget.found) {
					
					// If touching the target	
					if (activeTarget.pointCollisionDetection(point)) {

						// Notify the target
						activeTarget.touchEnd(point);	
						
						// Set target to found
						markTargetFound(activeTarget);
					}
				}
				if (activeTarget.getState() === "DRAG") {
					activeTarget.touchEnd(point);
					activeTarget.setState("HIDDEN");
				}
				activeTarget = null;
			}
		}
	};
	
	that.gameTitle = "Hide and Seek";
	that.gameId = "HideSeek";

	// Create canvases
	that.addCanvas("hidden");
	that.addCanvas("mask");
	that.addCanvas("found");
	that.addCanvas("drag");
	that.addCanvas("maskDrag");
	that.addCanvas("payoff");
	that.addCanvas("transition");
	
	that.init();
	
	return that;
}

