package worms.facade;

import worms.model.*;
import worms.util.IllegalNameException;
import worms.util.ModelException;
import worms.util.Coordinate;
import worms.util.MustNotImplementException;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

public class Facade implements IFacade {

    /**
     * Turns the given worm by the given angle.
     * 
     * @param worm
     * 		  the worm who is going to move
     * @param angle
     * 		  the angle the worm should turn
     * @post the worm has turned with the given angle
     * 		 |worm.turn(angle)
     */
    @Override
    public void turn(Worm worm, double angle) throws ModelException {

        try {
            worm.turn(angle);
        }
        catch (AssertionError e) {
            throw new ModelException("");
        }
    }

    /**
     * Return the location the farthest away from its current location to which the given
     * worm can move in the world in which that worm is positioned, if any, following
     * the given direction and not exceeding the given maximum distance.
     * - The maximum distance must be finite and may not be negative.
     * - The given direction must be in the range [0.0 .. PI[.
     * - On its road to the resulting location, the given worm will always be
     * positioned on passable terrain.
     * - The resulting position may be outside the boundaries of the world, if any, in
     * which the given worm is located.
     *
     * @param worm
     * @param direction
     * @param maxDistance
     */
    @Override
    public double[] getFurthestLocationInDirection(Worm worm, double direction, double maxDistance) throws ModelException {

        try {
            return worm.getFurthestLocationInDirection(direction, maxDistance).toArray();
        } catch(Exception e) {
            throw new ModelException("");
        }
    }

    /**
     * Move the given worm according to the rules in the assignment.
     *
     * @param worm
     */
    @Override
    public void move(Worm worm) throws ModelException {

        try {
            worm.move();
        } catch(IllegalArgumentException e) {
            throw new ModelException("");
        }
    }

    /**
     * Returns whether the given worm can fall.
     */
    @Override
    public boolean canFall(Worm worm) throws MustNotImplementException {
        return worm.canFall();
    }

    /**
     * Makes the given worm fall down until it rests on impassable terrain again,
     * or until it leaves the world in which it is in.
     */
    @Override
    public void fall(Worm worm) throws ModelException, MustNotImplementException {
        worm.fall();
    }

    /**
     * Return the time needed by the given worm to jump to the nearest position
     * adjacent to impassable terrain.
     * - deltaT determines the resolution to be used in successive steps of the jump.
     *
     * @param worm
     * @param deltaT
     */
    @Override
    public double getJumpTime(Worm worm, double deltaT) throws ModelException {
        try {
            return worm.jumpTime();
        } catch(IllegalStateException e){
            throw new ModelException(e);
        }
    }

//    /**
//     * Makes the given worm jump.
//     *
//     * @param worm
//     * 		  the worm who is going to move
//     * @post the worm has jumped
//     * @throws ModelException
//     * 		   the worm throws an IllegalStateException
//     * 		   |catch(IllegalStateException e)
//     */
//    @Override
//    public void jump(Worm worm) throws ModelException {
//        try {
//            worm.jump();
//        }
//        catch(IllegalStateException e) {
//            throw new ModelException(e);
//        }
//    }

//    /**
//     * Returns the total amount of time (in seconds) that a jump of the given worm
//     * would take.
//     *
//     * @param worm
//     * 		  the worm who is going to move
//     * @throws ModelException
//     * 		   the worm throws an IllegalStateException
//     * 		   |catch(IllegalStateException e)
//     */
//    @Override
//    public double getJumpTime(Worm worm) throws ModelException {
//
//        try {
//            return worm.jumpTime();
//        }
//        catch(IllegalStateException e) {
//            throw new ModelException(e);
//        }
//    }

    /**
     * Returns the location on the jump trajectory of the given worm after a time t.
     * 
     * @param worm
     * 		  the worm who is going to move
     * @param t
     * 		  the time the worm should jump
     * @return An array with two elements, with the first element being the
     * 		   x-coordinate and the second element the y-coordinate.
     * @throws ModelException
     * 		   the worm throws an IllegalArgumentException
     * 		   |catch (IllegalArgumentException e)
     */
    @Override
    public double[] getJumpStep(Worm worm, double t) throws ModelException {

        try {
            Coordinate jumpStep = worm.jumpStep(t);
            return new double[]{jumpStep.item1, jumpStep.item2};
        }
        catch (IllegalArgumentException e) {
            throw new ModelException(e);
        }
    }

    /**
     * Make the given worm jump using the given time step.
     * - The given time step determines a time interval during which
     * you may assume that the worm will not move through a piece
     * of impassable terrain.
     *
     * @param worm
     * @param timeStep
     */
    @Override
    public void jump(Worm worm, double timeStep) throws ModelException {
        worm.jump();
    }

    /**
     * Create and return a new portion of food that is positioned at the given
     * location in the given world.
     * = If the given world is not effective, the new food is simply positioned
     * at the given location.
     *
     * @param world
     * @param location
     */
    @Override
    public Food createFood(World world, double[] location) throws ModelException {
        return new Food(world, location);
    }

    /**
     * Terminate the given portion of food.
     *
     * @param food
     */
    @Override
    public void terminate(Food food) throws ModelException {
        food.terminate();
    }

    /**
     * Check whether the given portion of food is terminated.
     *
     * @param food
     */
    @Override
    public boolean isTerminated(Food food) throws ModelException {
        return food.isTerminated();
    }

    /**
     * Return the current location of the given portion of food.
     * - The resulting array contains the the x-coordinate of the given worm
     * followed by its y-coordinate.
     *
     * @param food
     */
    @Override
    public double[] getLocation(Food food) throws ModelException {
        return food.getLocationArray();
    }

    /**
     * Return the radius of the given portion of food.
     *
     * @param food
     */
    @Override
    public double getRadius(Food food) throws ModelException {
        return food.getRadius();
    }

    /**
     * Return the mass of the given portion of food.
     *
     * @param food
     */
    @Override
    public double getMass(Food food) throws ModelException {
        return food.getMass();
    }

    /**
     * Return the world to which this portion of food belongs.
     *
     * @param food
     */
    @Override
    public World getWorld(Food food) throws ModelException {
        return food.getWorld();
    }

    /**
     * Make the given worm eat a portion of food.
     *
     * @param worm
     */
    @Override
    public void eat(Worm worm) {
        worm.eat();
    }

//    /**
//     * Returns the x-coordinate of the current location of the given worm.
//     *
//     * @param worm
//     * 		  the worm who is going to move
//     */
//    @Override
//    public double getX(Worm worm) throws ModelException {
//        return worm.getLocation().item1;
//    }
//
//    /**
//     * Returns the y-coordinate of the current location of the given worm.
//     *
//     * @param worm
//     * 		  the worm who is going to move
//     */
//    @Override
//    public double getY(Worm worm) throws ModelException {
//        return worm.getLocation().item2;
//    }

    /**
     * Create a new world with given width and given height.
     * The passable map is a rectangular matrix indicating which parts of the terrain
     * are passable and which parts are impassable.
     * This matrix is derived from the transparency of the pixels in the image file
     * of the terrain. passableMap[r][c] is true if the location at row r and column c
     * is passable, and false if that location is impassable.
     * The elements in the first row (row 0) represent the pixels at the top of the
     * terrain (i.e., largest y-coordinates).
     * The elements in the last row (row passableMap.length-1) represent pixels at the
     * bottom of the terrain (smallest y-coordinates).
     * The elements in the first column (column 0) represent the pixels at the left
     * of the terrain (i.e., smallest x-coordinates).
     * The elements in the last column (column passableMap[0].length-1) represent the
     * pixels at the right of the terrain (i.e., largest x-coordinates).
     *
     * @param width
     * @param height
     * @param passableMap
     */
    @Override
    public World createWorld(double width, double height, boolean[][] passableMap) throws ModelException {
        return new World(width, height, passableMap);
    }

    /**
     * Terminate the given world.
     *
     * @param world
     */
    @Override
    public void terminate(World world) throws ModelException {
        world.terminate();
    }

    /**
     * Check whether the given world is terminated.
     *
     * @param world
     */
    @Override
    public boolean isTerminated(World world) throws ModelException {
        return world.isTerminated();
    }

    /**
     * Return the width of the given world.
     *
     * @param world
     */
    @Override
    public double getWorldWidth(World world) throws ModelException {
        return world.getWidth();
    }

    /**
     * Return the height of the given world.
     *
     * @param world
     */
    @Override
    public double getWorldHeight(World world) throws ModelException {
        return world.getHeight();
    }

    /**
     * Check whether the given world is passable at the given location.
     * - The location is an array containing the x-coordinate of the location to
     * check followed by the y-coordinate of that location.
     * - Locations outside the boundaries of the world are always passable.
     *
     * @param world
     * @param location
     */
    @Override
    public boolean isPassable(World world, double[] location) throws ModelException {
        return world.isPassable(location);
    }

    /**
     * Create and return a new worm that is positioned at the given location, looks
     * in the given direction, has the given radius and the given name.
     *
     * @param world
     * @param center
     * @param radius The radius of the new worm (in meter)
     */
    @Override
    public boolean isPassable(World world, double[] center, double radius) {
        return world.isPassable(center, radius);
    }

    /**
     * Check whether the circular area with given center and given radius
     * is adjacent to impassable terrain in the given world.
     * - The circular area must not lie completely within the given world.
     *
     * @param world
     * @param center
     * @param radius
     */
    @Override
    public boolean isAdjacent(World world, double[] center, double radius) {
        return world.isAdjacent(center, radius);
    }

    /**
     * Check whether the given world contains the given worm.
     *
     * @param world
     * @param worm
     */
    @Override
    public boolean hasAsWorm(World world, Worm worm) throws ModelException {
        return world.hasAsGameObject(worm);
    }

    /**
     * Add the given worm to the given world.
     *
     * @param world
     * @param worm
     */
    @Override
    public void addWorm(World world, Worm worm) throws ModelException {
        world.add(worm);
    }

    /**
     * Remove the given worm from the given world.
     *
     * @param world
     * @param worm
     */
    @Override
    public void removeWorm(World world, Worm worm) throws ModelException {
        world.remove(worm);
    }

    /**
     * Return a list filled with all the worms in the given world.
     *
     * @param world
     */
    @Override
    public List<Worm> getAllWorms(World world) throws ModelException {

        return world.getWormList();
    }

    /**
     * Check whether the given world contains the given food.
     *
     * @param world
     * @param food
     */
    @Override
    public boolean hasAsFood(World world, Food food) throws ModelException {
        return world.hasAsGameObject(food);
    }

    /**
     * Add the given portion of food to the given world.
     *
     * @param world
     * @param food
     */
    @Override
    public void addFood(World world, Food food) throws ModelException {
        world.add(food);
    }

    /**
     * Remove the given portion of food from the given world.
     *
     * @param world
     * @param food
     */
    @Override
    public void removeFood(World world, Food food) throws ModelException {
        world.remove(food);
    }

    /**
     * Return a collection filled with all the worms and all the
     * portions of food in the given world.
     *
     * @param world
     */
    @Override
    public Collection<Object> getAllItems(World world) throws ModelException {
        return world.getAllItems();
    }

    /**
     * Return a set of all the team in the given world.
     *
     * @param world
     */
    @Override
    public Set<Team> getAllTeams(World world) throws ModelException {
        return world.getAllTeams();
    }

    /**
     * Check whether the given world has an active game.
     *
     * @param world
     */
    @Override
    public boolean hasActiveGame(World world) throws ModelException {
        return world.hasActiveGame();
    }

    /**
     * Return the active worm in the given world.
     * - The active worm is the worm whose turn it is to perform
     * player-controlled actions.
     *
     * @param world
     */
    @Override
    public Worm getActiveWorm(World world) throws ModelException {
        try {
            return world.getActiveWorm();
        } catch(IllegalArgumentException e) {
            throw new ModelException(e);
        }
    }

    /**
     * Start a new game in the given world.
     *
     * @param world
     */
    @Override
    public void startGame(World world) throws ModelException {
        world.startGame();
    }

    /**
     * Finish the current game, if any, in the given world.
     *
     * @param world
     */
    @Override
    public void finishGame(World world) throws ModelException {
        world.finishGame();
    }

    /**
     * Activate the next worm in the given world.
     *
     * @param world
     */
    @Override
    public void activateNextWorm(World world) throws ModelException {
        world.activateNextWorm();
    }

    /**
     * Return the name of a single worm if that worm is the winner, or the name
     * of a team if that team is the winner and the team still has several members.
     *
     * @param world
     */
    @Override
    public String getWinner(World world) {
        return world.getWinner();
    }

    /**
     * Create and return a new worm that is positioned at the given location in
     * the given world, that looks in the given direction, that has the given radius
     * and the given name, and that is a member of the given team.
     * - If the given world is not effective, the new worm is simply positioned
     * at the given location.
     * - If the given team is not effective, the new worm is not part of any team.
     * The location is an array containing the x-coordinate of the location of
     * the new worm followed by the y-coordinate of that location.
     *
     * @param world
     * @param location
     * @param direction
     * @param radius
     * @param name
     * @param team
     */
    @Override
    public Worm createWorm(World world, double[] location, double direction, double radius, String name, Team team) throws ModelException {
        return new Worm(world, location, direction, radius, name, team);
    }

    /**
     * Terminate the given worm.
     *
     * @param worm
     */
    @Override
    public void terminate(Worm worm) throws ModelException {
        worm.terminate();
    }

    /**
     * Check whether the given worm is terminated.
     *
     * @param worm
     */
    @Override
    public boolean isTerminated(Worm worm) throws ModelException {
        return worm.isTerminated();
    }

    /**
     * Return the current location of the given worm.
     * - The resulting array contains the the x-coordinate of the given worm
     * followed by its y-coordinate.
     *
     * @param worm
     */
    @Override
    public double[] getLocation(Worm worm) throws ModelException {
        return worm.getLocationArray();
    }

    /**
     * Returns the current orientation of the given worm (in radians).
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public double getOrientation(Worm worm) throws ModelException {
        return worm.getOrientation();
    }

    /**
     * Returns the radius of the given worm.
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public double getRadius(Worm worm) throws ModelException {
        return worm.getRadius();
    }

    /**
     * Sets the radius of the given worm to the given value.
     * 
     * @param worm
     * 		  the worm who is going to move
     * @param newRadius
     * 		  the new radius of the worm
     * @post the new radius is equal to the given newRadius
     * 		 |worm.setRadius(newRadius)
     * @throws ModelException
     * 		   the worm throws an IllegalArgumentException
     * 		   |catch(IllegalArgumentException e)
     */
    @Override
    public void setRadius(Worm worm, double newRadius) throws ModelException {
        try {
            worm.setRadius(newRadius);
        }
        catch(IllegalArgumentException e) {
            throw new ModelException(e);
        }
    }

    /**
     * Returns the current number of action points of the given worm.
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public long getNbActionPoints(Worm worm) throws ModelException {
        return worm.getActionPoints();
    }

    /**
     * Decreases the current number of action points of the given worm with the
     * given delta.
     * 
     * @param worm
     * 		  the worm who is going to move
     * @param delta
     * 		  the value that schould be decreased
     * @post the action points are decreased with delta
     * 		 |worm.decreaseActionPoints((int) delta)
     */
    @Override
    public void decreaseNbActionPoints(Worm worm, long delta) throws ModelException {
        worm.decreaseActionPoints((int) delta);
    }

    /**
     * Return the current number of hit points of the given worm.
     *
     * @param worm
     */
    @Override
    public BigInteger getNbHitPoints(Worm worm) throws ModelException {
        return BigInteger.valueOf(worm.getHitPoints());
    }

    /**
     * Increment the current number of hit points of the given worm
     * with the given delta.
     * - The given delta may be negative.
     *
     * @param worm
     * @param delta
     */
    @Override
    public void incrementNbHitPoints(Worm worm, long delta) throws ModelException {
        worm.incrementHitPoints(delta);
    }

    /**
     * Returns the maximum number of action points of the given worm.
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public long getMaxNbActionPoints(Worm worm) throws ModelException {
        return worm.getMaxActionPoints();
    }

    /**
     * Returns the name the given worm.
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public String getName(Worm worm) throws ModelException {
        return worm.getName();
    }

    /**
     * Renames the given worm.
     * 
     * @param worm
     *        the worm who is going to move
     * @param newName
     * 		  the new name for the worm
     * @post the new name of the worm is equal to the given newName
     * 		 |worm.setName(newName)
     * @throws ModelException
     * 		   the worm throws an IllegalNameException
     * 		   |catch(IllegalNameException e)
     */
    @Override
    public void rename(Worm worm, String newName) throws ModelException {
        try {
            worm.setName(newName);
        }
        catch(IllegalNameException e) {
            throw new ModelException(e);
        }
    }

    /**
     * Return the world to which this worm belongs
     *
     * @param worm
     */
    @Override
    public World getWorld(Worm worm) throws ModelException {
        return worm.getWorld();
    }

    /**
     * Returns the mass of the given worm.
     * 
     * @param worm
     * 		  the worm who is going to move
     */
    @Override
    public double getMass(Worm worm) throws ModelException {
        return worm.getMass();
    }

    /**
     * Create a new team for the given world with given name and with no members yet.
     */
    @Override
    public Team createTeam(World world, String name) throws ModelException, MustNotImplementException {
        try {
            return new Team(name);
        } catch(IllegalNameException e) {
            throw new ModelException(e.getMessage());
        }
    }

    /**
     * Terminate the given team.
     */
    @Override
    public void terminate(Team team) throws ModelException, MustNotImplementException {
        team.terminate();
    }

    /**
     * Check whether the given portion of food is terminated.
     */
    @Override
    public boolean isTerminated(Team team) throws ModelException, MustNotImplementException {
        return team.isTerminated();
    }

    /**
     * Return the name of the given team.
     */
    @Override
    public String getName(Team team) throws ModelException, MustNotImplementException {
        return team.getName();
    }

    /**
     * Return the team to which this worm belongs.
     */
    @Override
    public Team getTeam(Worm worm) throws ModelException {
        return worm.getTeam();
    }

    /**
     * Return the number of worms in the given team.
     */
    @Override
    public int getNbWormsOfTeam(Team team) throws ModelException, MustNotImplementException {
        return team.getNbWorms();
    }

    /**
     * Return a list of all the worms in the given team, sorted alphabetically.
     * This method must run in linear time.
     */
    @Override
    public List<Worm> getAllWormsOfTeam(Team team) throws ModelException, MustNotImplementException {
        return (List<Worm>) team.getAllWormsOfTeam();
    }

    /**
     * Add the given worms to the given team.]
     */
    @Override
    public void addWormsToTeam(Team team, Worm... worms) throws ModelException, MustNotImplementException {
        team.addWorm(worms);
    }

    /**
     * Remove the given worms from the given team.
     */
    @Override
    public void removeWormsFromTeam(Team team, Worm... worms) throws ModelException, MustNotImplementException {
        team.removeWormsFromTeam(worms);
    }

    /**
     * Merge the given teams.
     *   - All the worms of the supplying team are transferred to the receiving team.
     */
    @Override
    public void mergeTeams(Team receivingTeam, Team supplyingTeam) throws ModelException, MustNotImplementException {
        Team.mergeTeams(receivingTeam, supplyingTeam);
    }
}
