function Entity() {
	this.remove = false;
	this.skip = false;
	this.marked = false;
	this.type = "ERROR";
	this.addedToManager = false;
	this.update = updateEntity;

	// unique id
	this.id = entity_id;	
	entity_id ++;
}


function updateEntity() {
}


// possibly move this elsewhere
function Effect(_x, _y) {
	this.addedToManager = false;
	this.remove = false;
	this.timer = 30;
	this.x = _x;
	this.y = _y;
	this.IMAGE = 150;
	this.sprite = 0;
	this.update = updateEffect;
	this.draw = drawEntitySimple;		
	effectManager.addEntity(this); // maybe make a makeEffect function which adds it?
}


function updateEffect() {
	this.timer --;
	if (this.timer <= 0)
		this.remove = true;
}


function format_number(input) {
	var string = input.toString(16);
	string = string.toUpperCase();
	return string;
}


function pad_string(input, length) {
	while (input.length < length)
		input += " ";
	// crop if over length
	if (input.length > length)
		input = input.substring(0, length);
	return input;
}


var ANIM4 = new Sequence();
ANIM4.base = 0;
ANIM4.addSprite(0, 4);
ANIM4.addSprite(1, 4);
ANIM4.addSprite(2, 4);
ANIM4.addSprite(3, 4);
ANIM4.looping = false;

var ANIM6 = new Sequence();
ANIM6.base = 0;
ANIM6.addSprite(0, 2);
ANIM6.addSprite(1, 2);
ANIM6.addSprite(2, 2);
ANIM6.addSprite(3, 2);
ANIM6.addSprite(4, 2);
ANIM6.addSprite(5, 2);
ANIM6.looping = false;

var ANIM8 = new Sequence();
ANIM8.base = 0;
for (var i = 0; i < 8; i ++)
	ANIM8.addSprite(i, 2);


function EFFCT(_x, _y) {
	var effect = new Effect(_x, _y);
	effect.sequence = ANIM4;
	effect.DELAY = 1;
	effect.timer = 0;
	effect.update = function() {
		effect.sprite = effect.sequence.getFrame(effect.timer);
		effect.timer ++;
		if (effect.timer > effect.sequence.sprite.length)
			effect.remove = true;
	};
	
	return effect;
}


// a sequence of actions or animation
function Sequence() {
	this.base = 0;
	this.sprite = []; // list of sprite indices for each frame
	this.looping = true;
	
	// gets the frame which should be displayed at the given time
	this.getFrame = function(time) {
		if (this.looping) // wrap
			return this.base + this.sprite[time % this.sprite.length];
		else // cap at last frame
			return this.base + this.sprite[Math.min(this.sprite.length - 1, time)];
	}
	
	// adds a sprite or action which is repeated for the specified number of frames
	this.addSprite = function(newSprite, duration) {
		for (var i = 0; i < duration; i ++)
			this.sprite.push(newSprite);
	}
}


function drawEntitySimple() {
	drawSprite(this.IMAGE + this.sprite, this.x - 0.5, this.y - 0.5, offscreenCanvas);
}


function drawEntityFlipX() {
	drawSpriteFlipped(this.IMAGE + this.sprite, this.x - 0.5, this.y - 0.5, this.flipX, 1.0, offscreenCanvas);
}


function drawentity4section() {
	for (var x = 0; x < 2; x ++) {
		for (var y = 0; y < 2; y ++) {
			var xoffset = this.flipX * ((x == 1) ? 0.5 : -0.5);
			drawSpriteFlipped(this.IMAGE + this.sprite + x + y * 32, 
			this.x - 1.5 + xoffset, 
			this.y - 2 + y, 
			this.flipX, 1.0, offscreenCanvas);
		}
	}
}


function EntityManager() {
	this.entity = [];
	this.update = updateEntityManager;
	this.draw = drawEntityManager;
	this.addEntity = addEntity;
	this.removeEntities = removeEntities;
	this.removeAllButMarked = removeAllButMarked;
	this.notifySignal = notifySignal;
	this.notifyEnterCell = notifyEnterCellEntityManager;
	this.notifyKeyPress = notifyKeyPressEntityManager;
	this.notifyActionPress = notifyActionPressEntityManager;
	this.notifyActionHold = notifyActionHoldEntityManager;
	this.notifyEnterRoom = notifyEnterRoomEntityManager;
	this.notifyRoomExit = notifyRoomExitEntityManager;
	this.notifyRoomClear = notifyRoomClearEntityManager;
}


// update all, simple
function updateEntityManager() {
	var current;
	for (var i = 0; i < this.entity.length; i ++) {
		current = this.entity[i];
		if (current.skip != true) {
			current.update();
		}
		current.CLOCK ++;
	}
}


function notifyEnterRoomEntityManager() {
	console.log("entering room " + roomX + ", " + roomY);
		
	var current;
	
	for (var i = 0; i < this.entity.length; i ++) {
		current = this.entity[i];
		if (current.RENTR) {
			current.RENTR();
			console.log(current.LABEL + " sent room enter notification");
		}
	}
}


function notifyRoomExitEntityManager() {
	var current;
	var notifyCount = 0;
	for (var i = 0; i < this.entity.length; i ++) {
		current = this.entity[i];
		if (current.REXIT) {
			current.REXIT();
			notifyCount ++;			
		}
	}
}


function notifyRoomClearEntityManager() {
	var current;
	var notifyCount = 0;
	for (var i = 0; i < this.entity.length; i ++) {
		current = this.entity[i];
		if (current.RMCLR) {
			current.RMCLR();
			notifyCount ++;			
		}
	}
	console.log(notifyCount + " entities notified of room clear");
}


function drawEntityManager() {
	var current;
	for (var i = 0; i < this.entity.length; i ++) {
		current = this.entity[i];
		if (current.skip != true)
			current.draw();
	}
}


function addEntity(newEntity) {
	if (newEntity.addedToManager == false) {
		this.entity.push(newEntity);
		newEntity.addedToManager = true;
	}
	else
		console.log("attempted double add of entity");
}


function removeAllButMarked() {
	for (var i = 0; i < this.entity.length; i ++) {
		if (this.entity[i].marked == false)
			this.entity[i].remove = true;
			
	}
	this.removeEntities();
}


function removeEntities() {
	var i = 0;
	while (i < this.entity.length) {
		if (this.entity[i].remove == true) {
			this.entity.splice(i, 1);
		}
		else
			i ++;
	}
}


function testEntityManager() {
	var count = randomIntIn(3, 9);
	for (var i = 0; i < count; i ++)
		entityManager.entity.push(new Person(i, randomInt(map.width), randomInt(map.height)));
	for (var i = 0; i < count; i ++) {
		var parent = entityManager.entity[i];
		entityManager.entity.push(new House(parent.x, parent.y, i));
	}
}


function notifySignal(n) {
	for (var i = 0; i < this.entity.length; i ++) {
		var currentTarget = this.entity[i];
		if (currentTarget.notifySignal != undefined)
			currentTarget.notifySignal(n);
	}
}


function notifyEnterCellEntityManager(entity, x, y) {
	for (var i = 0; i < this.entity.length; i ++) {
		var currentTarget = this.entity[i];
		if (currentTarget.notifyEnterCell != undefined)
			currentTarget.notifyEnterCell(x, y); // ADD: pass in entity itself
	}
}


function notifyKeyPressEntityManager(code) {
	for (var i = 0; i < this.entity.length; i ++) {
		var currentTarget = this.entity[i];
		if (currentTarget.notifyKeyPress != undefined)
			currentTarget.notifyKeyPress(code);
	}	
}


// sent out once in the frame when the action button is initially pressed
function notifyActionPressEntityManager(code) {
	for (var i = 0; i < this.entity.length; i ++) {
		var currentTarget = this.entity[i];
		if (currentTarget.notifyActionPress != undefined)
			currentTarget.notifyActionPress(code);
	}	
}


// sent out continuously when action button is held
function notifyActionHoldEntityManager(code) {
	for (var i = 0; i < this.entity.length; i ++) {
		var currentTarget = this.entity[i];
		if (currentTarget.notifyActionHold != undefined)
			currentTarget.notifyActionHold(code);
	}	
}