package worms.model;

import worms.util.Coordinate;

import java.util.*;
import java.util.stream.Collectors;

public class World {

    public World(double width, double height, boolean[][] map) {

        if (!isValidDimension(width) || !isValidDimension(height)) throw new IllegalArgumentException();

        this.width = width;
        this.height = height;
        this.map = map;

        this.lengthX = width / map[0].length;
        this.lengthY = height / map.length;
        this.game = false;
        this.gameObjects = new HashSet<>();
    }


    // region game
    //===================================================================================

    public boolean hasActiveGame() {
        return this.game;
    }

    public void startGame() {
        this.game = true;
        this.activeWorm = 0;
    }

    public void finishGame() {
        this.game = false;
    }

    public void activateNextWorm() throws IllegalStateException {

        if (getWormList().size() == 0) {
            throw new IllegalStateException("No worms");
        }

        this.activeWorm++;
        if (this.activeWorm == getWormList().size()) {
            this.activeWorm = 0;
        }
    }

    public Worm getActiveWorm() {
        if (getWormList().size() == 0) return null;
        return getWormList().get(this.activeWorm);
    }

    public String getWinner() {

        if (getWormList().size() == 1) {
            return getWormList().get(0).getName();
        } else if (getWormList().size() > 1) {

            Team lastTeam = getWormList().get(0).getTeam();
            for(Worm worm: getWormList()) {
                if (!lastTeam.equals(worm.getTeam())) {
                    return null;
                }
            }
            return lastTeam.getName();

        } else {
            return null;
        }
    }

    private boolean game;
    private int activeWorm;

    //===================================================================================
    // endregion


    // region Dimensions
    //===================================================================================

    public static boolean isValidDimension(double dimension) {
        return dimension >= 0.0 && dimension <= Double.MAX_VALUE;
    }

    public double getWidth() {
        return width;
    }
    public double getHeight() {
        return height;
    }
    private final double width;
    private final double height;

    // ===================================================================================
    // endregion



    // region map
    //===================================================================================
    public boolean[][] getMap() {
        return map;
    }


    public boolean isTerminated() {
        return this.terminated;
    }

    public void terminate() {
        this.terminated = true;
    }

    private final boolean[][] map;
    private boolean terminated = false;

    // ===================================================================================
    // endregion

    // region passable
    //===================================================================================

    /**
     *
     * @param location ...
     * @return ...
     */
    public boolean isPassable(double[] location) {

        if (Math.floor(location[0] / lengthX) >= getMap()[0].length || location[0] < 0.0 ||
                location[1] / lengthY >= getMap().length || location[1] < 0.0) {
            return false;
        }
        return this.map[(int) Math.floor(location[1] / lengthY)][(int) Math.floor(location[0] / lengthX)];
    }

    public boolean isPassable(Coordinate location) {
        return this.isPassable(location.toArray());
    }

    public boolean isPassable(Coordinate location, double radius) {
        return this.isPassable(location.toArray(), radius);
    }

    public boolean isPassable(double[] center, double radius) {

        for (double i = 0; i < 2 * Math.PI; i += Math.PI / 180) {
            double lenX = center[0] + radius * Math.cos(i);
            double lenY = center[1] + radius * Math.sin(i);

            if (i < 1.58 && i > 1.57) {
                lenY -= 0.0000000001;
            }
            else if (i < 0.79 && i > 0.78 ) {
                lenX -= 0.0000000001;
            }

            if (!isPassable(lenX, lenY)) {
                return false;
            }
        }
        return true;
    }

    /**
     *
     * @return ...
     */
    public boolean isAdjacent(double[] center, double radius) {
        return isAdjacent(Coordinate.create(center), radius);
    }

    /**
     *
     * @param center ...
     * @param radius ...
     * @return ...
     */
    public boolean isAdjacent(Coordinate center, double radius) {

        double maxDistance = radius * 0.1;
        double length = lengthX;
        if (lengthX > lengthY) length = lengthY;

        return isPassable(center, radius) && !isPassable(center, radius + maxDistance + 0.001);
    }

    public double getLengthX() {
        return this.lengthX;
    }
    public double getLengthY() {
        return this.lengthY;
    }
    private final double lengthX;
    private final double lengthY;

    /**
     *
     * @param x ...
     * @param y ...
     * @return ...
     */
    private boolean isPassable(double x, double y) {



        int xCoord = (int) Math.floor(x / lengthX);
        int yCoord = map.length - 1 - (int) Math.floor(y / lengthY);

        if (yCoord < 0 || yCoord >= map.length) {
            return true;
        }
        if (xCoord < 0 || xCoord >= map[0].length) {
            return true;
        }

        return getMap()[yCoord][xCoord];
    }

    // ===================================================================================
    // endregion


    // region objects
    //===================================================================================

    public Collection<Object> getAllItems() {

        return new ArrayList<>(getGameObjects());
    }

    public Set<Team> getAllTeams() {

        Set<Team> teams = new HashSet<Team>();
        for(Worm worm: getWormList()) {
           teams.add(worm.getTeam());
        }
        return teams;
    }

    public Set<GameObject> getGameObjects() {
        return this.gameObjects;
    }

    public void add(GameObject obj) throws NullPointerException {
        if (hasActiveGame()) throw new IllegalStateException();
        if (!getGameObjects().add(obj)) throw new IllegalArgumentException();
        obj.setWorld(this);
    }
    public void remove(GameObject obj) throws IllegalArgumentException {

        if (!getGameObjects().remove(obj)) throw new IllegalArgumentException();
    }

    public boolean hasAsGameObject(GameObject obj) throws NullPointerException {
        return getGameObjects().contains(obj);
    }

    public List<Worm> getWormList() {

        List<Worm> list = new ArrayList<>();
        for (GameObject x : getGameObjects()) {
            if (x.getClass().equals(Worm.class)) {
                list.add((Worm) x);
            }
        }
        return list;
    }

    public List<GameObject> getGameObjectList(Class cl) {
        List<GameObject> list = new ArrayList<>();
        for (GameObject x : getGameObjects()) {
            if (x.getClass().equals(cl)) {
                list.add(x);
            }
        }
        return list;
    }

    private Set<GameObject> gameObjects;


    // ===================================================================================
    // endregion

}
