/src/cell.ts
import Storable from './storable';
import { ID, generateID } from './id';
import { GameEvent, GameEventProperties, GameEventType } from './event';
import { GameObject, GameObjectProperties, GameObjectType } from './object';
import { GameAgent } from './agent';
import { Direction } from './direction';
import gs from './gamestate';

/* Cell

A Cell is the basic spatial unit of the map.  It has a title and a basic
description and it contains any number of Objects, Events, and Agents.

Upon entering a cell, the title and description are presented to the player,
any ENTER events are fired, and the available Actions of all present Objects
are enumerated and displayed.

Upon leaving a cell, any EXIT events are fired.

*/

export interface CellProperties {
    x: number
    y: number
    id: ID
    title: string
    description: string
    events: GameEventProperties[]
    objects: ID[]
    exits: [boolean, boolean, boolean, boolean]
    // agents are not saved
}

export class Cell extends Storable {
    id: ID
    x: number
    y: number
    title: string
    description: string
    exits = [true, true, true, true];

    events: Map<ID, GameEvent>
    objects: Map<ID, GameObject>
    agents: Map<ID, GameAgent>
    navigable: boolean
    saveProperties = ['x', 'y', 'id', 'title', 'description', 'exits']

    constructor(x: number, y: number) {
        super();
        this.id = generateID();
        this.x = x;
        this.y = y;
        this.title = '';
        this.description = '';

        this.events = new Map();
        this.objects = new Map();
        this.navigable = false;
    }

    addObject(o: GameObject) {
        // ensure that this object is not already in the gamestate
        if (this.objects.has(o.id)) {
            throw new Error('Object already in cell: ' +  o.id)
        }
        this.objects.set(o.id, o);
    }

    removeObject(o: GameObject) {
        if (this.objects.has(o.id)) {
            this.objects.delete(o.id);
        }
    }

    addEvent(e: GameEvent) {
        if (this.events.has(e.id)) {
            throw new Error('Event already in cell: ' + e.id);
        }
        this.events.set(e.id, e);
    }

    removeEvent(e: GameEvent) {
        if (this.events.has(e.id)) {
            this.events.delete(e.id);
        }
    }

    canMove(d: Direction): boolean {
        return this.exits[d];
    }

    store() {
        return Object.assign(<CellProperties>super.store(), {
            objects: Array.from(this.objects).map( ([id, obj]) => id),
            events: Array.from(this.events).map( ([id, e]) => e.store()),
        });
    }

    load(properties: CellProperties) {
        // x and y are ignored here - they are used only to address the
        // particular cell and load this data in
        this.id = properties.id;
        this.title = properties.title;
        this.description = properties.description;

        // At this point the objects should already be loaded in gamestate
        this.objects = new Map();
        for (let id of properties.objects) {
            if (!gs.objects.has(id)) {
                throw new Error('Object not found in gamestate:' + id);
            }
            this.objects.set(id, gs.objects.get(id));
        }

        this.events = new Map();
        for (let event of properties.events) {
            const ge = new GameEvent();
            ge.load(event);
            this.events.set(event.id, ge);
        }

        // We assume that since this cell was saved, it is navigable
        this.navigable = true;
    }
}