package worms.model;

import be.kuleuven.cs.som.annotate.Raw;
import worms.util.Coordinate;

import static java.lang.Math.PI;
import static java.lang.Math.pow;
import static java.lang.Math.round;

public abstract class GameObject {

    GameObject(World world, double[] location, double radius) {
        setWorld(world);
        world.add(this);
        setLocation(location);
        setRadius(radius);
    }

    /**
     *
     * @param world ...
     * @return ...
     */
    public static boolean isValidWorld(World world) {
        return !world.hasActiveGame() && !world.isTerminated();
    }
    /**
     *
     * @return World
     */
    public World getWorld() {
        return this.world;
    }
    public void setWorld(World world) {
        if (world == null) {
            this.world = null;
            return;
        }
        if (!isValidWorld(world)) throw new IllegalArgumentException();
        this.world = world;
    }

    private World world;

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

    /**
     * Terminate the object
     */
    public void terminate() {
        this.terminated = true;
        getWorld().remove(this);
    }
    private boolean terminated = false;



    /**
     * this variable contains the location of the worm (a Coordinate)
     */
    private Coordinate location;

    /**
     * Return the location of the game object
     * 		the location of the worm expresses the place of the game object
     * 		in the play area
     */
    Coordinate getLocation() {
        return this.location;
    }
    public double[] getLocationArray() {
        return this.location.toArray();
    }

    /**
     * set the location of the worm to the given location
     *
     * @param location
     * 		  the new location for the game object
     * @post  the new location of the game object is equal to the given location
     * 		  |new.getLocation() == location
     * @throws IllegalArgumentException
     * 		   the given location is not a valid location for a game object
     * 		   |! isValidLocation(location)
     */
    void setLocation(double[] location) throws IllegalArgumentException {

        Coordinate locationCoordinate = Coordinate.create(location);

        if (!isValidLocation(locationCoordinate)) throw new IllegalArgumentException();
        this.location = locationCoordinate;
    }

    void setLocation(Coordinate location) throws IllegalArgumentException {

        if (!isValidLocation(location)) throw new IllegalArgumentException();
        this.location = location;
    }


    /**
     * This variable contains the radius of the game object
     */
    double radius;

    /**
     * Return the radius of the game object
     * 		the radius of the game object expresses half of the
     * 		width of the game object
     */
    public double getRadius(){
        return this.radius;
    }
    void setRadius(double radius) {
        if (!canHaveAsRadius(radius)) throw new IllegalArgumentException();
        this.radius = radius;
    }

    /**
     * Check whether the given radius is a valid radius for the worm
     *
     * @param radius
     * 		  the radius to check
     * @return True if and only if the radius is bigger then the minimum radius
     * 	       (or equal) and the radius is a number
     * 		   |result == (radius >= getMinRadius() && !Double.isNaN(radius))
     */
    @Raw
    private boolean canHaveAsRadius(double radius) {
        return !Double.isNaN(radius) && radius > 0;
    }

    boolean isValidLocation(Coordinate location) {
        double radius = getRadius();

        if (world == null) {
            return !Double.isNaN(location.getX()) &&
                    !Double.isNaN(location.getY());
        }
        return !Double.isNaN(location.getX()) &&
                !Double.isNaN(location.getY()) &&
                !(location.getX() - radius < 0) &&
                !(location.getX() + radius > getWorld().getWidth()) &&
                !(location.getY() + radius > getWorld().getHeight()) &&
                !(location.getY() - radius < 0 &&
                !getWorld().isPassable(location));
    }


    double mass;
    public double getMass() {
        return this.mass;
    }

    /**
     * set the mass of the worm to the given mass (dependent on the radius)
     *
     * @param radius
     * 		  part of the formula to calculate the mass
     * @post  the new mass of the worm is equal to
     * 		  rho * (4 / 3 * Math.PI * Math.pow(radius, 3))
     * 		  |new.getMass() == rho * (4 / 3 * Math.PI * Math.pow(radius, 3))
     */
    void setMass(double radius, double rho) {
        this.mass = (double) round(rho * (4.0 / 3.0 * PI * pow(radius, 3)));
    }
}