/*
 * Copyright (C) 2025  Tim Zimmermann <tim@aglais.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.aglais.zms.ui.dialog;

import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import org.aglais.zms.ui.component.MapPanel;
import org.aglais.zms.ui.map.RadiusWaypoint;
import org.aglais.zms.ui.map.RadiusWaypointRenderer;
import org.jxmapviewer.viewer.GeoPosition;
import org.jxmapviewer.viewer.WaypointPainter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.ResourceBundle;
import java.util.Set;

/**
 * A dialog that allows users to select coordinates on a map.
 * Users can click on the map to place a waypoint and confirm or cancel their selection.
 */
public class MapCoordinateChooser extends JDialog {

    /**
     * Return value if cancel is chosen.
     */
    public static final int CANCEL_OPTION = 1;

    /**
     * Return value if approve (yes, ok) is chosen.
     */
    public static final int APPROVE_OPTION = 0;

    /**
     * Return value if an error occurred.
     */
    public static final int ERROR_OPTION = -1;

    private JPanel contentPane;
    private MapPanel map;
    private JButton okButton;
    private JButton cancelButton;
    private JSpinner radiusSpinner;
    private final WaypointPainter<RadiusWaypoint> waypointPainter = new WaypointPainter<org.aglais.zms.ui.map.RadiusWaypoint>();
    private GeoPosition position;
    private int radius = 0;
    private int returnValue = ERROR_OPTION;

    /**
     * Constructs a new MapCoordinateChooser dialog.
     * Initializes the UI components and sets up event listeners for user interactions.
     *
     * @param parent The parent component
     */
    public MapCoordinateChooser(Component parent) {
        super(SwingUtilities.getWindowAncestor(parent));
        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(okButton);

        okButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });

        cancelButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        // call onCancel() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });

        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

        waypointPainter.setRenderer(new RadiusWaypointRenderer());
        map.addOverlayPainter(waypointPainter);
        map.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                GeoPosition position = map.convertPointToGeoPosition(new Point2D.Float(e.getX(), e.getY()));

                setSelectedPosition(position, radius);
            }
        });

        radiusSpinner.addChangeListener((e) -> setSelectedPosition(position, (int) radiusSpinner.getValue()));

        ResourceBundle uiComponent = ResourceBundle.getBundle("ui_component");
        setTitle(uiComponent.getString("dialog_map_coordinate_chooser_title"));
    }

    /**
     * Handles the OK button click event.
     * Closes the dialog when the user confirms their selection.
     */
    private void onOK() {
        returnValue = APPROVE_OPTION;

        dispose();
    }

    /**
     * Handles the Cancel button click event.
     * Closes the dialog when the user cancels the selection.
     */
    private void onCancel() {
        returnValue = CANCEL_OPTION;

        dispose();
    }

    /**
     * Sets the currently selected geographic position and waypoint radius.
     * Updates the waypoint shown on the map to the specified position.
     *
     * @param position The {@link GeoPosition} to set as the selected coordinate
     * @param radius   The radius of the waypoint in meters
     */
    public void setSelectedPosition(GeoPosition position, int radius) {
        this.position = position;
        this.radius = radius;

        if (position != null) {
            RadiusWaypoint waypoint = new RadiusWaypoint(position, radius);

            waypointPainter.setWaypoints(Set.of(waypoint));
        } else {
            waypointPainter.setWaypoints(Set.of());
        }
        radiusSpinner.setValue(radius);
        map.repaint();
    }

    /**
     * Returns the currently selected geographic position.
     *
     * @return The selected {@link GeoPosition}, or {@code null} if no position has been selected
     */
    public GeoPosition getSelectedPosition() {
        return position;
    }

    /**
     * Returns the radius of the currently selected waypoint.
     *
     * @return The radius in meters of the selected waypoint, or 0 if no radius has been set
     */
    public int getSelectedRadius() {
        return radius;
    }

    /**
     * Zooms the map to the currently selected position with the specified radius.
     * This method centers the map view on the position and adjusts the zoom level
     * to include the given radius around it.
     */
    public void zoomToSelectedPosition() {
        if (position != null)
            map.zoomTo(position, radius);
    }

    /**
     * Displays the dialog to the user, blocking until the dialog is closed.
     * <p>
     * This method packs the dialog, makes it visible, and waits for the user
     * to either approve or cancel the coordinate selection.
     * </p>
     *
     * @param parent The parent dialog
     *
     * @return An integer indicating the user's action:
     * {@link #APPROVE_OPTION} if the user confirmed the selection,
     * {@link #CANCEL_OPTION} if the user cancelled the selection,
     * or {@link #ERROR_OPTION} if an error occurred or the dialog was closed unexpectedly.
     */
    public int showDialog(Component parent) {
        returnValue = ERROR_OPTION;

        pack();
        setLocationRelativeTo(parent);
        setVisible(true);

        return returnValue;
    }
}