// License: WTFPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.imagery_offset_db;

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

import java.awt.event.ActionEvent;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JOptionPane;

import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.data.UserIdentityManager;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
import org.openstreetmap.josm.tools.Logging;

/**
 * Upload the current imagery offset or an calibration geometry information.
 *
 * @author Zverik
 * @license WTFPL
 */
public class StoreImageryOffsetAction extends JosmAction {

    /**
     * Initializes the action.
     */
    public StoreImageryOffsetAction() {
        super(tr("Store Imagery Offset..."), "storeoffset",
                tr("Upload an offset for current imagery (or calibration object geometry) to a server"),
                null, true);
    }

    /**
     * Asks user for description and calls the upload task.
     * Also calculates a lot of things, checks whether the selected object
     * is suitable for calibration geometry, constructs a map of query parameters etc.
     * The only thing it doesn't do is check for the real user account name.
     * This is because all server queries should be executed in workers,
     * and we don't have one when a user name is needed.
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (!MainApplication.isDisplayingMapView())
            return;

        AbstractTileSourceLayer<?> layer = ImageryOffsetTools.getTopImageryLayer();
        if (layer == null)
            return;

        String userName = UserIdentityManager.getInstance().getUserName();
        if (userName == null || userName.length() == 0) {
            JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
                    tr("To store imagery offsets you must be a registered OSM user."),
                    ImageryOffsetTools.DIALOG_TITLE, JOptionPane.ERROR_MESSAGE);
            return;
        }
        if (userName.indexOf('@') > 0)
            userName = userName.replace('@', ',');

        // check if an object suitable for calibration is selected
        OsmPrimitive calibration = null;
        if (getLayerManager().getEditDataSet() != null) {
            Collection<OsmPrimitive> selectedObjects = getLayerManager().getEditDataSet().getSelected();
            if (selectedObjects.size() == 1) {
                OsmPrimitive selection = selectedObjects.iterator().next();
                if (!selection.isIncomplete() && (selection instanceof Way ||
                        (selection instanceof Node && !((Node) selection).isReferredByWays(1)))) {
                    String[] options = new String[] {tr("Store calibration geometry"), tr("Store imagery offset")};
                    int result = JOptionPane.showOptionDialog(MainApplication.getMainFrame(),
                            tr("The selected object can be used as a calibration geometry. What do you intend to do?"),
                            ImageryOffsetTools.DIALOG_TITLE, JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE,
                            null, options, options[0]);
                    if (result == 2 || result == JOptionPane.CLOSED_OPTION)
                        return;
                    if (result == 0)
                        calibration = selection;
                }
            }
        }

        Object message;
        LatLon center = ImageryOffsetTools.getMapCenter();
        ImageryOffsetBase offsetObj;
        if (calibration == null) {
            // register imagery offset
            if (Math.abs(layer.getDisplaySettings().getDx()) < 1e-8 && Math.abs(layer.getDisplaySettings().getDy()) < 1e-8) {
                if (JOptionPane.showConfirmDialog(MainApplication.getMainFrame(),
                        tr("The topmost imagery layer has no offset. Are you sure you want to upload this?"),
                        ImageryOffsetTools.DIALOG_TITLE, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
                    return;
            }
            LatLon offset = ImageryOffsetTools.getLayerOffset(layer, center);
            offsetObj = new ImageryOffset(ImageryOffsetTools.getImageryID(layer), offset);
            message = tr("You are registering an imagery offset. Other users in this area will be able to use it for mapping.\n"
                    + "Please make sure it is as precise as possible, and describe a region this offset is applicable to.");
        } else {
            // register calibration object
            offsetObj = new CalibrationObject(calibration);
            message = tr("You are registering a calibration geometry. It should be the most precisely positioned object, with\n"
                    + "clearly visible boundaries on various satellite imagery. Please describe this object and its whereabouts.");
        }
        String description = queryDescription(message);
        if (description == null)
            return;
        offsetObj.setBasicInfo(center, userName, null, null);
        offsetObj.setDescription(description);

        // upload object info to server
        try {
            Map<String, String> params = new HashMap<>();
            offsetObj.putServerParams(params);
            StringBuilder query = null;
            for (String key : params.keySet()) {
                if (query == null) {
                    query = new StringBuilder("store?");
                } else {
                    query.append('&');
                }
                query.append(key).append('=').append(URLEncoder.encode(params.get(key), "UTF8"));
            }
            MainApplication.worker.submit(new SimpleOffsetQueryTask(query.toString(), tr("Uploading a new offset...")));
        } catch (UnsupportedEncodingException ex) {
            Logging.error(ex);
        }
    }

    /**
     * Ask a user for a description / reason. This string should be 3 to 200 characters
     * long, and the method enforces that.
     * @param message A prompt for the input dialog.
     * @return Either null or a string 3 to 200 letters long.
     */
    public static String queryDescription(Object message) {
        String reason = null;
        boolean iterated = false;
        boolean ok = false;
        while (!ok) {
            Object result = JOptionPane.showInputDialog(MainApplication.getMainFrame(), message,
                    ImageryOffsetTools.DIALOG_TITLE, JOptionPane.PLAIN_MESSAGE, null, null, reason);
            if (result == null || result.toString().length() == 0) {
                return null;
            }
            reason = result.toString();
            if (reason.length() < 3 || reason.length() > 200) {
                if (!iterated) {
                    message = message + "\n" + tr("This string should be 3 to 200 letters long.");
                    iterated = true;
                }
            } else
                ok = true;
        }
        return reason;
    }

    /**
     * This action is enabled when there's a map and a visible imagery layer.
     * Note that it doesn't require edit layer.
     */
    @Override
    protected void updateEnabledState() {
        boolean state = true;
        if (!MainApplication.isDisplayingMapView() || !MainApplication.getMap().isVisible())
            state = false;
        if (ImageryOffsetTools.getTopImageryLayer() == null)
            state = false;
        setEnabled(state);
    }
}
