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

package pp.battleship.client.gui;

import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import pp.battleship.model.Battleship;
import pp.battleship.model.Shot;
import pp.util.Position;

/**
 * Synchronizes the visual representation of the ship map with the game model.
 * It handles the rendering of ships and shots on the map view, updating the view
 * whenever changes occur in the model.
 */
class MapViewSynchronizer extends ShipMapSynchronizer {
    // Constants for rendering properties
    private static final float SHIP_LINE_WIDTH = 6f;
    private static final float SHOT_DEPTH = -2f;
    private static final float SHIP_DEPTH = 0f;
    private static final float INDENT = 4f;

    // Colors used for different visual elements
    private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
    private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
    private static final ColorRGBA SHIP_BORDER_COLOR = ColorRGBA.White;
    private static final ColorRGBA PREVIEW_COLOR = ColorRGBA.Gray;
    private static final ColorRGBA ERROR_COLOR = ColorRGBA.Red;

    // The MapView associated with this synchronizer
    private final MapView view;

    /**
     * Constructs a new MapViewSynchronizer for the given MapView.
     * Initializes the synchronizer and adds existing elements from the model to the view.
     *
     * @param view the MapView to synchronize with the game model
     */
    public MapViewSynchronizer(MapView view) {
        super(view.getMap(), view.getNode());
        this.view = view;
        addExisting();
    }

    /**
     * Creates a visual representation of a shot on the map.
     * A hit shot is represented in red, while a miss is represented in blue.
     *
     * @param shot the Shot object representing the shot in the model
     * @return a Spatial representing the shot on the map
     */
    @Override
    public Spatial visit(Shot shot) {
        // Convert the shot's model coordinates to view coordinates
        final Position p1 = view.modelToView(shot.getX(), shot.getY());
        final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1);
        final ColorRGBA color = shot.isHit() ? HIT_COLOR : MISS_COLOR;

        // Create and return a rectangle representing the shot
        return view.getApp().getDraw().makeRectangle(p1.getX(), p1.getY(),
                                                     SHOT_DEPTH,
                                                     p2.getX() - p1.getX(), p2.getY() - p1.getY(),
                                                     color);
    }

    /**
     * Creates a visual representation of a battleship on the map.
     * The ship's border color depends on its status: normal, valid preview, or invalid preview.
     *
     * @param ship the Battleship object representing the ship in the model
     * @return a Spatial representing the ship on the map
     */
    @Override
    public Spatial visit(Battleship ship) {
        // Create a node to represent the ship
        final Node shipNode = new Node("ship"); //NON-NLS

        // Convert the ship's model coordinates to view coordinates
        final Position p1 = view.modelToView(ship.getMinX(), ship.getMinY());
        final Position p2 = view.modelToView(ship.getMaxX() + 1, ship.getMaxY() + 1);

        // Calculate the coordinates for the ship's bounding box
        final float x1 = p1.getX() + INDENT;
        final float y1 = p1.getY() + INDENT;
        final float x2 = p2.getX() - INDENT;
        final float y2 = p2.getY() - INDENT;

        // Determine the color based on the ship's status
        final ColorRGBA color = switch (ship.getStatus()) {
            case NORMAL -> SHIP_BORDER_COLOR;
            case VALID_PREVIEW -> PREVIEW_COLOR;
            case INVALID_PREVIEW -> ERROR_COLOR;
        };

        // Add the ship's borders to the node
        shipNode.attachChild(shipLine(x1, y1, x2, y1, color));
        shipNode.attachChild(shipLine(x1, y2, x2, y2, color));
        shipNode.attachChild(shipLine(x1, y1, x1, y2, color));
        shipNode.attachChild(shipLine(x2, y1, x2, y2, color));

        // Return the complete ship representation
        return shipNode;
    }

    /**
     * Creates a line geometry representing part of the ship's border.
     *
     * @param x1    the starting x-coordinate of the line
     * @param y1    the starting y-coordinate of the line
     * @param x2    the ending x-coordinate of the line
     * @param y2    the ending y-coordinate of the line
     * @param color the color of the line
     * @return a Geometry representing the line
     */
    private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) {
        return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH);
    }
}
