// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.command;

import static org.openstreetmap.josm.tools.I18n.trn;

import java.util.Collection;
import java.util.Objects;

import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;

/**
 * RotateCommand rotates a number of objects around their centre.
 *
 * @author Frederik Ramm
 */
public class RotateCommand extends TransformNodesCommand {

    /**
     * Pivot point
     */
    private final EastNorth pivot;

    /**
     * angle of rotation starting click to pivot
     */
    private double startAngle;

    /**
     * computed rotation angle between starting click and current mouse pos
     */
    private double rotationAngle;

    private double deltaAngle;

    /**
     * Creates a RotateCommand.
     * Assign the initial object set, compute pivot point and initial rotation angle.
     * @param objects objects to fetch nodes from
     * @param currentEN current east/north
     */
    public RotateCommand(Collection<? extends OsmPrimitive> objects, EastNorth currentEN) {
        super(objects);

        pivot = getNodesCenter();
        startAngle = getAngle(currentEN);
        rotationAngle = 0.0;

        handleEvent(currentEN);
    }

    /**
     * Get angle between the horizontal axis and the line formed by the pivot and given point.
     * @param currentEN current east/north
     * @return angle between the horizontal axis and the line formed by the pivot and given point
     **/
    protected final double getAngle(EastNorth currentEN) {
        if (pivot == null)
            return 0.0; // should never happen by contract
        return Math.atan2(currentEN.east()-pivot.east(), currentEN.north()-pivot.north());
    }

    /**
     * Compute new rotation angle and transform nodes accordingly.
     */
    @Override
    public final void handleEvent(EastNorth currentEN) {
        double currentAngle = getAngle(currentEN);
        rotationAngle = currentAngle - startAngle + deltaAngle;
        transformNodes();
    }

    /**
     * Handle a repeated rotation action where the mouse was moved to a different position
     * see #24695
     * @param startEN start cursor position of a repeated rotation
     * @since 19566
     */
    public void handleUpdate(EastNorth startEN) {
        deltaAngle = rotationAngle;
        startAngle = getAngle(startEN);
    }

    /**
     * Set the rotation angle.
     * @param rotationAngle The rotate angle
     */
    protected void setRotationAngle(double rotationAngle) {
        this.rotationAngle = rotationAngle;
    }

    /**
     * Returns the rotation angle.
     * @return The rotation angle
     */
    public double getRotationAngle() {
        return rotationAngle;
    }

    /**
     * Rotate nodes.
     */
    @Override
    protected void transformNodes() {
        double cosPhi = Math.cos(rotationAngle);
        double sinPhi = Math.sin(rotationAngle);
        for (Node n : nodes) {
            EastNorth oldEastNorth = oldStates.get(n).getEastNorth();
            double x = oldEastNorth.east() - pivot.east();
            double y = oldEastNorth.north() - pivot.north();
            // CHECKSTYLE.OFF: SingleSpaceSeparator
            double nx =  cosPhi * x + sinPhi * y + pivot.east();
            double ny = -sinPhi * x + cosPhi * y + pivot.north();
            // CHECKSTYLE.ON: SingleSpaceSeparator
            n.setEastNorth(new EastNorth(nx, ny));
        }
    }

    @Override
    public String getDescriptionText() {
        return trn("Rotate {0} node", "Rotate {0} nodes", nodes.size(), nodes.size());
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), pivot, startAngle, rotationAngle, deltaAngle);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        if (!super.equals(obj)) return false;
        RotateCommand that = (RotateCommand) obj;
        return Double.compare(that.startAngle, startAngle) == 0 &&
                Double.compare(that.rotationAngle, rotationAngle) == 0 &&
                Objects.equals(pivot, that.pivot) &&
                Objects.equals(deltaAngle, that.deltaAngle);
    }
}
