package pl.edu.pw.mini.sg.gamegen;

import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

import com.google.common.collect.ImmutableList;

public class BuildingGraphStructure {
    private final ImmutableList<NodeRole> nodeGrid;
    private final BuildingDimensions dimensions;
    private final ImmutableList<Boolean> xConnections;
    private final ImmutableList<Boolean> yConnections;

    public BuildingGraphStructure(ImmutableList<NodeRole> nodeGrid, BuildingDimensions dimensions,
            ImmutableList<Boolean> xConnections, ImmutableList<Boolean> yConnections) {
        super();
        this.nodeGrid = nodeGrid;
        this.dimensions = dimensions;
        this.xConnections = xConnections;
        this.yConnections = yConnections;
    }

    public NodeRole getNodeRole(final int x, final int y, final int floor) {
        return nodeGrid.get(BuildingCoordsHelpers.convert3dCoordsToIndex(x, y, floor, dimensions));
    }

    public NodeRole getNodeRole(final BuildingCoord point) {
        return getNodeRole(point.getX(), point.getY(), point.getFloor());
    }

    public boolean areNodesConnected(final BuildingCoord p1, final BuildingCoord p2) {
        if (p1.equals(p2)) {
            return false;
        } else if (BuildingCoord.areSameXY(p1, p2)) {
            return getNodeRole(p1) == NodeRole.ELEVATOR && getNodeRole(p2) == NodeRole.ELEVATOR;
        } else if (BuildingCoord.areXNeighbours(p1, p2)) {
            return xConnections.get(BuildingCoordsHelpers.getXConnectionIndex(p1.getX(), p2.getX(), p1.getY(),
                    p1.getFloor(), dimensions));
        } else if (BuildingCoord.areYNeighbours(p1, p2)) {
            return yConnections.get(BuildingCoordsHelpers.getYConnectionIndex(p1.getX(), p1.getY(), p2.getY(),
                    p1.getFloor(), dimensions));
        } else {
            return false;
        }
    }

    public Stream<BuildingCoord> getAllNodes() {
        Supplier<BuildingCoord> nodeSupplier = new Supplier<BuildingCoord>() {
            private BuildingCoord currCoord = new BuildingCoord(0, 0, 0);

            @Override
            public BuildingCoord get() {
                final BuildingCoord coord = currCoord;

                currCoord = advance(currCoord);
                return coord;
            }

            private BuildingCoord advance(BuildingCoord coord) {
                if (coord.getX() + 1 < getWidth()) {
                    return new BuildingCoord(coord.getX() + 1, coord.getY(), coord.getFloor());
                } else if (coord.getY() + 1 < getHeight()) {
                    return new BuildingCoord(0, coord.getY() + 1, coord.getFloor());
                } else {
                    return new BuildingCoord(0, 0, coord.getFloor() + 1);
                }
            }
        };

        return Stream.generate(nodeSupplier).limit(getFloorCount() * getWidth() * getHeight());
    }

    public int getWidth() {
        return dimensions.getWidth();
    }

    public int getHeight() {
        return dimensions.getHeight();
    }

    public int getFloorCount() {
        return dimensions.getFloorCount();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (int floor = 0; floor < getFloorCount(); floor++) {
            builder.append("Floor: " + floor + "\n");
            builder.append(floorToString(floor));
        }

        return builder.toString();
    }

    private String floorToString(int floor) {
        StringBuilder builder = new StringBuilder();

        for (int y = 0; y < getHeight(); y++) {
            for (int x = 0; x < getWidth(); x++) {
                final BuildingCoord current = new BuildingCoord(x, y, floor);
                builder.append(getNodeRole(current).getSymbol());
                final Optional<BuildingCoord> next = current.advanceX(dimensions);
                if (next.isPresent()) {
                    if (areNodesConnected(current, next.get())) {
                        builder.append("-");
                    } else {
                        builder.append(" ");
                    }
                }
            }
            builder.append('\n');
            for (int x = 0; x < getWidth(); x++) {
                final BuildingCoord current = new BuildingCoord(x, y, floor);
                final Optional<BuildingCoord> next = current.advanceY(dimensions);
                if (next.isPresent()) {
                    if (areNodesConnected(current, next.get())) {
                        builder.append("|");
                    } else {
                        builder.append(" ");
                    }
                }
                builder.append(" ");
            }
            builder.append('\n');
        }

        return builder.toString();
    }
}
