
package eu.smoser.dungeongenerator.dungeon;

import java.util.*;
import eu.smoser.dungeongenerator.util.*;
import org.jgrapht.UndirectedGraph;
import org.jgrapht.graph.SimpleGraph;

/**
 * Stores all information about the dungeon
 *
 * @author MrMcX
 */
public class Dungeon {

    private final List<Room> rooms;
    private final LinkedList<Room> toGenerate;
    private final Counter counter;
    private final HashMap<Integer, Object> greedyMap;

    /**
     * Standard constructor, creates a new dungeon
     *
     * @param size maximum number of rooms
     * @param type how the rooms are to construct
     */
    public Dungeon(int size, Type type) {
        rooms = new ArrayList<>();
        toGenerate = new LinkedList<>();
        counter = new Counter();
        greedyMap = GreedyMap(size, type);
    }
    
    /**
     * Returns the greedy map
     * @return HashMap greedy HashMap
     */
    public HashMap<Integer, Object> GetGreedyMap(){
        return greedyMap;
    }
    
    /**
     * Generates the dungeons contents
     * @param size Size of the dungeon
     * @param type type of environment
     * @param mode generation mode
     */
    public void generate(int size, Type type, Mode mode) {
        toGenerate.add(Room.RandomRoom(true, Exit.Start(), type, greedyMap));
        while (!toGenerate.isEmpty() && (rooms.size() < size || Experimental(type))) {
            Room next;
            switch (mode) {
                case STRAIGHT:
                    next = toGenerate.getLast();
                    break;
                case RANDOM:
                    next = toGenerate.get(Dice.Roll(toGenerate.size(), 1) - 1);
                    break;
                default:
                    // case BRANCHED
                    next = toGenerate.getFirst();
                    break;
            }
            toGenerate.addAll(next.generate(counter, type, greedyMap));
            toGenerate.remove(next);
            rooms.add(next);
        }
    }

    @Override
    public String toString() {
        return rooms.stream().map((r) -> r.toLongString() + "\n").reduce("", String::concat);
    }

    /**
     * Creates a graph from the dungeon
     *
     * @return UndirectedGraph from JGraphT
     */
    public UndirectedGraph<Room, Exit> toGraph() {
        UndirectedGraph<Room, Exit> g = new SimpleGraph<>(Exit.class);
        rooms.forEach(g::addVertex);
        rooms.forEach((r) -> {
            for (Exit e : r.exits) {
                if (e.isStart || e.room.number == 0 || g.containsEdge(r, e.room)) { 
                    continue;
                }
                g.addEdge(r, e.room, e);
            }
        });
        return g;
    }

    /**
     * Checks whether a type is natural
     *
     * @param n type to check
     * @return is natural
     */
    public static boolean Natural(Type n) {
        return (n == Type.NATURAL || n == Type.EXP_NATURAL);
    }

    /**
     * Checks whether a type is experimental
     *
     * @param n type to check
     * @return is experimental
     */
    public static boolean Experimental(Type n) {
        return (n == Type.EXP_ARTIFICIAL || n == Type.EXP_NATURAL);
    }

    /**
     * Different room generation sequence modes
     */
    public enum Mode {

        /**
         * FILO - straight dungeon
         */
        STRAIGHT,
        /**
         * FIFO - branched dungeon
         */
        BRANCHED,
        /**
         * Random - balanced dungeon
         */
        RANDOM,
    }

    /**
     * The type of rooms to generate
     */
    public enum Type {

        /**
         * Nature-made rooms
         */
        NATURAL,
        /**
         * "Human"-made rooms
         */
        ARTIFICIAL,
        /**
         * Nature-made rooms in experimental mode
         */
        EXP_NATURAL,
        /**
         * "Human"-made rooms in experimental mode
         */
        EXP_ARTIFICIAL
    }
    
    private static HashMap<Integer, Object> GreedyMap(int size, Type type) {
        HashMap<Integer, Object> expMap = new HashMap<>();
        if (Experimental(type)) {
            double expVal = 1.0 - (1.0 / size);
            expMap.put(0, expVal);
            int i = 1;
            boolean a = true;
            int b = 0;
            while (expVal >= 1.0 / 20.0 && i <= 20) {
                if (expVal >= (3.0 / 20.0)) {
                    expMap.put(i, 19);
                    expVal -= (3.0 / 20.0);
                } else if (expVal >= 1.0 / 8.0) {
                    expMap.put(i, a ? 8 : 11);
                    expVal -= 1.0 / 8.0;
                    a = !a;
                } else if (expVal >= 1.0 / 10.0) {
                    switch (b) {
                        case 1:
                            expMap.put(i, 1);
                            break;
                        case 2:
                            expMap.put(i, 5);
                            break;
                        default:
                            expMap.put(i, 18);
                            b = 0;
                            break;
                    }
                    expVal -= 1.0 / 10.0;
                    b++;
                } else if (expVal >= 3.0 / 40.0) {
                    expMap.put(i, 13);
                    expVal -= 3.0 / 40.0;
                } else {
                    expMap.put(i, 15);
                    expVal -= 1.0 / 20.0;
                }
                i++;
            }
            int c = 0;
            while (i <= 20) {
                switch (c) {
                    case 1:
                        expMap.put(i, 7);
                        break;
                    case 2:
                        expMap.put(i, 10);
                        break;
                    default:
                        expMap.put(i, 20);
                        c = 0;
                        break;
                }
                c++;
                i++;
            }
        } else {
            for (int i = 1; i <= 20; i++) {
                expMap.put(i, i);
            }
        }
        return expMap;
    }
}
