package eu.smoser.dungeongenerator.dungeon;

import eu.smoser.dungeongenerator.dungeon.objects.*;
import eu.smoser.dungeongenerator.util.*;

import java.util.*;

/**
 * @author MrMcX
 */
public class Room {

    /**
     * The unique number of the room
     */
    public int number;

    /**
     * The list of exits in this room
     */
    public Exit[] exits;


    private Dungeon.Type type;
    private final boolean isRoom;
    private boolean hasMonster, hasTrap, hasMagic, hasSpecial, hasTreasure = false;
    private ADungeonObject treasure, magic, enemy, trap, special;
    private String desc; // = null

    /**
     * Constructs a room with a given predecessor, number of exits, description and counter
     *
     * @param predecessor   Predecessor room
     * @param numberOfExits Number of exits
     * @param room          Whether it is a room
     * @param desc          Description of room
     */
    public Room(Exit predecessor, int numberOfExits, boolean room, String desc) {
        exits = new Exit[numberOfExits];
        exits[0] = predecessor;
        this.isRoom = room;
        this.desc = desc;
        number = 0;
    }

    /**
     * Generates the content of the room
     *
     * @param c    Room counter
     * @param type Environment type
     * @param map  Experimental map
     * @return Returns more rooms to generate
     */
    public List<Room> generate(Counter c, Dungeon.Type type, HashMap<Integer, Object> map) {
        number = c.Next();
        List<Room> list = new LinkedList<>();
        for (int i = 0; i < exits.length; i++) {
            if (exits[i] == null) {
                Exit predecessor = new Exit(this);
                Room newRoom = RandomRoom(false, predecessor, type, map);
                exits[i] = new Exit(newRoom, predecessor.type);
                list.add(newRoom);
            }
        }
        SetContents(type);
        return list;
    }

    private void SetContents(Dungeon.Type type) {
        if (isRoom) {
            switch (Dice.Roll(6, 2)) {
                case 2:
                case 3:
                    return;
                case 4:
                    hasMonster = true;
                    enemy = new Enemy(type);
                    return;
                case 5:
                    hasTrap = true;
                    trap = new Trap();
                case 6:
                    hasTreasure = true;
                    treasure = new Treasure();
                    return;
                case 7:
                    hasMagic = true;
                    magic = new Magic(type);
                    return;
                case 8:
                    hasSpecial = true;
                    special = new Special(type);
                    return;
                case 9:
                    hasMonster = true;
                    hasTrap = true;
                    enemy = new Enemy(type);
                    trap = new Trap();
                    return;
                case 10:
                    hasMonster = true;
                    hasTreasure = true;
                    enemy = new Enemy(type);
                    treasure = new Treasure();
                    return;
                case 11:
                    hasMonster = true;
                    hasMagic = true;
                    enemy = new Enemy(type);
                    magic = new Magic(type);
                    return;
                case 12:
                    hasTrap = true;
                    hasTreasure = true;
                    trap = new Trap();
                    treasure = new Treasure();
                    return;
            }
        } else {
            switch (Dice.Roll(6, 1)) {
                case 1:
                case 2:
                case 3:
                    return;
                case 4:
                    hasMonster = true;
                    enemy = new Enemy(type);
                    return;
                case 5:
                    hasTrap = true;
                    trap = new Trap();
                    return;
                case 6:
                    hasTreasure = true;
                    treasure = new Treasure();
                    return;
            }
        }
        this.type = type;
    }

    @Override
    public String toString() {
        if (!isRoom) {
            return "Gang " + number;
        } else if (Dungeon.Natural(type)) {
            return "Höhle " + number;
        } else {
            return "Raum " + number;
        }
    }

    /**
     * Returns a long description
     *
     * @return Long description string
     */
    String toLongString() {
        StringBuilder out = new StringBuilder();
        if (isRoom) {
            out.append("Raum ");
        } else {
            out.append("Gang ");
        }
        out.append("Nummer ").append(number).append(": ").append(desc).append("\n").append("Ausgänge:\n");
        for (Exit x : exits) {
            out.append(x.toLongString());
        }
        if (hasMonster) {
            out.append("Gegner:\n- ").append(enemy).append("\n");
        }
        if (hasTrap) {
            out.append("Fallen:\n- ").append(trap).append("\n");
        }
        if (hasMagic) {
            out.append("Magische Phänomene:\n- ").append(magic).append("\n");
        }
        if (hasSpecial) {
            out.append("Besondere Phänomene:\n- ").append(special).append("\n");
        }
        if (hasTreasure) {
            out.append("Fundsache:\n- ").append(treasure).append("\n");
        }
        return out.toString();
    }

    /**
     * Returns a short description
     *
     * @return Short description string
     */
    String toShortString() {
        if (isRoom) {
            return "Raum Nummer " + number;
        } else {
            return "Gang Nummer " + number;
        }
    }

    private static Room RoomBigExits(Exit predecessor, Dungeon.Type type) {
        int rand = Dice.Roll(3, 1);
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, rand + 1, true, "große Höhle mit " + rand + " Ausgängen");
        } else {
            return new Room(predecessor, rand + 1, true, "großer Raum mit " + rand + " Ausgängen");
        }
    }

    private static Room RoomSmallExits(Exit predecessor, Dungeon.Type type) {
        int rand = Dice.Roll(3, 1);
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, rand + 1, true, "kleine Höhle mit " + rand + " Ausgängen");
        } else {
            return new Room(predecessor, rand + 1, true, "kleiner Raum mit " + rand + " Ausgängen");
        }
    }

    private static Room RoomBigNoExits(Exit predecessor, Dungeon.Type type) {
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, 1, true, "große Höhle ohne Ausgang");
        } else {
            return new Room(predecessor, 1, true, "großer Raum ohne Ausgang");
        }
    }

    private static Room RoomBigExitsStair(Exit predecessor, Dungeon.Type type) {
        int rand = Dice.Roll(2, 1);
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, rand + 2, true, "große Höhle mit " + rand + " Ausgängen und einer Treppe");
        } else {
            return new Room(predecessor, rand + 2, true, "großer Raum mit " + rand + " Ausgängen und einer Treppe");
        }
    }

    private static Room RoomSmallNoExits(Exit predecessor, Dungeon.Type type) {
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, 1, true, "kleine Höhle ohne Ausgang");
        } else {
            return new Room(predecessor, 1, true, "kleiner Raum ohne Ausgang");
        }
    }

    private static Room RoomSmallExitsStair(Exit predecessor, Dungeon.Type type) {
        int rand = Dice.Roll(2, 1);
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, rand + 2, true, "kleine Höhle mit " + rand + " Ausgängen und einer Treppe");
        } else {
            return new Room(predecessor, rand + 2, true, "kleiner Raum mit " + rand + " Ausgängen und einer Treppe");
        }
    }

    private static Room RoomGiant(Exit predecessor, Dungeon.Type type) {
        int rand = Dice.Roll(2, 1);
        if (Dungeon.Natural(type)) {
            return new Room(predecessor, rand + 1, true, "gigantische Höhle mit " + rand + " Ausgängen");
        } else {
            return new Room(predecessor, rand + 1, true, "gigantischer Raum mit " + rand + " Ausgängen");
        }
    }

    private static Room Corridor(Exit predecessor) {
        return new Room(predecessor, 2, false, "Gang");
    }

    private static Room CrossingT(Exit predecessor) {
        return new Room(predecessor, 3, false, "T-Kreuzung mit Abzweigungen nach links und rechts");
    }

    private static Room Crossing(Exit predecessor) {
        return new Room(predecessor, 4, false, "Kreuzung, die in alle Richtungen weitergeht");
    }

    private static Room DeadEnd(Exit predecessor) {
        return new Room(predecessor, 1, false, "Sackgasse");
    }

    /**
     * Generates a random room
     *
     * @param first       Whether it is the first room
     * @param predecessor Predecessor room
     * @param type        Environment type
     * @param map         The map for experimental mode
     * @return random room
     */
    static Room RandomRoom(boolean first, Exit predecessor, Dungeon.Type type, HashMap<Integer, Object> map) {
        int number;
        if (first) {
            number = Dice.Roll(6, 1);
        } else {
            number = Dice.Roll(20, 1);
        }
        switch ((int) map.get(number)) {
            case 1:
            case 2:
            case 3:
                return RoomBigExits(predecessor, type);
            case 4:
            case 5:
            case 6:
                return RoomSmallExits(predecessor, type);
            case 7:
                return RoomBigNoExits(predecessor, type);
            case 8:
            case 9:
                return RoomBigExitsStair(predecessor, type);
            case 10:
                return RoomSmallNoExits(predecessor, type);
            case 11:
            case 12:
                return RoomSmallExitsStair(predecessor, type);
            case 13:
            case 14:
                return RoomGiant(predecessor, type);
            case 15:
            case 16:
            case 17:
                return Corridor(predecessor);
            case 18:
                return CrossingT(predecessor);
            case 19:
                return Crossing(predecessor);
            case 20:
                return DeadEnd(predecessor);
        }
        return null;
    }
}
