////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.battleship.model;

import com.jme3.network.serializing.Serializable;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import static java.lang.Math.max;
import static java.lang.Math.min;

/**
 * Represents a battleship in the game. A battleship is characterized by its length, position,
 * rotation, and status. It can be moved, rotated, and hit during the game. This class also
 * provides methods to check for collisions with other ships and to determine whether the
 * battleship has been destroyed.
 */
@Serializable
public class Battleship implements Item {
    /**
     * Enumeration representing the different statuses a battleship can have during the game.
     */
    public enum Status {
        /**
         * The ship is in its normal state, not being previewed for placement.
         */
        NORMAL,

        /**
         * The ship is being previewed in a valid position for placement.
         */
        VALID_PREVIEW,

        /**
         * The ship is being previewed in an invalid position for placement.
         */
        INVALID_PREVIEW
    }

    private final int length; // The length of the battleship
    private int x;            // The x-coordinate of the battleship's position
    private int y;            // The y-coordinate of the battleship's position
    private Rotation rot;     // The rotation of the battleship
    private Status status;    // The current status of the battleship
    private final Set<IntPoint> damaged = new HashSet<>(); // The set of positions that have been hit on this ship

    /**
     * Default constructor for serialization. Initializes a battleship with length 0,
     * at position (0, 0), with a default rotation of RIGHT.
     */
    private Battleship() {
        this(0, 0, 0, Rotation.RIGHT);
    }

    /**
     * Constructs a new Battleship with the specified length, position, and rotation.
     *
     * @param length the length of the battleship
     * @param x      the x-coordinate of the battleship's initial position
     * @param y      the y-coordinate of the battleship's initial position
     * @param rot    the rotation of the battleship
     */
    public Battleship(int length, int x, int y, Rotation rot) {
        this.x = x;
        this.y = y;
        this.rot = rot;
        this.length = length;
        this.status = Status.NORMAL;
    }

    /**
     * Returns the current x-coordinate of the battleship's position.
     *
     * @return the x-coordinate of the battleship
     */
    public int getX() {
        return x;
    }

    /**
     * Returns the current y-coordinate of the battleship's position.
     *
     * @return the y-coordinate of the battleship
     */
    public int getY() {
        return y;
    }

    /**
     * Moves the battleship to the specified coordinates.
     *
     * @param x the new x-coordinate of the battleship's position
     * @param y the new y-coordinate of the battleship's position
     */
    public void moveTo(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Moves the battleship to the specified position.
     *
     * @param pos the new position of the battleship
     */
    public void moveTo(IntPosition pos) {
        moveTo(pos.getX(), pos.getY());
    }

    /**
     * Returns the current status of the battleship.
     *
     * @return the status of the battleship
     */
    public Status getStatus() {
        return status;
    }

    /**
     * Sets the status of the battleship.
     *
     * @param status the new status to be set for the battleship
     */
    public void setStatus(Status status) {
        this.status = status;
    }

    /**
     * Returns the length of the battleship.
     *
     * @return the length of the battleship
     */
    public int getLength() {
        return length;
    }

    /**
     * Returns the minimum x-coordinate that the battleship occupies based on its current position and rotation.
     *
     * @return the minimum x-coordinate of the battleship
     */
    public int getMinX() {
        return x + min(0, (length - 1) * rot.dx());
    }

    /**
     * Returns the maximum x-coordinate that the battleship occupies based on its current position and rotation.
     *
     * @return the maximum x-coordinate of the battleship
     */
    public int getMaxX() {
        return x + max(0, (length - 1) * rot.dx());
    }

    /**
     * Returns the minimum y-coordinate that the battleship occupies based on its current position and rotation.
     *
     * @return the minimum y-coordinate of the battleship
     */
    public int getMinY() {
        return y + min(0, (length - 1) * rot.dy());
    }

    /**
     * Returns the maximum y-coordinate that the battleship occupies based on its current position and rotation.
     *
     * @return the maximum y-coordinate of the battleship
     */
    public int getMaxY() {
        return y + max(0, (length - 1) * rot.dy());
    }

    /**
     * Returns the current rotation of the battleship.
     *
     * @return the rotation of the battleship
     */
    public Rotation getRot() {
        return rot;
    }

    /**
     * Sets the rotation of the battleship.
     *
     * @param rot the new rotation to be set for the battleship
     */
    public void setRotation(Rotation rot) {
        this.rot = rot;
    }

    /**
     * Rotates the battleship by 90 degrees clockwise.
     */
    public void rotated() {
        setRotation(rot.rotate());
    }

    /**
     * Attempts to hit the battleship at the specified position.
     * If the position is part of the battleship, the hit is recorded.
     *
     * @param x the x-coordinate of the position to hit
     * @param y the y-coordinate of the position to hit
     * @return true if the position is part of the battleship, false otherwise
     * @see #contains(int, int)
     */
    public boolean hit(int x, int y) {
        if (!contains(x, y))
            return false;
        damaged.add(new IntPoint(x, y));
        return true;
    }

    /**
     * Attempts to hit the battleship at the specified position.
     * If the position is part of the battleship, the hit is recorded.
     * This is a convenience method for {@linkplain #hit(int, int)}.
     *
     * @param position the position to hit
     * @return true if the position is part of the battleship, false otherwise
     */
    public boolean hit(IntPosition position) {
        return hit(position.getX(), position.getY());
    }

    /**
     * Returns the positions of this battleship that have been hit.
     *
     * @return a set of positions that have been hit
     * @see #hit(int, int)
     */
    public Set<IntPoint> getDamaged() {
        return Collections.unmodifiableSet(damaged);
    }

    /**
     * Checks whether the specified position is covered by the battleship. This method does
     * not record a hit, only checks coverage.
     * This is a convenience method for {@linkplain #contains(int, int)}.
     *
     * @param pos the position to check
     * @return true if the position is covered by the battleship, false otherwise
     */
    public boolean contains(IntPosition pos) {
        return contains(pos.getX(), pos.getY());
    }

    /**
     * Checks whether the specified position is covered by the battleship. This method does
     * not record a hit, only checks coverage.
     *
     * @param x the x-coordinate of the position to check
     * @param y the y-coordinate of the position to check
     * @return true if the position is covered by the battleship, false otherwise
     */
    public boolean contains(int x, int y) {
        return getMinX() <= x && x <= getMaxX() &&
               getMinY() <= y && y <= getMaxY();
    }

    /**
     * Determines if the battleship has been completely destroyed. A battleship is considered
     * destroyed if all of its positions have been hit.
     *
     * @return true if the battleship is destroyed, false otherwise
     * @see #hit(int, int)
     */
    public boolean isDestroyed() {
        return damaged.size() == length;
    }

    /**
     * Checks whether this battleship collides with another battleship. Two battleships collide
     * if any of their occupied positions overlap.
     *
     * @param other the other battleship to check collision with
     * @return true if the battleships collide, false otherwise
     */
    public boolean collidesWith(Battleship other) {
        return other.getMaxX() >= getMinX() && getMaxX() >= other.getMinX() &&
               other.getMaxY() >= getMinY() && getMaxY() >= other.getMinY();
    }

    /**
     * Returns a string representation of the battleship, including its length, position,
     * and rotation.
     *
     * @return a string representation of the battleship
     */
    @Override
    public String toString() {
        return "Ship{length=" + length + ", x=" + x + ", y=" + y + ", rot=" + rot + '}';  //NON-NLS
    }

    /**
     * Accepts a visitor that returns a value of type {@code T}. This method is part of the
     * Visitor design pattern.
     *
     * @param visitor the visitor to accept
     * @param <T>     the type of the value returned by the visitor
     * @return the value returned by the visitor
     */
    @Override
    public <T> T accept(Visitor<T> visitor) {
        return visitor.visit(this);
    }

    /**
     * Accepts a visitor that does not return a value. This method is part of the
     * Visitor design pattern.
     *
     * @param visitor the visitor to accept
     */
    @Override
    public void accept(VoidVisitor visitor) {
        visitor.visit(this);
    }
}
