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

package pp.battleship.game.client;

import pp.battleship.message.client.MapMessage;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.ShipMap;
import pp.battleship.model.dto.ShipMapDTO;

import java.io.File;
import java.io.IOException;
import java.lang.System.Logger.Level;

import static pp.battleship.Resources.lookup;
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
import static pp.battleship.model.Battleship.Status.NORMAL;
import static pp.battleship.model.Battleship.Status.VALID_PREVIEW;
import static pp.battleship.model.Rotation.RIGHT;

/**
 * Represents the state of the client setting up the ship map.
 */
class EditorState extends ClientState {
    private Battleship preview;
    private Battleship selectedInHarbor;

    /**
     * Constructs a new EditorState with the specified ClientGameLogic.
     *
     * @param logic the ClientGameLogic associated with this state
     */
    public EditorState(ClientGameLogic logic) {
        super(logic);
    }

    /**
     * Returns true to indicate that the editor should be shown.
     *
     * @return true if the editor should be shown
     */
    @Override
    public boolean showEditor() {
        return true;
    }

    /**
     * Moves the preview ship to the specified position.
     *
     * @param pos the new position for the preview ship
     */
    @Override
    public void movePreview(IntPoint pos) {
        ClientGameLogic.LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
        if (preview == null || !ownMap().isValid(pos)) return;
        preview.moveTo(pos);
        setPreviewStatus(preview);
        ownMap().remove(preview);
        ownMap().add(preview);
    }

    /**
     * Handles a click on the player's own map.
     *
     * @param pos the position where the click occurred
     */
    @Override
    public void clickOwnMap(IntPoint pos) {
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
        if (!ownMap().isValid(pos)) return;
        if (preview == null)
            modifyShip(pos);
        else
            placeShip(pos);
    }

    /**
     * Modifies a ship on the map at the specified position.
     *
     * @param cursor the position of the ship to modify
     */
    private void modifyShip(IntPoint cursor) {
        preview = ownMap().findShipAt(cursor);
        if (preview == null)
            return;
        preview.moveTo(cursor);
        setPreviewStatus(preview);
        ownMap().remove(preview);
        ownMap().add(preview);
        selectedInHarbor = new Battleship(preview.getLength(), 0, freeY(), RIGHT);
        selectedInHarbor.setStatus(VALID_PREVIEW);
        harbor().add(selectedInHarbor);
    }

    /**
     * Places the preview ship at the specified position.
     *
     * @param cursor the position to place the ship
     */
    private void placeShip(IntPoint cursor) {
        ownMap().remove(preview);
        preview.moveTo(cursor);
        if (ownMap().isValid(preview)) {
            preview.setStatus(NORMAL);
            ownMap().add(preview);
            harbor().remove(selectedInHarbor);
            preview = null;
            selectedInHarbor = null;
        }
        else {
            preview.setStatus(INVALID_PREVIEW);
            ownMap().add(preview);
        }
    }

    /**
     * Handles a click on the harbor map.
     *
     * @param pos the position where the click occurred
     */
    @Override
    public void clickHarbor(IntPoint pos) {
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
        if (!harbor().isValid(pos)) return;
        final Battleship shipAtCursor = harbor().findShipAt(pos);
        if (preview != null) {
            ownMap().remove(preview);
            selectedInHarbor.setStatus(NORMAL);
            harbor().remove(selectedInHarbor);
            harbor().add(selectedInHarbor);
            preview = null;
            selectedInHarbor = null;
        }
        else if (shipAtCursor != null) {
            selectedInHarbor = shipAtCursor;
            selectedInHarbor.setStatus(VALID_PREVIEW);
            harbor().remove(selectedInHarbor);
            harbor().add(selectedInHarbor);
            preview = new Battleship(selectedInHarbor.getLength(), 0, 0, RIGHT);
            setPreviewStatus(preview);
            ownMap().add(preview);
        }
    }

    /**
     * Rotates the preview ship.
     */
    @Override
    public void rotateShip() {
        ClientGameLogic.LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
        if (preview == null) return;
        preview.rotated();
        ownMap().remove(preview);
        ownMap().add(preview);
    }

    /**
     * Finds a free position in the harbor to place a ship.
     *
     * @return the y coordinate of a free position in the harbor
     */
    private int freeY() {
        for (int i = 0; i < harbor().getHeight(); i++)
            if (harbor().findShipAt(0, i) == null)
                return i;
        throw new RuntimeException("Cannot find a free slot in harbor");
    }

    /**
     * Updates the status of the specified ship based on its validity.
     */
    private void setPreviewStatus(Battleship ship) {
        ship.setStatus(ownMap().isValid(ship) ? VALID_PREVIEW : INVALID_PREVIEW);
    }

    /**
     * The user has marked the map as finished.
     */
    @Override
    public void mapFinished() {
        if (!harbor().getItems().isEmpty()) return;
        logic.send(new MapMessage(ownMap().getRemainingShips()));
        logic.setInfoText("wait.for.opponent");
        logic.setState(new WaitState(logic));
    }

    /**
     * Checks if the player's map is complete (i.e., all ships are placed).
     *
     * @return true if all ships are placed, false otherwise
     */
    @Override
    public boolean isMapComplete() {
        return harbor().getItems().isEmpty();
    }

    /**
     * Checks if there is currently a preview ship.
     *
     * @return true if there is currently a preview ship, false otherwise
     */
    @Override
    public boolean movingShip() {
        return preview != null;
    }

    /**
     * Returns the player's own map.
     *
     * @return the player's own map
     */
    private ShipMap ownMap() {
        return logic.getOwnMap();
    }

    /**
     * Returns the harbor map.
     *
     * @return the harbor map
     */
    private ShipMap harbor() {
        return logic.getHarbor();
    }

    /**
     * Loads a map from the specified file.
     *
     * @param file the file to load the map from
     * @throws IOException if the map cannot be loaded
     */
    @Override
    public void loadMap(File file) throws IOException {
        final ShipMapDTO dto = ShipMapDTO.loadFrom(file);
        if (!dto.fits(logic.getDetails()))
            throw new IOException(lookup("map.doesnt.fit"));
        ownMap().clear();
        dto.getShips().forEach(ownMap()::add);
        harbor().clear();
        preview = null;
        selectedInHarbor = null;
    }

    /**
     * Checks if the player's own map may be loaded from a file.
     *
     * @return true if the own map may be loaded from file, false otherwise
     */
    @Override
    public boolean mayLoadMap() {
        return true;
    }

    /**
     * Checks if the player's own map may be saved to a file.
     *
     * @return true if the own map may be saved to file, false otherwise
     */
    @Override
    public boolean maySaveMap() {
        return harbor().getItems().isEmpty();
    }
}
