var BaseSprite = Class.create({
	DIR_STOP: 0,
	DIR_LEFT: -1,
	DIR_RIGHT: 1,
	DIR_DOWN: -1,
	DIR_UP: 1,

	topCollision: false,
	rightCollision: false,
	bottomCollision: false,
	leftCollision: false,

	canvas: null,
	collisionEngine: null,
	velocity: null,
	direction: null,
	id: null,
	x: null,
	y: null,
	width: null,
	height: null,
	mass: 10,

	initialize: function(id, canvas) {
		this.id = id
		this.canvas = canvas;
		this.element = $(id);
		this.direction = new Vector(0, 0);
		this.velocity = new Vector(0, 0);
		canvas.addSprite(this);
		this.collisionEngine = new CollisionEngine(this, []);
		
		topCollision = false;
		rightCollision = false;
		bottomCollision = false;
		leftCollision = false;

		
		if(this.element.style.top == "") {
			this.y = this.canvas.getHeight() - this.element.offsetTop;
		} else {
			this.y = this.canvas.getHeight() - parseInt(this.element.style.top);
		}

		if(this.element.style.left == "") {
			this.x = this.element.offsetLeft;
		} else {
			this.x = parseInt(this.element.style.left);
		}
		
		if(this.element.style.width == "") {
			this.width = this.element.offsetWidth;
		} else {
			this.width = parseInt(this.element.style.width);
		}
		
		if(this.element.style.height == "") {
			this.height = this.element.offsetHeight;
		} else {
			this.height = parseInt(this.element.style.height);
		}
	},

	render: function() {
		// This is a static Sprite, so render doesn't do anything	
	},

	getX: function() {
		return this.x;
	},

	getY: function() {
		return this.y;
	},

	setX: function(x) {
		this.x = x;
		this.element.style.left = x + "px";
	},

	setY: function(y) {
		this.y = y;
		this.element.style.top = (this.canvas.getHeight() - y) + "px";
	},

	getWidth: function() {
		return this.width;
	},

	getHeight: function() {
		return this.height;
	},

	onTopCollision: function(sprite) {
	
	},

	onRightCollision: function(sprite) {
	
	},

	onBottomCollision: function(sprite) {
	
	},

	onLeftCollision: function(sprite) {
	
	},
	
	killSprite: function(sprite) {
		sprite.element.parentNode.removeChild(sprite.element);
		this.canvas.removeSprite(sprite);
		sprite = null;
	},
	
	score: function(val) {
		var newElement = document.createElement('div');
		
		newElement.className = "sprite score-sprite";
		newElement.innerHTML = val;
		
		newElement.style.left = this.element.style.left;
		newElement.style.top = this.element.style.top;

		this.element.parentNode.appendChild(newElement);
		var scoreSprite = new ScoreSprite(newElement, this.canvas);
	}
});

var GroundSprite = Class.create(BaseSprite, {
	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'pipe-sprite', 'block-sprite', 'tower-sprite', 'score-sprite' ]);
	}
});

var TowerSprite = Class.create(BaseSprite, {
	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'ground-sprite', 'tower-sprite', 'score-sprite' ]);
	}
});

var PipeSprite = Class.create(BaseSprite, {
	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'ground-sprite', 'score-sprite' ]);
	}
});

var BlockSprite = Class.create(BaseSprite, {
	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'ground-sprite', 'block-sprite', 'score-sprite' ]);
	}
});

var AnimatedSprite = Class.create(BaseSprite, {
	element: null,
	gravityCoefficient: null,
	frame: 0,
	animationState: this.DIR_STOP,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.direction = new Vector(0, 0);
		this.velocity = new Vector(4, 0);
		
		this.collisionEngine = new CollisionEngine(this, []);
		this.gravityCoefficient = Math.sqrt(this.canvas.gravity) / this.mass;
	},

	flipBackground: function(pos) {
		this.element.style.backgroundPosition = "-" + (pos * this.getWidth()) + "px 0px";
		this.animationState = pos;
	},

	render: function() {
		if(!this.bottomCollision) {
			this.setY(this.getY() + (this.direction.y * this.velocity.y));
			
			newVy = Math.floor((this.direction.y * this.velocity.y) - this.gravityCoefficient);
			
			if (newVy == 0) {
				this.direction.y = this.DIR_STOP;
				this.velocity.y = this.DIR_STOP;
			} else {
				this.direction.y = newVy / Math.abs(newVy);
				this.velocity.y = Math.abs(newVy);
			}
		}

		if(!((this.collisionLeft && this.direction.x != this.DIR_RIGHT) || this.collisionRight && this.direction.x != this.DIR_LEFT)) {
			this.setX(this.getX() - (this.direction.x * this.velocity.x));
		}

		if(this.getX() + this.getWidth() > this.canvas.getWidth() || this.getX() < 0) {
			if(this.direction.x == this.DIR_LEFT) {
				this.leftCollision = true;
				this.onLeftCollision(null);
			} else if(this.direction.x == this.DIR_RIGHT) {
				this.rightCollision = true;
				this.onRightCollision(null);
			}
		}
		
		this.animate();
	},
	
	onLeftCollision: function(sprite) {
		this.frame = 0;
		this.animationState = this.WALK_RIGHT;
		this.direction.x = this.DIR_RIGHT;
			
		if(sprite != null) {
			this.setX(sprite.getX() - this.getWidth());
		}
	},
	
	onRightCollision: function(sprite) {
		this.frame = 0;
		this.animationState = this.WALK_LEFT;
		this.direction.x = this.DIR_LEFT;
			
		if(sprite != null) {
			this.setX(sprite.getX() + sprite.getWidth());
		}
	},

	onBottomCollision: function(sprite) {
		this.direction.y = this.DIR_STOP;
		this.velocity.y = this.DIR_STOP;
		this.setY(sprite.getY() + this.getHeight());
	}
});

var ScoreSprite = Class.create(AnimatedSprite, {
	frame: 0,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new CollisionEngine(this, []);
		this.frame = 0;
		this.element.style.opacity = 1;
	},

	render: function() {
		if(this.frame == 10) {
			this.killSprite(this);
			return;
		}
		
		if(this.frame < 10) {
			this.element.style.opacity = 1 - (this.frame / 10);
			this.setY(this.getY() + 1);
			this.frame++;
		}
	},

	onTopCollision: function() {

	},

	onRightCollision: function() {

	},

	onBottomCollision: function() {

	},

	onLeftCollision: function() {

	}
});

var CoinSprite = Class.create(AnimatedSprite, {
	frame: 0,
	collected: false,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new CollisionEngine(this, []);
		this.frame = 0;
	},

	render: function() {
		if(this.collected) {
			if(this.frame == 6) {
				this.killSprite(this);
				return;
			}
		
			if(this.frame < 6) {
				this.setY(this.getY() + 16);
				this.frame++;
			}
		}
	},

	onTopCollision: function() {
	
	},

	onRightCollision: function() {

	},

	onBottomCollision: function() {

	},

	onLeftCollision: function() {

	}
});


var BrickSprite = Class.create(AnimatedSprite, {
	origHeight: null,
	
	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'question-sprite', 'mushroom-sprite', 'score-sprite' ]);
		this.origHeight = this.getY();
	},

	render: function() {
		if(this.origHeight > this.getY()) {
			this.setY(this.origHeight);
			this.velocity.y = this.DIR_STOP;
			this.direction.y = this.DIR_STOP;
		}

		if(this.origHeight < this.getY()) {
			this.setY(this.getY() + (this.direction.y * this.velocity.y));
			
			newVy = Math.floor((this.direction.y * this.velocity.y) - this.gravityCoefficient);
			
			if (newVy == 0) {
				this.direction.y = this.DIR_STOP;
				this.velocity.y = this.DIR_STOP;
			} else {
				this.direction.y = newVy / Math.abs(newVy);
				this.velocity.y = Math.abs(newVy);
			}
		}
	},

	onBottomCollision: function(sprite) {
		if(sprite.element.hasClassName('character-sprite')) {
			if(sprite.element.hasClassName('super')) {
				this.killSprite(this);
			} else {
				this.direction.y = this.DIR_UP;
				this.velocity.y = 3;
				this.setY(this.getY() + 3);
			}
		}
	},

	onLeftCollision: function() {

	},

	onRightCollision: function() {

	}
});


var QuestionSprite = Class.create(BrickSprite, {
	FLASH_1: 0,
	FLASH_2: 1,
	FLASH_3: 2,

	doAnimate: true,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'brick-sprite', 'mushroom-sprite', 'score-sprite', 'coin-sprite' ]);
	},

	onBottomCollision: function($super, sprite) {
		if(sprite.element.hasClassName('character-sprite')) {
			if(this.element.hasClassName('mushroom-powerup')) {
				// Create a mushrrom object
				newElement = document.createElement('div');
				newElement.className = "sprite mushroom-sprite";
				newElement.style.left = this.element.style.left;
				newElement.style.top = this.element.style.top;

				this.element.parentNode.appendChild(newElement);
				
				var mushroom = new MushroomSprite(newElement, this.canvas);

				this.element.removeClassName('mushroom-powerup');
				this.score(100);

			} else if(this.element.hasClassName('coin')) {
				newElement = document.createElement('div');
				newElement.className = "sprite coin-sprite";
				newElement.style.left = this.element.style.left;
				newElement.style.top = this.element.style.top;

				this.element.parentNode.appendChild(newElement);
				
				var coin = new CoinSprite(newElement, this.canvas);
				coin.collected = true;
				this.element.removeClassName('coin');
				this.score(100);
			}
		}

		this.doAnimate = false;

		this.direction.y = this.DIR_UP;
		this.velocity.y = 3;
		this.setY(this.getY() + 3);
		
		this.element.removeClassName('question-sprite');
		this.element.addClassName('block-sprite');
		this.flipBackground(this.FLASH_1);
	},

	animate: function() {
		if(this.frame == 6) {
			this.flipBackground(this.FLASH_2);
		} else if(this.frame == 12) {
			this.flipBackground(this.FLASH_3);
		} else if(this.frame == 0) {
			this.flipBackground(this.FLASH_1);
		}

		this.frame++;
		
		if(this.frame > 18) {
			this.frame = 0;
		}
	},

	render: function($super) {
		if(this.doAnimate) {
			this.animate();
		}
		
		$super();
	}
});


var MushroomSprite = Class.create(AnimatedSprite, {
	GROWING: 0,
	GROWN: 1,

	lifeCycleState: this.GROWING,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		
		this.lifecycleState = this.GROWING;
		this.collisionEngine = new SquareCollisionEngine(this, [ 'question-sprite', 'block-sprite', 'ground-sprite', 'score-sprite' ]);

		this.frame = 0;
		
		this.direction.x = this.DIR_STOP;
		this.velocity.x = 0;

		this.direction.y = this.DIR_STOP;
		this.velocity.y = 0;
	},

	animate: function() {
		// Do nothing
	},

	render: function($super) {
		if(this.lifecycleState == this.GROWING) {
			if(this.frame * 4 >= this.getHeight()) {
				this.lifecycleState = this.GROWN;
				this.direction.y = this.DIR_STOP;
				this.velocity.y = 0;
				this.direction.x = this.DIR_LEFT;
				this.velocity.x = 4;
				
				this.collisionEngine = new SquareCollisionEngine(this, [ 'ground-sprite' ]);
			
			} else {
				this.setY(this.getY() + 4);
				this.frame++;
			}
		} else if(this.lifecycleState == this.GROWN) {
			// Remove all the ignores from the collision engine
			$super();
		}
	}
});

var GoombaSprite = Class.create(AnimatedSprite, {
	WALK_RIGHT: 0,
	WALK_RIGHT_2: 1,
	WALK_LEFT_2: 2,
	WALK_LEFT: 3,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.collisionEngine = new SquareCollisionEngine(this, [ 'ground-sprite', 'pipe-sprite', 'block-sprite', 'goomba-sprite', 'score-sprite' ]);
		this.direction.x = this.DIR_LEFT;
		this.velocity.x = 4;

		this.direction.y = this.DIR_STOP;
		this.velocity.y = 0;
	},

	animate: function() {
		if(this.direction.x == this.DIR_LEFT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_RIGHT_2);
			} else if(this.frame == 0) {
				this.flipBackground(this.WALK_RIGHT);
			}
		} else if(this.direction.x == this.DIR_RIGHT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_LEFT_2);
			} else if(this.frame == 0) {
				this.flipBackground(this.WALK_LEFT);
			}
		}

		this.frame++;
		
		if(this.frame > 10) {
			this.frame = 0;
		}
	}
});


var KoopaSprite = Class.create(AnimatedSprite, {
	WALK_RIGHT: 5,
	WALK_RIGHT_2: 4,
	TURN_RIGHT: 3,
	TURN_LEFT: 2,
	WALK_LEFT_2: 1,
	WALK_LEFT: 0,

	initialize: function($super, id, canvas) {
		$super(id, canvas);
		this.direction.x = this.DIR_RIGHT;
	},

	animate: function() {
		if(this.animationState == this.TURN_RIGHT && this.direction.x == this.DIR_LEFT) {
			if(this.frame == 5) {
				this.flipBackground(this.TURN_LEFT);
			}
		} else if(this.animationState == this.TURN_RIGHT && this.direction.x == this.DIR_RIGHT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_RIGHT);
			}

		} else if(this.animationState == this.TURN_LEFT && this.direction.x == this.DIR_RIGHT) {
			if(this.frame == 5) {
				this.flipBackground(this.TURN_RIGHT);
			}

		} else if(this.animationState == this.TURN_LEFT && this.direction.x == this.DIR_LEFT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_LEFT);
			}

		} else if(this.direction.x == this.DIR_LEFT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_RIGHT_2);
			} else if(this.frame == 0) {
				this.flipBackground(this.WALK_RIGHT);
			}
		} else if(this.direction.x == this.DIR_RIGHT) {
			if(this.frame == 5) {
				this.flipBackground(this.WALK_LEFT_2);
			} else if(this.frame == 0) {
				this.flipBackground(this.WALK_LEFT);
			}
		}
		this.frame++;
		
		if(this.frame > 10) {
			this.frame = 0;
		}
	}
});

var CharacterSprite = Class.create(AnimatedSprite, {
	WALK_RIGHT: 0,
	WALK_RIGHT_2: 1,
	RIGHT_JUMP: 2,
	RIGHT_LAND: 3,
	RIGHT_DUCK: 4,
	LEFT_DUCK: 5,
	LEFT_LAND: 6,
	LEFT_JUMP: 7,
	WALK_LEFT_2: 8,
	WALK_LEFT: 9,
	DYING: 10,
	DYING_2: 11,

	ducking: false,
	dying: false,
	superMode: false,

	initialize: function($super, id, canvas) {
		$super(id, canvas);

		this.velocity.x = 0;
		this.velocity.y = 8;
		this.direction.x = this.DIR_RIGHT;
		this.direction.y = this.DIR_DOWN;

		Event.observe(document, 'keypress', this.keypress.bindAsEventListener(this));
		Event.observe(document, 'keyup', this.keyup.bindAsEventListener(this));
		Event.observe(document, 'keydown', this.keydown.bindAsEventListener(this));
	},

	keypress: function(e) {
		return false;
	},

	keydown: function(e) {
		switch(e.keyCode) {
			case Event.KEY_UP:
				this.jump();
				break;
			case Event.KEY_DOWN:
				this.ducking = true;
				if(this.direction.x == this.DIR_LEFT) {
					this.flipBackground(this.LEFT_DUCK);
				} else {
					this.flipBackground(this.RIGHT_DUCK);
				}
				break;
			case Event.KEY_LEFT:
				this.velocity.x = 8;
				this.direction.x = this.DIR_LEFT;
				break;
			case Event.KEY_RIGHT:
				this.velocity.x = 8;
				this.direction.x = this.DIR_RIGHT;
				break;
		}
		return false;
	},
	
	keyup: function(e) {
		switch(e.keyCode) {
			case Event.KEY_UP:
				break;
			case Event.KEY_DOWN:
				this.ducking = false;
				if(this.direction.x == this.DIR_LEFT) {
					this.flipBackground(this.WALK_LEFT);
				} else {
					this.flipBackground(this.WALK_RIGHT);
				}
				break;
			case Event.KEY_LEFT:
				if(this.direction.x == this.DIR_LEFT) {
					this.velocity.x = 0;
				}
				break;
			case Event.KEY_RIGHT:
				if(this.direction.x == this.DIR_RIGHT) {
					this.velocity.x = 0;
				}
				break;
		}
		return false;

	},

	animate: function() {
		if(this.ducking) {
			return;
		}
		
		if(this.dying) {
			if(this.frame == 5) {
				this.flipBackground(this.DYING);

			} else if(this.frame == 0) {
				this.flipBackground(this.DYING_2);

			}
		} else if(this.direction.y == this.DIR_STOP) {
			if(this.velocity.x == this.DIR_STOP) {
				if(this.direction.x == this.DIR_LEFT) {
					this.flipBackground(this.WALK_LEFT);
				} else {
					this.flipBackground(this.WALK_RIGHT);
				}

			} else if(this.direction.x == this.DIR_LEFT) {
				if(this.frame == 5) {
					this.flipBackground(this.WALK_LEFT);

				} else if(this.frame == 0) {
					this.flipBackground(this.WALK_LEFT_2);

				}

			} else if(this.direction.x == this.DIR_RIGHT) {
				if(this.frame == 5) {
					this.flipBackground(this.WALK_RIGHT_2);
				} else if(this.frame == 0) {
					this.flipBackground(this.WALK_RIGHT);
				}
			}
		} else {
			if(this.direction.y == this.DIR_UP) {
				if(this.direction.x == this.DIR_LEFT) {
					this.flipBackground(this.LEFT_JUMP);
				} else {
					this.flipBackground(this.RIGHT_JUMP);
				}

			} else if(this.direction.y == this.DIR_DOWN) {
				if(this.direction.x == this.DIR_LEFT) {
					this.flipBackground(this.LEFT_LAND);

				} else {
					this.flipBackground(this.RIGHT_LAND);
				}
			}
		}
		
		this.frame++;
		
		if(this.frame > 10) {
			this.frame = 0;
		}
	},

	render: function() {
		if(!this.bottomCollision) {
			this.setY(this.getY() + (this.direction.y * this.velocity.y));
			newVy = Math.floor((this.direction.y * this.velocity.y) - this.gravityCoefficient);
			
			// This hack stops the jump action from occuring at the apex
			if(newVy == 0 && this.direction.y == this.DIR_UP) {
				newVy = -0.01;
			}

			if (newVy == 0) {
				this.direction.y = this.DIR_STOP;
				this.velocity.y = this.DIR_STOP;
			} else {
				this.direction.y = newVy / Math.abs(newVy);
				this.velocity.y = Math.abs(newVy);
			}
		}

		if(!((this.collisionLeft && this.direction.x != this.DIR_RIGHT) || this.collisionRight && this.direction.x != this.DIR_LEFT)) {
			this.setX(this.getX() + (this.direction.x * this.velocity.x));
		}

		this.animate();
		
		window.scrollTo(this.getX() - (window.innerWidth / 2), 0);

		if(this.getY() < 0) {
			if(this.dying) {
				this.canvas.stop();
				location.reload();
			} else {
				// We want the sprite to die immediately, regardless of whether it is super mario or not
				this.superMode = false;
				this.die();
			}
		}
	},

	jump: function() {
		if(this.bottomCollision) {
			this.bottomCollision = false;
			this.direction.y = this.DIR_UP;
			this.velocity.y = this.superMode ? 21 : 20;
			this.setY(this.getY() + this.velocity.y);
			this.render();
		}
	},

	die: function() {
		if(this.superMode) {
			this.shrink();
		} else {
			this.dying = true;
			this.flipBackground(this.DYING);
			this.direction.y = this.DIR_UP;
			this.velocity.y = 20;
			this.setY(this.getY() + this.velocity.y);
			// Unregister all sprites from the canvas, to stop them renderinga and collisding - this game is over, baby.
			this.canvas.sprites = new Array();
			this.canvas.addSprite(this);
			this.render();
		}
	},
	
	grow: function() {
		this.superMode = true;
		this.element.addClassName('super');
		
		this.width = 64;
		this.height = 64;

		this.element.style.width = this.width + "px";
		this.element.style.height = this.height + "px";
	},

	shrink: function() {
		this.superMode = false;
		this.element.removeClassName('super');
		
		this.width = 48;
		this.height = 48;

		this.element.style.width = this.width + "px";
		this.element.style.height = this.height + "px";
	},


	onLeftCollision: function(sprite) {
		if(this.leftCollision) {
			if(sprite.element.hasClassName('goomba-sprite')) {
				this.die();
			} else if(sprite.element.hasClassName('mushroom-sprite')) {
				this.killSprite(sprite);
				this.grow();
			} else {
				this.velocity.x = this.DIR_STOP;
				this.setX(sprite.getX() - this.getWidth());
			}
		}
	},
	
	onRightCollision: function(sprite) {
		if(this.rightCollision) {
			if(sprite.element.hasClassName('goomba-sprite')) {
				this.die();
			} else if(sprite.element.hasClassName('mushroom-sprite')) {
				this.killSprite(sprite);
				this.grow();
			} else {
				this.velocity.y = 5;
				this.velocity.x = this.DIR_STOP;
				this.setX(sprite.getX() + sprite.getWidth() + 1);
			}
		}
	},

	onTopCollision: function(sprite) {
		if(this.topCollision) {
			if(sprite.element.hasClassName('mushroom-sprite')) {
				if(sprite.lifecycleState == sprite.GROWN) {
					this.killSprite(sprite);
					this.grow();
				}
			} else {
				this.direction.y = this.DIR_DOWN;
				this.setY(sprite.getY() - this.getHeight() - 1);
			}
		}
	},
	
	onBottomCollision: function(sprite) {
		if(this.bottomCollision) {
			try {
				if(sprite.element.hasClassName('goomba-sprite')) {
					this.score(1000);
					this.killSprite(sprite);
					this.jump();
				} else if(sprite.element.hasClassName('mushroom-sprite')) {
					this.score(100);
					this.killSprite(sprite);
					this.grow();
				} else {
					this.direction.y = this.DIR_STOP;
					this.velocity.y = this.DIR_STOP;
					this.setY(sprite.getY() + this.getHeight() - 1);
				}
			} catch(e) {

			}
		}
	}
});

