/*
 * LittleLives, by Dan Krusi for airbit AG (www.airbit.ch)
 * Zurich, Switzerland, October 2008. Version 1
 * 
 * LittleLives is a very simple side-scrolling game written for
 * mShell 3.0. It takes advantage of m's new object-oriented
 * programming support, such as classes and inheritance. It is
 * designed to show the power and speed of mShell on Symbian
 * mobile devices, and furthermore the creative results a small
 * m script can produce.
 *
 * The game is built up around the Object class which contains
 * two methods process() and paint(). The process() method is
 * responsible for processing the objects logics, such as 
 * movement and simple AI. The paint() method is responsible for
 * drawing the object on screen, including it's rotation and
 * viewport offset. For each frame in the game, every living
 * (active) object is processed and painted once.
 *
 * The arrow keys on the mobile device control an on-screen
 * cursor. The object .tadpole will always follow this cursor.
 * Furthermore, moving the cursor off the edge of the screen
 * will scroll the viewport.
 *
 * There are no real events in the game; each object is
 * responsible for figuring out its own events. 
 *
 * Please feel free to modify this script to your wishes and
 * needs. Feedback is always appreciated: dan.kruesi@airbit.ch.
 */
use graph;
use array;
use ui;
use math;
use time;
//use accel;

// Forward declarations
function getAngle(x, y, destX, destY) forward;
function rndInt(min, max = null) forward;
function getObjectsOfType(type) forward;
function getRndObjectOfType(type) forward;
function addLetterText(x, y, size, text) forward;

// Global constants					Optimal value
WATERPLANT_CLUSTERS_MIN = 5; 		// 5
WATERPLANT_CLUSTERS_MAX = 6; 		// 6
WATERPLANT_PER_CLUSTER_MIN = 2; 	// 2
WATERPLANT_PER_CLUSTER_MAX = 6; 	// 6
FOOD_CLUSTERS_MIN = 5; 				// 5
FOOD_CLUSTERS_MAX = 20; 			// 20
FOOD_PER_CLUSTER_MIN = 2; 			// 2
FOOD_PER_CLUSTER_MAX = 10; 			// 10
TADPOLES_MIN = 5; 					// 5
TADPOLES_MAX = 10; 					// 20
TADPOLE_STARTING_SIZE = 0.05; 		// 0.05
TADPOLE_STARTING_TAILSIZE = 0.12; 	// 0.12
TADPOLE_ENERGY_MAX = 1.0; 			// 1.0
RANDOM_RIPPLE_THRESHOLD = 20; 		// 20
MAP_WIDTH = 5.0; 					// 5.0
MAP_HEIGHT = 5.0; 					// 5.0
TARGET_FRAME_TIME = 1;				// 20
FOOD_ENERGY_RATIO = 1;				// 1
FOOD_SIZE_RATIO = 0.08;				// 0.08
FOOD_TAILSIZE_RATIO = 0.06;			// 0.06
RADIO_WAVE_DISTANCE = 0.4;			// 0.4
RADIO_WAVE_ENERGY = 0.02;			// 0.02
FISH_MIN = 2; 						// 2
FISH_MAX = 4; 						// 4
FISH_SIZE = 0.15; 					// 0.15
FISH_TAILSIZE = 0.22;				// 0.22
FISH_FINSIZE = 0.20;				// 0.20
GAME_STATE_SPLASH = 0;				
GAME_STATE_PLAYING = 1;
GAME_STATE_GAMEOVER = 2;
GAME_STATE_FINISHED = 3;

/*
 * View - used by most objects during thier paint() method
 * for thier on-screen offset. This class does not offer any
 * functionality (yet).
 */
class View
	
	x;
	y;
	
	function init(x,y)
		this.x = x;
		this.y = y;
	end;
	
end;
view : View = View(0,0);

/*
 * Map - contains all the objects within the game. objects is the
 * superset of backgroundObjects, gameObjects and guiObjects. The
 * objects are organized in such a way for easy and quick layering.
 */
class Map
	
	width;
	height;
	objects;
	backgroundObjects;
	gameObjects;
	guiObjects;

	function init(width, height)
		this.width = width;
		this.height = height;
		objects = [];
		backgroundObjects = [];
		gameObjects = [];
		guiObjects = [];
	end;
	
end;
map : Map = Map(0,0);

/*
 * Object - the core class of the game. Every entity must extend
 * this class. The two methods process() and paint() must be
 * implemented by these subclasses. These methods are called on
 * all living objects on every frame.
 */
class Object
	
	x;
	y;
	speed;
	type;		// The object type, should correspond to the class
	layer;		// Drawing layer, either "background", "game" or "gui"
	age;		// The age, incremented on every process()
	dead;		// false when the object's die() method has been called.
				// Use this instead of object = null comparison.
	size;		// The size, used for collision detection...
	
	/*
	 * Initializes the object. Subclasses should call this prior
	 * to thier own initialization.
	 * @param type	The type of object, i.e. class name. Used for
	 *              reflection...
	 * @param layer	The layer on which the object is draw on. Either
	 *              "background", "game" or "gui".
	 */
	function init(type, layer)
		this.type = type;
		this.layer = layer;
		speed = 0.0;
		x = 0.0;
		y = 0.0;
		size = 0.0;
		age = 0;
		dead = false;
		
		// Insert self into the correct object arrays...
		array.insert(.map.objects, .len(.map.objects), this);
		if layer = null or layer = "game" then
			array.insert(.map.gameObjects, .len(.map.gameObjects), this);
		elsif layer = "background" then
			array.insert(.map.backgroundObjects, .len(.map.backgroundObjects), this);
		elsif layer = "gui" then
			array.insert(.map.guiObjects, .len(.map.guiObjects), this);
		end; 
	end;
	
	/*
	 * Processes the current object. Subclasses should call this
	 * method prior to doing thier own processing. process() of 
	 * each object is called exactly once during every frame.
	 */
	function process() 
		age++;
	end;
	
	/*
	 * Draws the current object. paint() of 
	 * each object is called exactly once during every frame.
	 */
	function paint() end;
	
	/*
	 * Kills the object in the game. The object is remove from
	 * the map according to which layer it belongs on.
	 */
	function die()
		// Remove self from the correct object arrays...
		array.remove(.map.objects, array.index(.map.objects, this));
		if layer = null or layer = "game" then
			array.remove(.map.gameObjects, array.index(.map.gameObjects, this));
		elsif layer = "background" then
			array.remove(.map.backgroundObjects, array.index(.map.backgroundObjects, this));
		elsif layer = "gui" then
			array.remove(.map.guiObjects, array.index(.map.guiObjects, this));
		end; 
		dead = true;
	end;
	
	/*
	 * Moves the object in the given direction (angle) for the
	 * given distance
	 * @param angle			The absolute direction
	 * @param distance		The distance to move
	 */
	function move(angle, distance)
		offsetX = math.cos(angle*math.pi/180) * distance;
		offsetY = math.sin(angle*math.pi/180) * distance;
		x += offsetX;
		y += offsetY;
	end;
    
	/*
	 * Checks whether an object is on screen or not. This method
	 * compares only by using a box calculation based on size.
	 * @return	true if the object is on screen, false otherwise.
	 */
    function isOnScreen()
    	if layer = "gui" or (x + size > -.view.x and x - size < -.view.x + 1.0 and y + size > -.view.y and y - size < -.view.y + 1.0) then
    		return true;
    	else
    		return false;
    	end;
    end;
	
end;

/*
 * Debug Object - used for displaying a series of debug information
 * on screen.
 */
class DebugObject is Object
   
   	text;	// Complete text buffer to display...
   	
    function init()
    	super.init("debug", "gui");
    	text = "";
    end;
    
	/*
	 * Adds the given text to the series of debug information to
	 * display. The text is removed after every frame.
	 * @param text	The text to add.
	 */
    function print(text)
    	this.text = this.text + text + " ";
    end;
	
	function paint()
		graph.font(["Arial", 16, false, false]);
		graph.text(0, 0, text);
		text = "";
	end;
	
end;
debug : DebugObject = DebugObject();

/*
 * Test Object - object used for testing. Bounces back-and-forth
 * on screen. This object does not adhere to the view.
 */
class TestObject is Object
    
    speedX;
    speedY;
    
	/*
	 * @param x		Starting x coordinate
	 * @param y		Starting y coordinate
	 * @param speed	Movement speed
	 */
    function init(x,y,speed)
    	super.init("test", "gui");
    	this.x = x;
    	this.y = y;
    	this.speed = speed;
    	speedX = speed;
    	speedY = speed;
    end;
    
    function process()
    	super.process();
		if x > 1.0 then speedX = -speed; end;
		if x < 0 then speedX = speed; end;
		if y > 1.0 then speedY = -speed; end;
		if y < 0 then speedY = speed; end;
		x += speedX;
		y += speedY;
	end;
	
	function paint()
		graph.text(x, y, "Test Object");
	end;
	
end;

/*
 * Cursor Object - displays a cross-hair cursor on screen. Used
 * for influencing the .tadpole movement as well as controlling
 * the viewport position.
 */
class Cursor is Object
    
    SPEED_STEP;
    BORDER_SIZE;
    MOVE_SPEED;
    
    speedX;
    speedY;
    
    function init(x, y)
		super.init("cursor", "gui");

		SPEED_STEP = 0.002;
		BORDER_SIZE = 0.05;
		MOVE_SPEED = 0.03;

    	this.x = x;
    	this.y = y;
    	size = 0.06;
    	speedX = 0.0;
    	speedY = 0.0;
    	
    end;
    
    function process()
    	super.process();
    	
    	// Process speed
    	if speedX > 0.0 then 
    		speedX = speedX - SPEED_STEP;
    		if speedX < 0.0 then speedX = 0.0; end;
    	elsif speedX < 0.0 then 
    		speedX = speedX + SPEED_STEP;
    		if speedX > 0.0 then speedX = 0.0; end;
    	end;
    	if speedY > 0.0 then 
    		speedY = speedY - SPEED_STEP;
    		if speedY < 0.0 then speedY = 0.0; end;
    	elsif speedY < 0.0 then 
    		speedY = speedY + SPEED_STEP;
    		if speedY > 0.0 then speedY = 0.0; end;
    	end;
    	
    	// Do movement
    	if x + speedX > 0.0 + BORDER_SIZE and x + speedX < 1.0 - BORDER_SIZE then x += speedX; end;
    	if y + speedY > 0.0 + BORDER_SIZE and y + speedY < 1.0 - BORDER_SIZE then y += speedY; end;
    	
    	// Move view with cursor...
    	if x < BORDER_SIZE*2 then .view.x -= speedX;
    	elsif x > 1.0-(BORDER_SIZE*2) then .view.x -= speedX; end;
    	if y < BORDER_SIZE*2 then .view.y -= speedY;
    	elsif y > 1.0-(BORDER_SIZE*2) then .view.y -= speedY; end;
	end;
	
	function paint()
		graph.alpha(0.5);
		graph.line(x, y-(size/2), x, y+(size/2));
		graph.line(x-(size/2), y, x+(size/2), y);
	end;
	
	function moveLeft() speedX = -MOVE_SPEED; end;
	function moveRight() speedX = MOVE_SPEED; end;
	function moveUp() speedY = MOVE_SPEED; end;
	function moveDown() speedY = -MOVE_SPEED; end;
	
	/*
	 * Get's the cursors position on the map.
	 * @return The x coordinate of the cursor.
	 */
	function mapX() return x - .view.x; end;

	/*
	 * Get's the cursors position on the map.
	 * @return The y coordinate of the cursor.
	 */
	function mapY() return y - .view.y; end;
	
end;
cursor : Cursor = Cursor(0, 0);

/*
 * Ripple Object - mimicks a single ripple moving through the
 * water. Eventually fades away when reaching maxSize.
 */
class Ripple is Object
    
    maxSize;
    
	/*
	 * @param x			The x coordinate of the epicenter.
	 * @param y			The y coordinate of the epicenter.
	 * @param maxSize	The maximum size the ripple will reach
	 *					before dissappearing...
	 */
    function init(x, y, maxSize)
    	super.init("ripple", "background");
    	this.x = x;
    	this.y = y;
    	this.maxSize = maxSize;
    	size = 0.0;
    end;
    
    function process()
    	super.process();
		if size > maxSize then 
			die();
		else
			size += 0.002;
		end;
	end;
	
	function paint()
		graph.pen(graph.white);
		if size/maxSize > 1.0 then graph.alpha(1.0); else graph.alpha(size/maxSize); end;
		graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size);
	end;
	
end;

/*
 * Beam Object - A horizontal and vertical beam flashes and
 * dissappears slowly at the given x and y coordinates...
 */
class Beam is Object
    
    ALPHA_STEP;
    
    alpha;
    
    function init(x, y)
    	super.init("beam", "gui");
    	this.x = x;
    	this.y = y;
    	alpha = 0.0;
    	ALPHA_STEP = 0.1;
    end;
    
    function process()
    	super.process();
		if alpha + ALPHA_STEP > 1.0 then 
			die();
		else
			alpha += ALPHA_STEP;
		end;
	end;
	
	function paint()
		graph.pen(graph.white);
		graph.alpha(alpha);
		graph.line(.view.x + x, 0, .view.x + x, 1); // Vertical line
		graph.line(0, .view.y + y, 1, .view.y + y); // Horizontal line
	end;
	
end;

/*
 * Energy Object - visual representation of energy consumption.
 * Displayed when a tadpole eats some food and gains energy.
 */
class Energy is Object
    
    ALPHA_STEP;
    SIZE_STEP;
    
    alpha;
    color;
    
	/*
	 * @param x			The x coordinate of the epicenter.
	 * @param y			The y coordinate of the epicenter.
	 * @param size      Starting size of the energy wave.
	 * @param color     Wave color.
	 */
    function init(x, y, size, color)
    	super.init("energy", "gui");
    	
    	ALPHA_STEP = 0.1;
    	SIZE_STEP = 0.01;
    	
    	this.x = x;
    	this.y = y;
    	alpha = 0.0;
    	this.size = size;
    	this.color = color;
    end;
    
    function process()
    	super.process();
    	size += SIZE_STEP;
		if alpha + ALPHA_STEP > 1.0 then 
			die();
		else
			alpha += ALPHA_STEP;
		end;
	end;
	
	function paint()
		graph.pen(color);
		graph.alpha(alpha);
		graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size);
	end;
	
end;

/*
 * Water Plant Object - a simple floating water plant. This object
 * drifts slowly in the water and is designed to be in a cluster.
 */
class WaterPlant is Object
    
    MAX_SPEED;
    MAX_DRIFT;
    
    speedX;
    speedY;
    origX;
    origY;
    color;
    
	/*
	 * Sets a new drifting speed in a random direction.
	 */
    function setNewDrift()
    	speedX = (-math.random() + math.random()) * MAX_SPEED;
    	speedY = (-math.random() + math.random()) * MAX_SPEED; 
    end;
    
    function init(x, y)
    	super.init("waterplant", "background");
    	
    	MAX_SPEED = 0.001;
    	MAX_DRIFT = 0.10;
    	
    	this.x = x;
    	this.y = y;
    	origX = x;
    	origY = y;
    	color = 0x5fb3ae;
    	size = 0.4 + math.random()*0.4;
    	setNewDrift();
    end;
    
    function process()
    	super.process();
    	if x + speedX < origX - MAX_DRIFT or x + speedX > origX + MAX_DRIFT or y + speedY < origY - MAX_DRIFT or y + speedY > origY + MAX_DRIFT then 
    		setNewDrift();
    	else
			x += speedX;
			y += speedY;
		end;
	end;
	
	function paint()
		graph.pen(false);
		graph.brush(color);
		graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size);
	end;
	
end;

/*
 * Food Object - a randomly drifting piece of food which can
 * be eaten by tadpoles. When a food is eaten, the tadpole
 * gains in energy and size...
 */
class Food is Object
    
    MAX_SPEED;
    MAX_DRIFT;
    
    speedX;
    speedY;
    origX;
    origY;
    color;
    
	/*
	 * Sets a new drifting speed in a random direction.
	 */
    function setNewDrift()
    	speedX = (-math.random() + math.random()) * MAX_SPEED;
    	speedY = (-math.random() + math.random()) * MAX_SPEED; 
    end;
    
    function init(x, y)
    	super.init("food", "background");
    	
    	MAX_SPEED = 0.008;
    	MAX_DRIFT = 1.0;
    	
    	this.x = x;
    	this.y = y;
    	origX = x;
    	origY = y;
    	color = graph.yellow;
    	size = 0.01 + math.random()*0.02;
    	setNewDrift();
    end;
    
    function process()
    	super.process();
    	
    	// Move randomly...
    	if x + speedX < origX - MAX_DRIFT or x + speedX > origX + MAX_DRIFT or y + speedY < origY - MAX_DRIFT or y + speedY > origY + MAX_DRIFT then 
    		setNewDrift();
    	else
			x += speedX;
			y += speedY;
		end;
	end;
	
	function paint()
		graph.pen(false);
		graph.brush(color);
		graph.ellipse(.view.x + x-(size/2), .view.y + y-(size/2), size, size);
	end;
	
end;

/*
 * Letter Objects - represents a single character floating
 * randomly onscreen. Use the function addLetterText(x,y,text)
 * to display a series of these letters onscreen.
 */
class Letter is Object
    
    MAX_SPEED;
    ALPHA_STEP;
    SIZE_STEP;
    
    speedX;
    speedY;
    color;
    alpha;
    letter;
    
	/*
	 * Sets a new drifting speed in a random direction.
	 */
    function setNewDrift()
    	speedX = (-math.random() + math.random()) * MAX_SPEED;
    	speedY = (-math.random() + math.random()) * MAX_SPEED; 
    end;
    
	/*
	 * @param x			The x coordinate of the letter.
	 * @param y			The y coordinate of the letter.
	 * @param size		Size of the letter font in pixels.
	 * @param letter	Character to display.
	 */
    function init(x, y, size, letter)
    	super.init("letter", "gui");
    	
    	MAX_SPEED = 0.002;
    	ALPHA_STEP = 0.03;
    	SIZE_STEP = 2;
    	
    	this.x = x;
    	this.y = y;
    	this.letter = letter;
    	this.size = size;
    	color = graph.white;
    	alpha = 0.0;
    	speedX = (-math.random() + math.random()) * MAX_SPEED;
    	speedY = (-math.random() + math.random()) * MAX_SPEED; 
    end;
    
    function process()
    	super.process();
    	
    	size += SIZE_STEP;
		if alpha + ALPHA_STEP > 1.0 then 
			die();
		else
			alpha += ALPHA_STEP;
		end;
		x += speedX;
		y += speedY;
	end;
	
	function paint()
		graph.pen(color);
		graph.brush(color);
		graph.alpha(alpha);
		graph.font(["Arial", size, true, false]);
		graph.text(x, y, letter);
	end;
	
end;

/*
 * Water Animal - super class for any moving water animal.
 * This class provides the functionality for moving through the
 * water, wiggling tails and/or fins, navigating to a specific
 * point, and accelerating/decelerating...
 */
class WaterAnimal is Object

	SPEED_STEP;
	WIGGLE_STEP_SLOW;
	WIGGLE_STEP_FAST;
	WIGGLE_MAX;
	ROTATION_STEP;
	RIPPLE_THRESHOLD;
	
	wiggleOffset;		// The current wiggle offset of a tail/fin
	rotation;			// Absolute rotation
    currentSpeed;		// The speed at which the object moves
    wantedSpeed;		// The requested speed
    destX;				// If set, the animal moves to these coordinates
    destY;
    energy;				// Current energy of the animal. Increased when the animal eats food.
    wiggleStep;			// The current wiggle step. This is faster as the animal moves, slower as it is idle.
    color;
    maxSpeed;
    
	/*
	 * Sets the wiggle step to the given value without changing
	 * the wiggle direction
	 * @param step		Wiggle step amout.
	 */
    function setWiggleStep(step)
		if wiggleStep > 0 then
			wiggleStep = step;
		else
			wiggleStep = -step;
		end;
	end;
    
	/*
	 * @param type		The object type.
	 * @param x			The starting x coordinate.
	 * @param y			The starting y coordinate.
	 * @param size		Main body size.
	 * @param color		Main body color.
	 * @param maxSpeed	The object maximum speed when moving.
	 */
    function init(type, x, y, size, color, maxSpeed)
    	super.init(type, "game");
    	
    	SPEED_STEP = 0.001;
    	ROTATION_STEP = 5;
    	WIGGLE_STEP_SLOW = 1;
    	WIGGLE_STEP_FAST = 4;
    	WIGGLE_MAX = 10;
    	RIPPLE_THRESHOLD = 6;
    	
    	this.x = x;
    	this.y = y;
    	this.size = size;
    	this.color = color;
    	this.maxSpeed = maxSpeed;
    	wiggleOffset = 0;
    	rotation = 0;
    	currentSpeed = 0.0;
    	wantedSpeed = 0.0;
    	destX = 0.0;
    	destY = 0.0;
    	energy = 0.0;
    	wiggleStep = WIGGLE_STEP_SLOW;
    end;
    
    function process()
    	super.process();
    	
    	// Steer to wanted speed...
    	if currentSpeed < wantedSpeed then
    		currentSpeed += SPEED_STEP;
    	elsif currentSpeed > wantedSpeed then
    		currentSpeed -= SPEED_STEP;
    	end;
    	
    	// Are we at our destination?
    	if x >= destX - maxSpeed and x <= destX + maxSpeed and y >= destY - maxSpeed and y <= destY + maxSpeed then
    		wantedSpeed = 0;
    		destX = x;
    		destY = y;
    		setWiggleStep(WIGGLE_STEP_SLOW);
    	else
    		rotation = .getAngle(x,y,destX,destY);
    	end;
    	
    	// Are we moving?
    	if currentSpeed # 0 then
			// Cause ripple and move...
			if age % RIPPLE_THRESHOLD = 0 then ripple:Ripple = Ripple(x, y, 0.1); end;
    		move(rotation, currentSpeed);
    	end;
    	
    	// Wiggle tail
    	wiggleOffset += wiggleStep;
		if (wiggleStep > 0 and wiggleOffset > WIGGLE_MAX) or (wiggleStep < 0 and wiggleOffset < -WIGGLE_MAX) then
			wiggleStep = -wiggleStep;
		end;
    	
	end;
	
	/*
	 * Moves the object to the given point. This will not happen
	 * immediately, but rather causes an animated effect for the
	 * object over the forthcoming frames where the object slowly
	 * progresses to the direction of the given coordinates.
	 * @param x		Destination x coordinate.
	 * @param y		Destination y coordinate.
	 */
	function moveToPoint(x, y)
    	setWiggleStep(WIGGLE_STEP_FAST);
		destX = x;
		destY = y;
		wantedSpeed = maxSpeed;
	end;
	
end;

/*
 * Tadpole Water Animal - represents a small tadpole. Tadpole is a WaterAnimal,
 * thus it automatically knows how to move through the water and
 * wiggle it's tail.
 */
class Tadpole is WaterAnimal

	TAIL_ANGLE;
	
	tailSize;
	targetFood : Food;
    
	/*
	 * @param x			The starting x coordinate.
	 * @param y			The starting y coordinate.
	 * @param size		Body size.
	 * @param tailSize	Tail size.
	 * @param color		Body and tail color.
	 */
    function init(x, y, size, tailSize, color)
    	super.init("tadpole", x, y, size, color, 0.02);
    	
    	TAIL_ANGLE = 20;
    	
		this.tailSize = tailSize;
		targetFood = null;
    	
    end;
    
    function process()
    	super.process();
		
		// Can we eat something?
		foodObjects = getObjectsOfType("food");
		for food : Food in foodObjects do
			if food.x >= x - size and food.x <= x + size and food.y >= y - size and food.y <= y + size then
	    		energy += food.size * .FOOD_ENERGY_RATIO;
	    		size += food.size * .FOOD_SIZE_RATIO;
	    		tailSize += food.size * .FOOD_TAILSIZE_RATIO;
	    		energyObject : Energy = Energy(food.x, food.y, food.size, food.color);
	    		food.die();
	    	end;
		end;
    	
	end;

	/*
	 * Sets a new food target, making the tadpole slowly move
	 * towards the food object...
	 */
	function moveToFood()
		targetFood = getRndObjectOfType("food");
	end;
	
	function paint()
		graph.pen(false);
		graph.brush(color);
		graph.circle(.view.x + x-(size/2), .view.y + y-(size/2), size);
		graph.ellipse(.view.x + x-(tailSize/2), .view.y + y-(tailSize/2), tailSize, tailSize, rotation+180+wiggleOffset-(TAIL_ANGLE/2), rotation+180+wiggleOffset+(TAIL_ANGLE/2));
	end;
	
end;
tadpole : Tadpole = Tadpole(0,0,0,0,0);


/*
 * AI Tadpole Water Animal - a tadpole which can move by itself. This object
 * either moves to a random location, to our own tadpole, or to a
 * targetted random piece of food.
 */
class AITadpole is Tadpole

	MOVE_THRESHOLD;
	MAX_MOVE_DISTANCE;
	
	/*
	 * @param x			The starting x coordinate.
	 * @param y			The starting y coordinate.
	 * @param size		Body size.
	 * @param tailSize	Tail size.
	 * @param color		Body and tail color.
	 */
    function init(x, y, size, tailSize, color)
    	super.init(x, y, size, tailSize, color);
    	
    	MOVE_THRESHOLD = 20;
		MAX_MOVE_DISTANCE = 1.0;x
		
    end;
    
    function process()
    	super.process();
    	
    	// Move to a random location
    	if currentSpeed = 0 and rndInt(0, MOVE_THRESHOLD) = 0 then
    		rnd = rndInt(1,8);
    		if rnd = 1 then
    			// Move to random location
	    		offsetX = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random());
	    		offsetY = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random());
	    		moveToPoint(x + offsetX, y + offsetY);
    		elsif rnd = 2 then
    			// Move to our tadpole
    			moveToPoint(.tadpole.x, .tadpole.y);
    		else
    			// Move to a food piece if we can find one
    			if targetFood = null or targetFood.dead = true then targetFood = getRndObjectOfType("food"); end;
				if targetFood # null then moveToPoint(targetFood.x, targetFood.y); end;
    		end;
    	end;
	end;
	
end;

/*
 * Fish Water Animal - a large fish water animal which can move through
 * the water by itself. This object moves to either a random location
 * or towards our own tadpole.
 */
class Fish is WaterAnimal

	TAIL_ANGLE;
	FIN_ANGLE;
	MOVE_THRESHOLD;
	MAX_MOVE_DISTANCE;
	
	tailSize;
	finSize;

	/*
	 * @param x			The starting x coordinate.
	 * @param y			The starting y coordinate.
	 * @param size		Body size.
	 * @param tailSize	Tail size.
	 * @param finSize	Fin size.
	 * @param color		Body, tail and fin color.
	 */
    function init(x, y, size, tailSize, finSize, color)
    	super.init("fish", x, y, size, color, 0.005);
    	
    	TAIL_ANGLE = 40;
    	FIN_ANGLE = 18;
    	MOVE_THRESHOLD = 20;
		MAX_MOVE_DISTANCE = 1.0;
    	
    	this.tailSize = tailSize;
    	this.finSize = finSize;
    end;
    
    function process()
    	super.process();
    	
    	// Move to a random location
    	if currentSpeed = 0 and rndInt(0, MOVE_THRESHOLD) = 0 then
    		rnd = rndInt(1,4);
    		if rnd = 1 then
    			// Move to random location
	    		offsetX = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random());
	    		offsetY = -MAX_MOVE_DISTANCE + (MAX_MOVE_DISTANCE*2*math.random());
	    		moveToPoint(x + offsetX, y + offsetY);
    		else
    			// Move to our tadpole
    			moveToPoint(.tadpole.x, .tadpole.y);
    		end;
    	end;
    	
    	// Can we eat something?
		eatableObjects = getObjectsOfType("tadpole");
		for tadpole : Tadpole in eatableObjects do
			if tadpole.x >= x - size/2 and tadpole.x <= x + size/2 and tadpole.y >= y - size/2 and tadpole.y <= y + size/2 then
	    		energyObject : Energy = Energy(tadpole.x, tadpole.y, tadpole.size, tadpole.color);
	    		tadpole.die();
	    		if tadpole = .tadpole then
					addLetterText(0.28, 0.6, 30, "game");
					addLetterText(0.28, 0.45, 30, "over");
				end;
	    	end;
		end;
	end;
	
	function paint()
		graph.pen(false);
		graph.brush(color);
		graph.circle(.view.x + x-(size/2), .view.y + y-(size/2), size);
		graph.ellipse(.view.x + x-(tailSize/2), .view.y + y-(tailSize/2), tailSize, tailSize, rotation+180+wiggleOffset-(TAIL_ANGLE/2), rotation+180+wiggleOffset+(FIN_ANGLE/2));
		graph.ellipse(.view.x + x-(finSize/2), .view.y + y-(finSize/2), finSize, finSize, rotation+240-(FIN_ANGLE/2), rotation+240+(FIN_ANGLE/2));
		graph.ellipse(.view.x + x-(finSize/2), .view.y + y-(finSize/2), finSize, finSize, rotation+120-(FIN_ANGLE/2), rotation+120+(FIN_ANGLE/2));
	end;
	
end;

/*
 * Energy Bar Object - A thin horizontal bar at the screen bottom
 * which displays our tadpoles current energy. The bar dissappears
 * after a short moment in order to enable a fuller gaming experience.
 */
class EnergyBar is Object
    
    ALPHA_STEP;
    BORDER_SPACE;
    HEIGHT;
    
    alpha;
    lastEnergy;
    tadpole : Tadpole;
	percent;
	width;

	/*
	 * @param tadpole		A Tadpole Object which the energy bar
	 *						is locked too. Whenever the energy of
	 *						this tadpole changes, the energy bar will
	 *						display itself briefly and then fade out.
	 */
    function init(tadpole : Tadpole)
    	super.init("energybar", "gui");
    	
    	ALPHA_STEP = 0.05;
    	BORDER_SPACE = 0.05;
    	HEIGHT = 0.05;
    	
    	this.tadpole = tadpole;
    	alpha = 1.0;
    	lastEnergy = 0;
		width = 1.0 - 2 * BORDER_SPACE;
		percent = 0.0;
    	
    end;
    
    function process()
    	super.process();
    	
    	if lastEnergy # tadpole.energy then 
    		lastEnergy = tadpole.energy;
    		alpha = 0.0;
			percent = (tadpole.energy / .TADPOLE_ENERGY_MAX);
			if percent > 1.0 then percent = 1.0; end;
    	end;
    	
		if alpha + ALPHA_STEP < 1.0 then 
			alpha += ALPHA_STEP;
		end;
	end;
	
	function paint()
		if alpha + ALPHA_STEP < 1.0 then 
			graph.pen(graph.white);
			graph.alpha(alpha);
			graph.brush(graph.white);
			graph.rect(BORDER_SPACE, BORDER_SPACE, width * percent, HEIGHT); // Fill
			graph.brush(false);
			graph.rect(BORDER_SPACE, BORDER_SPACE, width, HEIGHT); // Outline
		end;
		
	end;
	
end;

/*
 * Radio Wave Object - a set of small radio waves which are
 * dispersed at the given angle from the origin.
 */
class RadioWave is Object
    
    SIZE_STEP;
    ARC_ANGLE;
    WAVE_OFFSET;
    WAVE_NUMBER;
    
    maxSize;
    angle;
    sender : Tadpole
    collided;
    
	/*
	 * @param x			The x coordinate of the epicenter.
	 * @param y			The y coordinate of the epicenter.
	 * @param maxSize   Maximum size of the radio wave, i.e. the distance of the wave.
	 * @param angle     Angle, or direction of the waves.
	 * @param sender    The sending tadpole object. Used to make sure we don't interact with our own radio waves...
	 */
    function init(x, y, maxSize, angle, sender)
    	super.init("radiowave", "background");
    	
    	SIZE_STEP = 0.05;
    	ARC_ANGLE = 30;
    	WAVE_OFFSET = 0.1;
    	WAVE_NUMBER = 3;
    	
    	this.x = x;
    	this.y = y;
    	this.maxSize = maxSize;
    	this.angle = angle;
    	this.sender = sender;
    	size = 0.0;
    	collided = false;
    end;
    
    function process()
    	super.process();
		
		if size > maxSize then 
			die();
		else
			size += SIZE_STEP;
		end;
		
		// Are we reaching a tadpole?
		if collided = false then
			for tadpole : Tadpole in getObjectsOfType("tadpole") do
				if tadpole # sender and tadpole.x >= x - size/2 and tadpole.x <= x + size/2 and tadpole.y >= y - size/2 and tadpole.y <= y + size/2 then
		    		// Mark as collided and tell the tadpole to go swim to some food...
					collided = true;
					tadpole.targetFood = null;
					tadpole.moveToPoint(.map.width*math.random(), .map.height*math.random());
		    	end;
			end;
		end;
		
	end;
	
	function paint()
		graph.brush(false);
		graph.pen(graph.green);
		// Just draw a series of arcs...
		if size/maxSize > 1.0 then graph.alpha(1.0); else graph.alpha(size/maxSize); end;
		for i = 1 to WAVE_NUMBER do
			waveSize = size + (WAVE_OFFSET * i);
			if waveSize > 0 then
				graph.ellipse(.view.x + x-(waveSize/2), .view.y + y-(waveSize/2), waveSize, waveSize, angle - ARC_ANGLE/2, angle + ARC_ANGLE/2);
			end;
		end;
	end;
	
end;

/*
 * rndInt Global Function
 * @param min		The minimum returnable value.
 * @param max		The maximum returnable value. 
 *                  If null or omitted then min is used as the 
 *                  maximum while the minimum is assumed to be 0.
 * @return          Random whole number between min and max.
 */
function rndInt(min, max = null)
	if max = null then
		return (math.random()*10000) % min;
	else
		return min + ((math.random()*10000) % (max-min));
	end;
end;

/*
 * getObjectsOfType Global Function
 * @param type		The requested type of object (string).
 * @return			Returns an array containing all the matching objects.
 */
function getObjectsOfType(type)
	if type = null then
		return .map.objects;
	else
		objectsOfType = [];
		for obj : Object in .map.objects do
			if obj.type = type then
				array.insert(objectsOfType, .len(objectsOfType), obj);
			end;
		end;
		return objectsOfType;
	end;
end;

/*
 * getRndObjectOfType Global Function
 * @param type		The requested type of object (string).
 * @return          Returns a random matching object.
 */
function getRndObjectOfType(type)
	objectsOfType = getObjectsOfType(type);
	if .len(objectsOfType) = 0 then return null; end;
	rndIndex = rndInt(0, .len(objectsOfType));
	return objectsOfType[0];
end;

/*
 * getAngle Global Function
 * @param x			The source x coordinate.
 * @param y			The source y coordinate.
 * @param destX		The destination x coordinate.
 * @param destY		The destination y coordinate.
 * @return			Returns the relative angle in degrees between the source and destination.
 */
function getAngle(x, y, destX, destY)
	if x = destX and y = destY then return 0; end;
	delta = 0.0;
	if destX >= x and destY >= y then
		delta = math.atan( (destY-y) / (destX-x) ) * (180 / math.pi);
	elsif destX >= x and destY < y then
		delta = 360 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi);
	elsif destX < x and destY < y then
		delta = 180 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi);
	elsif destX < x and destY >= y then
		delta = 180 + math.atan( (destY-y) / (destX-x) ) * (180 / math.pi);
	end;
	return delta;
end;

/*
 * resetGraph Global Function - Resets the graph so that it is
 * ready for drawing a object.
 */
function resetGraph()
	graph.alpha(0);
	graph.pen(graph.black);
	graph.brush(false);
end;

/*
 * addLetterText Global Function - Adds a string text onscreen
 * using Letter objects. 
 * @param x			The x starting coordinate.
 * @param y			The y starting coordinate.
 * @param size		Starting font size in pixels of letters.
 * @param text		String of text to show.
 */
function addLetterText(x, y, size, text)
	for i = 0 to len(text)-1 do
		letter : Letter = Letter(x + (0.1)*i, y, 30, substr(text, i, 1));
	end;
end;

// Init game objects
ui.busy("Loading...");
gameState = GAME_STATE_SPLASH;
map = Map(MAP_WIDTH, MAP_HEIGHT);
view = View(-map.width/2 + 0.5, -map.width/2 + 0.5);
debug = DebugObject();
cursor = Cursor(0.5, 0.5);
tadpole = Tadpole(map.width/2, map.height/2, TADPOLE_STARTING_SIZE, TADPOLE_STARTING_TAILSIZE, graph.black);
energyBar : EnergyBar = EnergyBar(tadpole);
addLetterText(0.12, 0.6, 30, "little");
addLetterText(0.2, 0.3, 30, "lives");

// Create some random water plants
for waterPlantClusterIndex = 0 to rndInt(WATERPLANT_CLUSTERS_MIN, WATERPLANT_CLUSTERS_MAX) do
	waterPlantClusterX = math.random() * map.width;
	waterPlantClusterY = math.random() * map.height;
	for plantIndex = 0 to rndInt(WATERPLANT_PER_CLUSTER_MIN, WATERPLANT_PER_CLUSTER_MAX) do
		plantX = math.random() * 0.5;
		plantY = math.random() * 0.5;
		clusterWaterPlant:WaterPlant = WaterPlant(waterPlantClusterX + plantX, waterPlantClusterY + plantY);
	end;
end;

// Create some random food...
for foodClusterIndex = 0 to rndInt(FOOD_CLUSTERS_MIN, FOOD_CLUSTERS_MAX) do
	foodClusterX = math.random() * map.width;
	foodClusterY = math.random() * map.height;
	for plantIndex = 0 to rndInt(FOOD_PER_CLUSTER_MIN, FOOD_PER_CLUSTER_MAX) do
		foodX = math.random() * 0.1;
		foodY = math.random() * 0.1;
		clusterFood:Food = Food(foodClusterX + foodX, foodClusterY + foodY);
	end;
end;

// Create some other tadpoles...
for tadpoleIndex = 0 to rndInt(TADPOLES_MIN, TADPOLES_MAX) do
	aiTadpole = AITadpole(math.random() * map.width, math.random() * map.height, TADPOLE_STARTING_SIZE, TADPOLE_STARTING_TAILSIZE, graph.black);
end;

// Create some big pad fish...
for tadpoleIndex = 0 to rndInt(FISH_MIN, FISH_MAX) do
	aiFish = Fish(math.random() * map.width, math.random() * map.height, FISH_SIZE, FISH_TAILSIZE, FISH_FINSIZE, 0x660000);
end;

// Test objects
//aiTadpole : AITadpole = AITadpole(0.7, 0.7, 0.05, 0.12, graph.black);
//clusterFood : Food = Food(0.1, 0.1);
//clusterFoodx : Food = Food(0.5, 0.1);
//letter : Letter = Letter(0.5,0.5,30,"L");
//addLetterText(0.1, 0.5, 30, "text");
//fish = Fish(map.width/2- 0.5, map.height/2-0.5, FISH_SIZE, FISH_TAILSIZE, FISH_FINSIZE, 0x660000);

// Init drawing canvas
graph.scale(true);
graph.bg(0x5f97b3);
ui.keys(false);
stop = false;
accelEnabled = false;
accelOffsetX = 0;
accelOffsetY = 0;
accelTrigger = 30;
ui.busy();
ui.msg("Use the arrow keys to move the cursor which your tadpole always follows.", "Little Lives - 1/4");
ui.msg("Eat the floating food before your friends do in order to grow larger and gain energy.","Little Lives - 2/4");
ui.msg("Press the GO key to speak with other tadpoles in order to mislead them from food.","Little Lives - 3/4");
ui.msg("Press the * key at any time to quit. Happy Swimming!", "Little Lives - 4/4");
//if accel.version # null then 
//	accelEnabled = ui.confirm("Your phone contains an accelerometer. Would you like to use this feature?", "Little Lives");
//	a = accel.get();
//	accelOffsetX = a["x"];
//	accelOffsetY = a["y"];
//end;

// Application loop
gameState = GAME_STATE_PLAYING;
while(stop = false) do
	
	// Debug counters...
	frameTimeBegin = time.get();
	processedObjects = 0;
	paintedObjects = 0;
	ui.idletime(true);
	
	// Process input...
	cmd = ui.cmd(0);
	if cmd # null then
		if cmd = 42 then // * key
			// Exit game loop
			stop = true;
		elsif cmd = 35 then // # key 
			// Test key...
			//rndFood : Food = getRndObjectOfType("food");
			//tadpole.moveToPoint(rndFood.x, rndFood.y);
		elsif cmd = ui.downkey and tadpole.dead = false then 
			cursor.moveDown();
			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
		elsif cmd = ui.upkey and tadpole.dead = false then 
			cursor.moveUp();
			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
		elsif cmd = ui.leftkey and tadpole.dead = false then 
			cursor.moveLeft();
			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
		elsif cmd = ui.rightkey and tadpole.dead = false then 
			cursor.moveRight();
			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
		elsif cmd = ui.gokey and tadpole.dead = false then
			// Send a readio beam in direction of cursor
			beam : Beam = Beam(cursor.mapX(), cursor.mapY());
			waveAngle = getAngle(tadpole.x, tadpole.y, cursor.mapX(), cursor.mapY());
			radioWave : RadioWave = RadioWave(tadpole.x, tadpole.y, RADIO_WAVE_DISTANCE, waveAngle, tadpole);
		end;
	elsif accelEnabled = true then
//  		a = accel.get();
//  		if a["x"]-accelOffsetX < -accelTrigger then 
//			cursor.moveDown();
//			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
//		elsif a["x"]-accelOffsetX > accelTrigger then 
//			cursor.moveUp();
//			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
//		end;
//		if a["y"]-accelOffsetY > accelTrigger then 
//			cursor.moveLeft();
//			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
//		elsif a["y"]-accelOffsetY < -accelTrigger then 
//			cursor.moveRight();
//			tadpole.moveToPoint(cursor.mapX(), cursor.mapY());
//		end;
	end;
	
	// Other processing
	if rndInt(RANDOM_RIPPLE_THRESHOLD) = 0 then
		randomRipple : Ripple = Ripple(-.view.x + math.random(), -.view.y + math.random(), math.random()*0.3);
	end;
	if gameState = GAME_STATE_PLAYING and energyBar.percent >= 1.0 then
		gameState = GAME_STATE_FINISHED;
		ui.msg("Congratulations! You are now a full grown tadpole!\n\nHappy Swimming!", "Little Lives");
	end;
	
	// Process objects
	for obj : Object in map.objects do
		obj.process();
		processedObjects++;
	end;
	
	// Paint all objects, one layer at a time
	graph.clear();
	for backgroundObject : Object in map.backgroundObjects do
		if backgroundObject.isOnScreen() = true then
			resetGraph();
			backgroundObject.paint();
			paintedObjects++;
		end;
	end;
	for gameObject : Object in map.gameObjects do
		if gameObject.isOnScreen() = true then
			resetGraph();
			gameObject.paint();
			paintedObjects++;
		end;
	end;
	for guiObject : Object in map.guiObjects do
		if guiObject.isOnScreen() = true then
			resetGraph();
			guiObject.paint();
			paintedObjects++;
		end;
	end;
	
	// Show debug infos...
	//debug.print(processedObjects);
	//debug.print(paintedObjects);
	
	// Show paint changes
	graph.show();
	
	// Take a break if we were fast enough
	frameTimeEnd = time.get();
	if frameTimeEnd-frameTimeBegin < TARGET_FRAME_TIME * 1000 then
		sleep( TARGET_FRAME_TIME - (frameTimeEnd-frameTimeBegin)/1000 );
	end;
end;