/// <reference path="../Room.ts" />
/// <reference path="RoomRandom.ts" />

interface MapCoordinates {
    x : number;
    y : number;
    z : number;
}

class RoomRandomMap {
    private positionTable : {[x : number] : {[y : number] : RoomRandom}} = {};
    private roomMap = new Map<Room, Array<number>>();
    public lowestX = 0;
    public lowestY = 0;
    public highestX = 0;
    public highestY = 0;
    public limitsInvalid = false;

    /**
     * This is a count of how many connections are actually available, assuming rooms that can connect through some direction.
     * @type {number}
     */
    public availableConnections : Array<Array<MapCoordinates>>;

    /**
     * This is a count of rooms since the last cache clear. Used to mean more, but now is just a count of how many rooms were placed from the current region.
     * @type {number}
     */
    public rooms;

    /**
     * Clears the current room cache and creates a new one that is empty
     */
    public emptyCache () {
        this.availableConnections = [];
        Room.DIRECTIONS.forEach(() => {
            this.availableConnections.push([]);
        });
        this.rooms = 0;
    }

    public getAvailableConnections () {
        return this.availableConnections.reduce((previousValue, currentValue, currentIndex, array) => {
            return previousValue + (currentValue.length);
        }, 0);
    }

    public removeFromCache (coordinatesToRemove : MapCoordinates, direction : number) {
        let directionArray = this.availableConnections[direction];
        for (let k = 0, coordinates = directionArray[k]; coordinates != undefined; coordinates = directionArray[++k]) {
            if (coordinates.x == coordinatesToRemove.x && coordinates.y == coordinatesToRemove.y) {
                directionArray.splice(k, 1);
                return;
            }
        }
    }

    public addToCache (coordinatesToAdd : MapCoordinates, direction : number) {
        let directionArray = this.availableConnections[direction];
        for (let k = 0, coordinates = directionArray[k]; coordinates != undefined; coordinates = directionArray[++k]) {
            if (coordinates.x == coordinatesToAdd.x && coordinates.y == coordinatesToAdd.y) {
                return;
            }
        }
        directionArray.push(coordinatesToAdd);
    }

    public getAnyFromCache (direction : number) : RoomRandom {
        let array = this.availableConnections[direction];
        if (array.length > 0) {
            let randomIndex = Math.floor(Math.random() * (array.length));
            let randomCoordinates = array[randomIndex];
            if (randomCoordinates != undefined) {
                return this.getRoom(randomCoordinates.x, randomCoordinates.y);
            }
        }
    }

    public isFree (x : number, y : number) {
        return (this.positionTable[x] == undefined || this.positionTable[x][y] === undefined);
    }

    public block (x : number, y : number) {
        if (this.isFree(x, y)) {
            if (this.positionTable[x] == undefined) {
                this.positionTable[x] = {};
            }
            this.positionTable[x][y] = null;
            this.updateCacheOnPosition(x, y);
            this.updateLimits(x, y);
        }
    }

    public updateAllLimits () {
        this.highestX = 0;
        this.highestY = 0;
        this.lowestX = 0;
        this.lowestY = 0;
        for (let x in this.positionTable) {
            for (let y in this.positionTable[x]) {
                if (this.positionTable[x][y] != undefined && this.positionTable[x][y] != null) {
                    this.updateLimits(parseInt(x), parseInt(y));
                }
            }
        }
        this.limitsInvalid = false;
    }

    public updateLimits (x, y) {
        if (x > this.highestX) this.highestX = x;
        if (y > this.highestY) this.highestY = y;
        if (y < this.lowestY) this.lowestY = y;
        if (x < this.lowestX) this.lowestX = x;
    }

    public map (room : RoomRandom, x : number, y : number) {
        if (this.positionTable[x] == undefined) {
            this.positionTable[x] = {};
        }
        this.positionTable[x][y] = room;
        this.roomMap.set(room, [x, y]);

        this.updateCacheOnPosition(x, y);

        // If we ever want to draw maps, this will be useful
        this.updateLimits(x, y);

        room.placed = true;
        room.lastMap = this;

        this.rooms++;
    }

    public unmap (x : number, y : number) {
        if (this.positionTable[x] != undefined && this.positionTable[x][y] != undefined) {
            this.roomMap.delete(this.positionTable[x][y]);
            delete (this.positionTable[x][y]);
            this.rooms--;
            this.updateCacheOnPosition(x, y);
            this.limitsInvalid = true;
        }
    }

    private updateCacheOnPosition (x : number, y : number) {
        let coordinates = [x, y, 0];
        let coordinatesMap = <MapCoordinates> {x : x, y : y};
        let coordinatesBlocked = !this.isFree(x, y);
        let coordinatesRoom = this.getRoom(x, y);

        Room.DIRECTIONS.forEach(direction => {
            let oppositeDirection = OppositeDirection[Direction[direction]];
            let shifted = Room.shift(coordinates, direction);
            let shiftedMap = <MapCoordinates> {x : shifted[0], y : shifted[1]};
            let shiftedBlocked = !this.isFree(shifted[0], shifted[1]);
            let shiftedRoom = this.getRoom(shifted[0], shifted[1]);

            if (coordinatesRoom != undefined) {
                if (shiftedBlocked) {
                    this.removeFromCache(coordinatesMap, direction);
                } else if (coordinatesRoom.isConnectableOn(direction)) {
                    this.addToCache(coordinatesMap, direction);
                }
            } else {
                this.removeFromCache(coordinatesMap, direction);
            }

            if (shiftedRoom != undefined) {
                if (coordinatesBlocked) {
                    this.removeFromCache(shiftedMap, oppositeDirection);
                } else if (shiftedRoom.isConnectableOn(oppositeDirection)) {
                    this.addToCache(shiftedMap, oppositeDirection);
                }
            } else {
                this.removeFromCache(shiftedMap, oppositeDirection);
            }
        });
    }

    public getRoom (x : number, y : number) {
        if (this.positionTable[x] != undefined) {
            if (this.positionTable[x][y] != null) {
                return this.positionTable[x][y];
            }
        }
        return undefined;
    }

    public getCoordinates (room : Room) : Array<number> {
        return this.roomMap.get(room);
    }

    public getRoomCount () {
        return this.roomMap.size;
    }

    public getWidth () {
        return this.highestX - this.lowestX;
    }

    public getHeight () {
        return this.highestY - this.lowestY;
    }

    public static PREFERRED_GROWTH_HORIZONTAL = 0;
    public static PREFERRED_GROWTH_VERTICAL = 1;
    public static PREFERRED_GROWTH_ANY = 2;

    public getPreferredGrowth () {
        let ratio = this.getHeight() / this.getWidth();
        let idealRatio = 2.5; // This means we'd prefer a tall map that'd look good on our screen

        let difference = Math.abs(ratio - idealRatio);
        if (difference < 0.5) {
            return RoomRandomMap.PREFERRED_GROWTH_ANY;
        } else {
            if (ratio < idealRatio) {
                return RoomRandomMap.PREFERRED_GROWTH_VERTICAL;
            } else {
                return RoomRandomMap.PREFERRED_GROWTH_HORIZONTAL;
            }
        }
    }

    public static isDirectionPreferred (direction : number, growth : number) {
        if (growth == RoomRandomMap.PREFERRED_GROWTH_ANY) {
            return true;
        } else if (direction == Direction.NORTH || direction == Direction.SOUTH) {
            return growth == RoomRandomMap.PREFERRED_GROWTH_VERTICAL;
        } else {
            return growth == RoomRandomMap.PREFERRED_GROWTH_HORIZONTAL;
        }
    }
}