/// <reference path="../Room.ts" />
interface TrickierOptions {
    region : RegionRandom;
    map : RoomRandomMap;
    otherRoom : RoomRandom;
}

interface TrickyOptions extends TrickierOptions {
    otherRoomDirection : number;
    trickyRoomDirection : number;
    x : number;
    y : number;
}

interface RoomRandomNode {
    room : RoomRandom;
    coordinates : Array<number>;
    distance : number;
}

class RoomRandom extends Room {
    public connectableOn : Array<number> = [Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST];
    public randomizable = true; // non-randomizable rooms don't get placed automatically
    public placed = false;
    public appearChance = 75;
    public extraConnectionChance = 75; // Requires two successes
    public backgroundImage = "tomato";

    public lastMap : RoomRandomMap;

    public constructor (id? : string, fodder? : boolean) {
        super(id, fodder);
    }

    public getBackgroundClass () {
        if (this.isImageDefined()) {
            return this.backgroundImage;
        }
        return "tomato";
    }

    private isImageDefined () {
        try {
            for (var i = 0; i < document.styleSheets.length; i++) {
                var rules = document.styleSheets[i]['rules'] || document.styleSheets[i]['cssRules'];
                for (var x in rules) {
                    if (typeof rules[x].selectorText == 'string' && rules[x].selectorText == "." + this.backgroundImage) {
                        return true;
                    }
                }
            }
            return false;
        } catch (e) {
            console.warn("Unable to read image");
            return true;
        }
    }

    public isConnectableOn (oppositeDirection : number) {
        return this.connectableOn.indexOf(oppositeDirection) != -1;
    }

    public trickyCode : (options : TrickyOptions) => boolean;

    public getAnyDirection (options : TrickierOptions) : TrickyOptions {
        let directionShuffler = new Shuffler<number>(this.connectableOn);
        for (let direction = directionShuffler.getOne(); direction != undefined; direction = directionShuffler.getOne()) {
            let oppositeDirection = OppositeDirection[Direction[direction]];
            let otherCoordinates = options.map.getCoordinates(options.otherRoom);
            let wouldbeCoordinates = Room.shift(otherCoordinates, oppositeDirection);
            let trickyOptions = {
                otherRoom : options.otherRoom,
                otherRoomDirection : oppositeDirection,
                trickyRoomDirection : direction,
                map : options.map,
                region : options.region,
                x : wouldbeCoordinates[0],
                y : wouldbeCoordinates[1]
            };

            if (this.isPlaceable(trickyOptions)) {
                return trickyOptions;
            }
        }
    }

    public isPlaceable (options : TrickyOptions) {
        if (!this.isConnectableOn(options.trickyRoomDirection) || !options.map.isFree(options.x, options.y)) {
            // This can't connect through that!
            // That coordinate isn't free!
            return false;
        }

        if (options.otherRoom == undefined || !options.otherRoom.isConnectableOn(options.otherRoomDirection)) {
            // There is no other room there?
            // The other room doesn't like it this way
            return false;
        }

        // Do I have my own tricky code?
        if (this.trickyCode != undefined) {
            return this.trickyCode(options);
        }


        return true;
    }

    public getDistanceTo (room : RoomRandom) {
        let myCoordinates = this.lastMap.getCoordinates(this);
        let otherCoordinates = this.lastMap.getCoordinates(room);
        if (myCoordinates != undefined && otherCoordinates != undefined) {
            let c1 = myCoordinates;
            let c2 = otherCoordinates;
            return Math.abs(c1[0] - c2[0]) + Math.abs(c1[1] - c2[1]);
        }
    }

    /**
     * This implementation is sufficiently fast for constant use.
     * @param pathEnd
     * @param map
     * @param availableRooms
     * @returns {Array}
     */
    public findPathTo (pathEnd : RoomRandom, validRoom? : (room : RoomRandom) => boolean) {
        validRoom = validRoom == undefined ? () => {return true;} : validRoom;
        let map = this.lastMap;
        let endPosition = map.getCoordinates(pathEnd);
        let open = [];

        let distance = (c1, c2) => {
            return Math.abs(c1[0] - c2[0]) + Math.abs(c1[1] - c2[1]);
        };

        let neighbors = (room : RoomRandom, x : number, y : number) => {
            let neighs = [];
            for (let direction = 0; direction < room.connections.length; direction++) {
                let otherRoom = room.connections[direction];
                if (otherRoom != undefined && open.indexOf(otherRoom) == -1 && validRoom(<RoomRandom> otherRoom)) {
                    let dirCoordinates = Room.shift([x, y], direction);
                    let dir = [otherRoom, dirCoordinates, distance(endPosition, dirCoordinates)];
                    neighs.push(dir);
                }
            }
            return neighs.sort((a,b) => { return (<number> a[2]) - (<number> b[2]);});
        };


        let shortestPath = {
            length : map.getRoomCount()
        };
        let noPath = shortestPath;
        let cPath = [];
        let findPath = (myArray) => {
            let room = myArray[0];
            cPath.push(myArray);
            if (room == pathEnd) {
                if (shortestPath.length >= cPath.length) {
                    shortestPath = cPath.slice(0);
                }
            } else if (shortestPath.length > (cPath.length)) {
                open.push(room);
                let otherRooms = neighbors(room, myArray[1][0], myArray[1][1]);
                for (let i = 0; i < otherRooms.length; i++) {
                    if ((cPath.length + 1) < shortestPath.length) {
                        findPath(otherRooms[i]);
                    }
                }
                open.pop();
            }
            cPath.pop();
        };

        findPath([this, map.getCoordinates(this)]);
        return shortestPath != noPath ? shortestPath : undefined;
    }

    public getBestDirectionTo (otherRoom : RoomRandom, validRoom? : (room : RoomRandom) => boolean) {
        let path = this.findPathTo(otherRoom, validRoom);
        if (path != undefined) {
            if (path.length == 1) {
                return undefined;
            }
            return this.connections.indexOf(path[1][0]);
        }
    }

    public getAStarPathTo (otherRoom : RoomRandom, validRoom? : (room : RoomRandom) => boolean) {
        validRoom = validRoom != undefined ? validRoom : () => {return true};

        let distance = (c1, c2) => {
            return Math.abs(c1[0] - c2[0]) + Math.abs(c1[1] - c2[1]);
        };

        let isVisited = (room) => {
            return visited.indexOf(room) != -1;
        };

        let getNeighbors = (node : RoomRandomNode) => {
            let neighbors : Array<RoomRandomNode> = [];
            for (let direction = 0; direction < node.room.connections.length; direction++) {
                if (node.room.connections[direction] != undefined && !isVisited(node.room.connections[direction]) && validRoom(<RoomRandom> node.room.connections[direction])) {
                    let coordinates = Room.shift(node.coordinates, direction);
                    neighbors.push({
                        room : <RoomRandom> node.room.connections[direction],
                        coordinates : coordinates,
                        distance : distance(coordinates, endNode.coordinates)
                    });
                    visited.push(<RoomRandom> node.room.connections[direction]);
                }
            }
            return neighbors;
        };

        let getClosestPath = () => {
            let shortest = 0;
            for (let i = 1; i < open.length; i++) {
                let lastPoint = open[i][open[i].length - 1];
                if (lastPoint.distance < open[shortest][open[shortest].length - 1].distance) {
                    shortest = i;
                }
            }
            return shortest;
        };


        let endNode = {room : otherRoom, coordinates : this.lastMap.getCoordinates(otherRoom), distance : 0};
        let startCoordinates = this.lastMap.getCoordinates(this);
        let startNode = {room : this, coordinates : startCoordinates, distance : distance(startCoordinates, endNode.coordinates)};
        let open : Array<Array<RoomRandomNode>> = [[startNode]];
        let closed : Array<Array<RoomRandomNode>> = [];
        let shortestPath = this.lastMap.getRoomCount();
        let shortestIndex;
        let myPath;
        let closest = 0;
        let visited : Array<RoomRandom> = [this];
        while (open.length > 0) {
            myPath = open.splice(closest, 1)[0];

            if (myPath[myPath.length - 1].distance == 0) {
                let push = closed.push(myPath);
                if (myPath.length < shortestPath) {
                    shortestPath = myPath.length;
                    shortestIndex = push - 1;
                }
                // lazy, first path is very likely to be the best
                break;
            } else {
                let neighbors = getNeighbors(myPath[myPath.length - 1]);
                for (let i = 0; i < neighbors.length; i++) {
                    open.push(myPath.concat([neighbors[i]]));
                }
            }
            for (let i = open.length - 1; i >= 0; i--) {
                if (open[i].length >= shortestPath) {
                    open.splice(i, 1);
                }
            }
            closest = getClosestPath();
        }

        return closed[shortestIndex];
    }

    public getAStarBestDirectionTo (otherRoom : RoomRandom, validRoom? : (room : RoomRandom) => boolean) {
        let path = this.getAStarPathTo(otherRoom, validRoom);
        if (path != undefined) {
            if (path.length == 1) {
                return undefined;
            }
            return this.connections.indexOf(path[1].room);
        }
    }

    public getConnectedDirection () {
        let shuffler = new Shuffler(Room.DIRECTIONS);
        for (let direction = shuffler.getOne(); direction != undefined; direction = shuffler.getOne()) {
            if (this.connections[direction] != undefined) {
                return direction;
            }
        }
    }

    // TODO: Return all things of type that are in placed RoomRandom.
    public static getActive (type : typeof Thing) {

    }
}

// random = new RoomRandom...
// random.connectableOn = [Room.DIRECTION_NORTH, Room.DIRECTION_SOUTH...]