/// <reference path="../Region.ts" />
/// <reference path="../Shuffler.ts" />
/// <reference path="ShufflerDirection.ts" />
/// <reference path="RoomRandomMap.ts" />
/// <reference path="RoomRandom.ts" />
/// <reference path="RoomRandomFodder.ts" />
interface RandomizingRoomOptions {
    region : RegionRandom;
    room : RoomRandom;
    map : RoomRandomMap;
}

class RegionRandom extends Region {
    private randomized = false;
    public map : RoomRandomMap;
    public fodderRoomClass : typeof RoomRandom = RoomRandomFodder;
    public placedRooms : Array<RoomRandom> = [];

    public constructor (name : string, map? : RoomRandomMap) {
        super(name);
        this.map = map == undefined ? new RoomRandomMap() : map;
    }

    public async randomize () {
        if (!this.randomized) {
            await RegionRandom.rulebookRandomizeRegion.execute({
                noun : this
            });
        }
    }

    public static rng : () => number = () => { return Math.random(); };

    public static rulebookRandomizeRegion = new Rulebook<Region>("Randomizing Random Region something");
    public static rulebookPlaceRoom = new Rulebook<RandomizingRoomOptions>("Placing Random Room something");
    public static rulebookBeforePlaceRoom = new Rulebook<RandomizingRoomOptions>("Before placing Random Room something");
    public static rulebookAfterPlaceRoom = new Rulebook<RandomizingRoomOptions>("After placing Random Room something");

    public static ruleFirstRandomizeRegion = RegionRandom.rulebookRandomizeRegion.createAndAddRule({
        name : "Empty map cache to start randomizing region",
        firstPriority : Rule.PRIORITY_HIGHEST,
        code : runner => {
            let region = <RegionRandom> runner.noun;

            // We don't want to connect a region to another region unintentionally
            region.map.emptyCache();
        }
    });

    public static ruleBasicRandomizeRegion = RegionRandom.rulebookRandomizeRegion.createAndAddRule({
        name : "Randomize all unplaced, randomizable rooms in region something",
        code : async runner => {
            let region = <RegionRandom> runner.noun;

            let roomShuffler = new Shuffler(
                Region.InRelation.getAllRightTypes(region, RoomRandom).filter((room : RoomRandom) => {
                    return room.randomizable && !room.placed &&
                        (room == WorldState.player.getRoom() || (RegionRandom.rng() * 100) <= room.appearChance);
                }), RegionRandom.rng
            );

            for (let room = roomShuffler.getOne(); room != undefined; room = roomShuffler.getOne()) {
                // Prevent available connections from getting too low
                // If we're placing rooms with too few availableConnections, we might end up reaching 0 availableConnections
                // Which would mean no more rooms can be placed.
                while (region.map.rooms > 0 && region.map.getAvailableConnections() < 4) {
                    let fodder = new region.fodderRoomClass();
                    let options = <RandomizingRoomOptions> {
                        map : region.map,
                        room : fodder,
                        region : region
                    };

                    region.place(fodder);

                    await RegionRandom.rulebookBeforePlaceRoom.execute({noun : options});
                    await RegionRandom.rulebookPlaceRoom.execute({noun : options});
                    await RegionRandom.rulebookAfterPlaceRoom.execute({noun : options});
                }

                let options = <RandomizingRoomOptions> {
                    map : region.map,
                    room : room,
                    region : region
                };
                await RegionRandom.rulebookBeforePlaceRoom.execute({noun : options});
                await RegionRandom.rulebookPlaceRoom.execute({noun : options});
                await RegionRandom.rulebookAfterPlaceRoom.execute({noun : options});

                if (!room.placed) {
                    Elements.CurrentTurnHandler.printAsError(new Say("Was unable to place room ", room, ". Game might be unplayable."));
                }
            }
        }
    });

    public static ruleAddExtraConnections = RegionRandom.rulebookRandomizeRegion.createAndAddRule({
        firstPriority : Rule.PRIORITY_LOWEST,
        name : "Add extra connections to rooms in region",
        code : runner => {
            let region = <RegionRandom> runner.noun;
            let placedRooms = Region.InRelation.getAllRightTypes(region, RoomRandom).filter((room : RoomRandom) => {
                return room.randomizable && room.placed;
            });

            placedRooms.forEach((room : RoomRandom) => {
                let myCoordinates = region.map.getCoordinates(room);
                let directionShuffler = new Shuffler<number>(room.connectableOn.slice(0), RegionRandom.rng);
                for (let direction = directionShuffler.getOne(); direction != undefined && (RegionRandom.rng() * 100) <= room.extraConnectionChance; direction = directionShuffler.getOne()) {
                    if (room.connections[direction] == undefined) {
                        let otherCoordinates = Room.shift(myCoordinates, direction);
                        let otherRoom = region.map.getRoom(otherCoordinates[0], otherCoordinates[1]);
                        if (otherRoom != undefined && otherRoom.randomizable
                            && otherRoom.isConnectableOn(OppositeDirection[Direction[direction]])
                            && (RegionRandom.rng() * 100) <= otherRoom.extraConnectionChance) {
                            room.mapRoom(otherRoom, direction);
                        }
                    }
                }
            });
        }
    });

    public static rulePlaceFirstRoom = RegionRandom.rulebookPlaceRoom.createAndAddRule({
        name : "Placing First room something",
        firstPriority : Rule.PRIORITY_HIGHEST,
        code : runner => {
            let placingOptions = <RandomizingRoomOptions> runner.noun;

            if (placingOptions.map.isFree(0, 0)) {
                placingOptions.map.map(placingOptions.room, 0, 0);
                return true;
            }
        },
        conditions : runner => {
            return runner.noun.map.rooms == 0;
        }
    });

    public static rulePlaceNonTrickyRoom = RegionRandom.rulebookPlaceRoom.createAndAddRule({
        name : "Placing non-tricky room something",
        firstPriority : Rule.PRIORITY_HIGH,
        code : runner => {
            let placingOptions = <RandomizingRoomOptions> runner.noun;


            //let directionShuffler = new Shuffler<number>(placingOptions.room.connectableOn);
            let preferredGrowthDirection = placingOptions.map.getPreferredGrowth();
            let directionShuffler = new ShufflerDirection(placingOptions.room.connectableOn, preferredGrowthDirection);
            //for (let direction = directionShuffler.getOne(); direction != undefined; direction = directionShuffler.getOne()) {
            for (let direction = directionShuffler.getDirection(); direction != undefined; direction = directionShuffler.getDirection()) {
                let oppositeDirection = OppositeDirection[Direction[direction]];
                let connectableRoom = placingOptions.map.getAnyFromCache(oppositeDirection);
                if (connectableRoom != undefined) {
                    let otherCoordinates = placingOptions.map.getCoordinates(connectableRoom);
                    let myCoordinates = Room.shift(otherCoordinates, oppositeDirection);
                    placingOptions.room.mapRoom(connectableRoom, direction);
                    placingOptions.map.map(placingOptions.room, myCoordinates[0], myCoordinates[1]);
                    return true;
                }
            }
        },
        conditions : runner => {
            return runner.noun.room.trickyCode == undefined;
        }
    });

    public static rulePlaceTrickyRoom = RegionRandom.rulebookPlaceRoom.createAndAddRule({
        name : "Placing tricky room something",
        code : runner => {
            let placingOptions = <RandomizingRoomOptions> runner.noun;
            let placedRooms = <Array<RoomRandom>> placingOptions.region.getRooms().filter((room) => {
                return room instanceof RoomRandom && room.placed;
            });

            if (placedRooms.length == 0) {
                Elements.CurrentTurnHandler.printAsError("Unable to place room " + placingOptions.room.getPrintedName() + ": There are no rooms to connect to!");
                return false;
            }

            let roomShuffler = new Shuffler(placedRooms, RegionRandom.rng);
            for (let connectableRoom = roomShuffler.getOne(); connectableRoom != undefined; connectableRoom = roomShuffler.getOne()) {
                let trickier = <TrickierOptions> {
                    region : placingOptions.region,
                    map : placingOptions.map,
                    otherRoom : connectableRoom
                };
                let tricky = placingOptions.room.getAnyDirection(trickier);
                if (tricky != undefined) {
                    placingOptions.room.mapRoom(connectableRoom, tricky.trickyRoomDirection);
                    placingOptions.map.map(placingOptions.room, tricky.x, tricky.y);
                    return true;
                }
            }

            // THIS WORKS
            // I DON'T KNOW WHY
            // I DON'T CARE WHY
            // LEAVE
            let connectableThroughFodder = (fodderStep : number, connectingRoom : RoomRandom) => {
                let trickier = <TrickierOptions> {
                    region : placingOptions.region,
                    map : placingOptions.map,
                    otherRoom : connectingRoom
                };

                if (fodderStep == 0) {
                    return placingOptions.room.getAnyDirection(trickier);
                } else {
                    let newFodder = new (placingOptions.region.fodderRoomClass)();
                    //let directionShuffler = new Shuffler<number>(newFodder.connectableOn, RegionRandom.rng);
                    let preferredGrowthDirection = placingOptions.map.getPreferredGrowth();
                    let directionShuffler = new ShufflerDirection(placingOptions.room.connectableOn, preferredGrowthDirection);
                    for (let direction = directionShuffler.getDirection(); direction != undefined; direction = directionShuffler.getDirection()) {
                        let oppositeDirection = OppositeDirection[Direction[direction]];
                        let otherCoordinates = placingOptions.map.getCoordinates(connectingRoom);
                        let wouldbeCoordinates = Room.shift(otherCoordinates, oppositeDirection);
                        let fodderTricky = {
                            otherRoom : connectingRoom,
                            otherRoomDirection : oppositeDirection,
                            trickyRoomDirection : direction,
                            map : placingOptions.map,
                            region : placingOptions.region,
                            x : wouldbeCoordinates[0],
                            y : wouldbeCoordinates[1]
                        };

                        if (newFodder.isPlaceable(fodderTricky)) {
                            newFodder.mapRoom(connectingRoom, fodderTricky.trickyRoomDirection);
                            placingOptions.map.map(newFodder, fodderTricky.x, fodderTricky.y);

                            let nextTricky = connectableThroughFodder(fodderStep - 1, newFodder);
                            if (nextTricky != undefined) {
                                placingOptions.region.place(newFodder);
                                return nextTricky;
                            } else {
                                newFodder.unmapRoom(fodderTricky.trickyRoomDirection);
                                placingOptions.map.unmap(fodderTricky.x, fodderTricky.y);
                            }
                        }
                    }
                }
            };

            for (let fodderLevel = 1; fodderLevel < 10; fodderLevel++) {
                roomShuffler.restart();
                for (let connectableRoom = roomShuffler.getOne(); connectableRoom != undefined; connectableRoom = roomShuffler.getOne()) {
                    let tricky = connectableThroughFodder(fodderLevel, connectableRoom);
                    if (tricky != undefined) {
                        placingOptions.room.mapRoom(tricky.otherRoom, tricky.trickyRoomDirection);
                        placingOptions.map.map(placingOptions.room, tricky.x, tricky.y);
                        return true;
                    }
                }
            }

            Elements.CurrentTurnHandler.printAsError("Unable to place room " + placingOptions.room.getPrintedName() + ": All attempts failed");
            return false;
        }
    });
}