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

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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.oauth.OAuthParameters;
import org.openstreetmap.josm.data.oauth.OAuthToken;
import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel;
import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
import org.openstreetmap.josm.gui.oauth.TestAccessTokenTask;
import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
import org.openstreetmap.josm.gui.widgets.JosmTextField;
import org.openstreetmap.josm.io.OsmApi;
import org.openstreetmap.josm.io.auth.CredentialsManager;
import org.openstreetmap.josm.tools.ImageProvider;

/**
 * The preferences panel for the OAuth preferences. This just a summary panel
 * showing the current Access Token Key and Access Token Secret, if the
 * user already has an Access Token.
 *
 * For initial authorisation see {@link OAuthAuthorizationWizard}.
 *
 */
public class OAuthAuthenticationPreferencesPanel extends JPanel implements PropertyChangeListener {
    private JPanel pnlAuthorisationMessage;
    private NotYetAuthorisedPanel pnlNotYetAuthorised;
    private AlreadyAuthorisedPanel pnlAlreadyAuthorised;
    private AdvancedOAuthPropertiesPanel pnlAdvancedProperties;
    private String apiUrl;
    private JCheckBox cbShowAdvancedParameters;
    private JCheckBox cbSaveToPreferences;

    /**
     * Builds the panel for entering the advanced OAuth parameters
     *
     * @return panel with advanced settings
     */
    protected JPanel buildAdvancedPropertiesPanel() {
        JPanel pnl = new JPanel(new GridBagLayout());
        GridBagConstraints gc = new GridBagConstraints();

        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.weightx = 0.0;
        gc.insets = new Insets(0, 0, 0, 3);
        pnl.add(cbShowAdvancedParameters = new JCheckBox(), gc);
        cbShowAdvancedParameters.setSelected(false);
        cbShowAdvancedParameters.addItemListener(
                new ItemListener() {
                    @Override
                    public void itemStateChanged(ItemEvent evt) {
                        pnlAdvancedProperties.setVisible(evt.getStateChange() == ItemEvent.SELECTED);
                    }
                }
        );

        gc.gridx = 1;
        gc.weightx = 1.0;
        JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters"));
        lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
        pnl.add(lbl, gc);

        gc.gridy = 1;
        gc.gridx = 1;
        gc.insets = new Insets(3, 0, 3, 0);
        gc.fill = GridBagConstraints.BOTH;
        gc.weightx = 1.0;
        gc.weighty = 1.0;
        pnl.add(pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(), gc);
        pnlAdvancedProperties.initFromPreferences(Main.pref);
        pnlAdvancedProperties.setBorder(
                BorderFactory.createCompoundBorder(
                        BorderFactory.createLineBorder(Color.GRAY, 1),
                        BorderFactory.createEmptyBorder(3, 3, 3, 3)
                )
        );
        pnlAdvancedProperties.setVisible(false);
        return pnl;
    }

    /**
     * builds the UI
     */
    protected final void build() {
        setLayout(new GridBagLayout());
        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        GridBagConstraints gc = new GridBagConstraints();

        // the panel for the OAuth parameters. pnlAuthorisationMessage is an
        // empty panel. It is going to be filled later, depending on the
        // current OAuth state in JOSM.
        gc.fill = GridBagConstraints.BOTH;
        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.weighty = 1.0;
        gc.weightx = 1.0;
        gc.insets = new Insets(10, 0, 0, 0);
        add(pnlAuthorisationMessage = new JPanel(), gc);
        pnlAuthorisationMessage.setLayout(new BorderLayout());

        // create these two panels, they are going to be used later in refreshView
        //
        pnlAlreadyAuthorised = new AlreadyAuthorisedPanel();
        pnlNotYetAuthorised = new NotYetAuthorisedPanel();
    }

    protected void refreshView() {
        pnlAuthorisationMessage.removeAll();
        if (OAuthAccessTokenHolder.getInstance().containsAccessToken()) {
            pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER);
            pnlAlreadyAuthorised.refreshView();
            pnlAlreadyAuthorised.revalidate();
        } else {
            pnlAuthorisationMessage.add(pnlNotYetAuthorised, BorderLayout.CENTER);
            pnlNotYetAuthorised.revalidate();
        }
        repaint();
    }

    /**
     * Create the panel
     */
    public OAuthAuthenticationPreferencesPanel() {
        build();
        refreshView();
    }

    /**
     * Sets the URL of the OSM API for which this panel is currently displaying OAuth properties.
     *
     * @param apiUrl the api URL
     */
    public void setApiUrl(String apiUrl) {
        this.apiUrl = apiUrl;
        pnlAdvancedProperties.setApiUrl(apiUrl);
    }

    /**
     * Initializes the panel from preferences
     */
    public void initFromPreferences() {
        setApiUrl(Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL).trim());
        refreshView();
    }

    /**
     * Saves the current values to preferences
     */
    public void saveToPreferences() {
        OAuthAccessTokenHolder.getInstance().setSaveToPreferences(cbSaveToPreferences.isSelected());
        OAuthAccessTokenHolder.getInstance().save(Main.pref, CredentialsManager.getInstance());
        pnlAdvancedProperties.rememberPreferences(Main.pref);
    }

    /**
     * The preferences panel displayed if there is currently no Access Token available.
     * This means that the user didn't run through the OAuth authorisation procedure yet.
     *
     */
    private class NotYetAuthorisedPanel extends JPanel {
        /**
         * Constructs a new {@code NotYetAuthorisedPanel}.
         */
        public NotYetAuthorisedPanel() {
            build();
        }

        protected void build() {
            setLayout(new GridBagLayout());
            GridBagConstraints gc = new GridBagConstraints();

            // A message explaining that the user isn't authorised yet
            gc.anchor = GridBagConstraints.NORTHWEST;
            gc.insets = new Insets(0, 0, 3, 0);
            gc.fill = GridBagConstraints.HORIZONTAL;
            gc.weightx = 1.0;
            JMultilineLabel lbl;
            add(lbl = new JMultilineLabel(
                    tr("You do not have an Access Token yet to access the OSM server using OAuth. Please authorize first.")), gc);
            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));

            // Action for authorising now
            gc.gridy = 1;
            gc.fill = GridBagConstraints.NONE;
            gc.weightx = 0.0;
            add(new SideButton(new AuthoriseNowAction()), gc);

            // filler - grab remaining space
            gc.gridy = 2;
            gc.fill = GridBagConstraints.BOTH;
            gc.weightx = 1.0;
            gc.weighty = 1.0;
            add(new JPanel(), gc);
        }
    }

    /**
     * The preferences panel displayed if there is currently an AccessToken available.
     *
     */
    private class AlreadyAuthorisedPanel extends JPanel {
        private JosmTextField tfAccessTokenKey;
        private JosmTextField tfAccessTokenSecret;

        protected void build() {
            setLayout(new GridBagLayout());
            GridBagConstraints gc = new GridBagConstraints();
            gc.anchor = GridBagConstraints.NORTHWEST;
            gc.insets = new Insets(0, 0, 3, 3);
            gc.fill = GridBagConstraints.HORIZONTAL;
            gc.weightx = 1.0;
            gc.gridwidth = 2;
            JMultilineLabel lbl;
            add(lbl = new JMultilineLabel(tr("You already have an Access Token to access the OSM server using OAuth.")), gc);
            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));

            // -- access token key
            gc.gridy = 1;
            gc.gridx = 0;
            gc.gridwidth = 1;
            gc.weightx = 0.0;
            add(new JLabel(tr("Access Token Key:")), gc);

            gc.gridx = 1;
            gc.weightx = 1.0;
            add(tfAccessTokenKey = new JosmTextField(), gc);
            tfAccessTokenKey.setEditable(false);

            // -- access token secret
            gc.gridy = 2;
            gc.gridx = 0;
            gc.gridwidth = 1;
            gc.weightx = 0.0;
            add(new JLabel(tr("Access Token Secret:")), gc);

            gc.gridx = 1;
            gc.weightx = 1.0;
            add(tfAccessTokenSecret = new JosmTextField(), gc);
            tfAccessTokenSecret.setEditable(false);

            // -- access token secret
            gc.gridy = 3;
            gc.gridx = 0;
            gc.gridwidth = 2;
            gc.weightx = 1.0;
            add(cbSaveToPreferences = new JCheckBox(tr("Save to preferences")), gc);
            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());

            // -- action buttons
            JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
            btns.add(new SideButton(new RenewAuthorisationAction()));
            btns.add(new SideButton(new TestAuthorisationAction()));
            gc.gridy = 4;
            gc.gridx = 0;
            gc.gridwidth = 2;
            gc.weightx = 1.0;
            add(btns, gc);

            // the panel with the advanced options
            gc.gridy = 5;
            gc.gridx = 0;
            gc.gridwidth = 2;
            gc.weightx = 1.0;
            add(buildAdvancedPropertiesPanel(), gc);

            // filler - grab the remaining space
            gc.gridy = 6;
            gc.fill = GridBagConstraints.BOTH;
            gc.weightx = 1.0;
            gc.weighty = 1.0;
            add(new JPanel(), gc);

        }

        public final void refreshView() {
            String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey();
            tfAccessTokenKey.setText(v == null ? "" : v);
            v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret();
            tfAccessTokenSecret.setText(v == null ? "" : v);
            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
        }

        /**
         * Constructs a new {@code AlreadyAuthorisedPanel}.
         */
        public AlreadyAuthorisedPanel() {
            build();
            refreshView();
        }
    }

    /**
     * Action to authorise the current user
     */
    private class AuthoriseNowAction extends AbstractAction {
        public AuthoriseNowAction() {
            putValue(NAME, tr("Authorize now"));
            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process"));
            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
                    OAuthAuthenticationPreferencesPanel.this,
                    apiUrl
            );
            wizard.setVisible(true);
            if (wizard.isCanceled()) return;
            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
            holder.setAccessToken(wizard.getAccessToken());
            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
            refreshView();
        }
    }

    /**
     * Launches the OAuthAuthorisationWizard to generate a new Access Token
     */
    private class RenewAuthorisationAction extends AbstractAction {
        /**
         * Constructs a new {@code RenewAuthorisationAction}.
         */
        public RenewAuthorisationAction() {
            putValue(NAME, tr("New Access Token"));
            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process and generate a new Access Token"));
            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));

        }

        @Override
        public void actionPerformed(ActionEvent arg0) {
            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
                    OAuthAuthenticationPreferencesPanel.this,
                    apiUrl
            );
            wizard.setVisible(true);
            if (wizard.isCanceled()) return;
            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
            holder.setAccessToken(wizard.getAccessToken());
            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
            refreshView();
        }
    }

    /**
     * Runs a test whether we can access the OSM server with the current Access Token
     */
    private class TestAuthorisationAction extends AbstractAction {
        /**
         * Constructs a new {@code TestAuthorisationAction}.
         */
        public TestAuthorisationAction() {
            putValue(NAME, tr("Test Access Token"));
            putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token"));
            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));

        }

        @Override
        public void actionPerformed(ActionEvent evt) {
            OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken();
            OAuthParameters parameters = OAuthParameters.createFromPreferences(Main.pref);
            TestAccessTokenTask task = new TestAccessTokenTask(
                    OAuthAuthenticationPreferencesPanel.this,
                    apiUrl,
                    parameters,
                    token
            );
            Main.worker.submit(task);
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (!evt.getPropertyName().equals(OsmApiUrlInputPanel.API_URL_PROP))
            return;
        setApiUrl((String) evt.getNewValue());
    }
}
