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

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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

import org.openstreetmap.josm.gui.widgets.HtmlPanel;
import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.openstreetmap.josm.tools.ImageProvider;

/**
 * VehicleExceptionEditor is UI widget for editing exceptions to a turn restriction
 * based on vehicle types.
 *
 */
public class VehicleExceptionEditor extends JPanel implements Observer {

    private TurnRestrictionEditorModel model;
    private JCheckBox cbPsv;
    private JCheckBox cbBicyle;
    private JCheckBox cbMoped;
    private JCheckBox cbHgv;
    private JCheckBox cbMotorcar;
    private JTextField tfNonStandardValue;
    private ButtonGroup bgStandardOrNonStandard;
    private JRadioButton rbStandardException;
    private JRadioButton rbNonStandardException;
    private JPanel pnlStandard;
    private JPanel pnlNonStandard;
    private ExceptValueModel exceptValue = new ExceptValueModel();

    private StandardVehicleTypeChangeListener svtChangeListener;

    private JPanel buildMessagePanel() {
        JPanel pnl = new JPanel(new BorderLayout());
        HtmlPanel msg = new HtmlPanel();
        pnl.add(msg, BorderLayout.CENTER);
        msg.setText(
                "<html><body>"
                + tr("Select the vehicle types this turn restriction is <strong>not</strong> applicable for.")
                + "</body></html>"
        );
        return pnl;
    }

    private JPanel buildStandardInputPanel() {
        if (pnlStandard != null)
            return pnlStandard;

        svtChangeListener = new StandardVehicleTypeChangeListener();

        GridBagConstraints gc = new GridBagConstraints();
        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.gridx = 0;
        gc.gridy = 0;

        pnlStandard = new JPanel(new GridBagLayout());
        JLabel lbl;
        cbPsv = new JCheckBox();
        cbPsv.addItemListener(svtChangeListener);
        lbl = new JLabel();
        lbl.setText(tr("Public Service Vehicles"));
        lbl.setToolTipText(tr("Public service vehicles like buses, tramways, etc."));
        lbl.setIcon(ImageProvider.get("vehicle", "psv"));

        gc.weightx = 0.0;
        pnlStandard.add(cbPsv, gc);
        gc.weightx = 1.0;
        gc.gridx++;
        pnlStandard.add(lbl, gc);

        cbHgv = new JCheckBox();
        cbHgv.addItemListener(svtChangeListener);
        lbl = new JLabel();
        lbl.setText(tr("Heavy Goods Vehicles"));
        lbl.setIcon(ImageProvider.get("vehicle", "hgv"));

        gc.weightx = 0.0;
        gc.gridx++;
        pnlStandard.add(cbHgv, gc);
        gc.weightx = 1.0;
        gc.gridx++;
        pnlStandard.add(lbl, gc);

        cbMotorcar = new JCheckBox();
        cbMotorcar.addItemListener(svtChangeListener);
        lbl = new JLabel();
        lbl.setText(tr("Motorcars"));
        lbl.setIcon(ImageProvider.get("vehicle", "motorcar"));

        gc.weightx = 0.0;
        gc.gridx = 0;
        gc.gridy = 1;
        pnlStandard.add(cbMotorcar, gc);
        gc.weightx = 1.0;
        gc.gridx++;
        pnlStandard.add(lbl, gc);

        cbBicyle = new JCheckBox();
        cbBicyle.addItemListener(svtChangeListener);
        lbl = new JLabel();
        lbl.setText(tr("Bicycles"));
        lbl.setIcon(ImageProvider.get("vehicle", "bicycle"));

        gc.weightx = 0.0;
        gc.gridx++;
        pnlStandard.add(cbBicyle, gc);
        gc.weightx = 1.0;
        gc.gridx++;
        pnlStandard.add(lbl, gc);

        cbMoped = new JCheckBox();
        cbMoped.addItemListener(svtChangeListener);
        lbl = new JLabel(tr("Mopeds"));
        lbl.setIcon(ImageProvider.get("vehicle", "moped"));

        gc.weightx = 0.0;
        gc.gridx = 0;
        gc.gridy = 2;
        pnlStandard.add(cbMoped, gc);
        gc.weightx = 1.0;
        gc.gridx++;
        pnlStandard.add(lbl, gc);

        return pnlStandard;
    }

    private JPanel buildNonStandardInputPanel() {
        if (pnlNonStandard != null)
            return pnlNonStandard;
        pnlNonStandard = 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, 4, 0);
        gc.gridx = 0;
        gc.gridy = 0;

        pnlNonStandard.add(new JLabel(tr("Value:")), gc);
        gc.gridx = 1;
        gc.weightx = 1.0;
        pnlNonStandard.add(tfNonStandardValue = new JTextField(), gc);
        SelectAllOnFocusGainedDecorator.decorate(tfNonStandardValue);

        NonStandardVehicleTypesHandler inputChangedHandler = new NonStandardVehicleTypesHandler();
        tfNonStandardValue.addActionListener(inputChangedHandler);
        tfNonStandardValue.addFocusListener(inputChangedHandler);
        return pnlNonStandard;
    }

    /**
     * Builds the UI for entering standard values
     */
    protected void buildStandard() {
        setLayout(new GridBagLayout());
        GridBagConstraints gc = new GridBagConstraints();
        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.weightx = 1.0;
        gc.gridx = 0;
        gc.gridy = 0;
        add(buildMessagePanel(), gc);

        gc.gridy = 1;
        add(buildStandardInputPanel(), gc);
    }

    /**
     * Builds the UI for entering either standard or non-standard values
     */
    protected void buildNonStandard() {
        setLayout(new GridBagLayout());
        GridBagConstraints gc = new GridBagConstraints();
        gc.anchor = GridBagConstraints.NORTHWEST;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.weightx = 1.0;
        gc.gridx = 0;
        gc.gridy = 0;
        add(buildMessagePanel(), gc);

        gc.gridx = 0;
        gc.gridy = 1;
        gc.insets = new Insets(0, 0, 0, 0);
        add(rbStandardException = new JRadioButton(tr("Use standard exceptions")), gc);

        gc.gridx = 0;
        gc.gridy = 2;
        gc.insets = new Insets(0, 20, 0, 0);
        add(buildStandardInputPanel(), gc);

        gc.gridx = 0;
        gc.gridy = 3;
        gc.insets = new Insets(0, 0, 0, 0);
        add(rbNonStandardException = new JRadioButton(tr("Use non-standard exceptions")), gc);

        gc.gridx = 0;
        gc.gridy = 4;
        gc.insets = new Insets(0, 20, 0, 0);
        add(buildNonStandardInputPanel(), gc);

        bgStandardOrNonStandard = new ButtonGroup();
        bgStandardOrNonStandard.add(rbNonStandardException);
        bgStandardOrNonStandard.add(rbStandardException);

        StandardNonStandardChangeHandler changeHandler = new StandardNonStandardChangeHandler();
        rbNonStandardException.addItemListener(changeHandler);
        rbStandardException.addItemListener(changeHandler);
    }

    protected void build() {
        removeAll();
        buildNonStandardInputPanel();
        buildStandardInputPanel();
        if (exceptValue.isStandard()) {
            buildStandard();
        } else {
            buildNonStandard();
        }
        init();
        invalidate();
    }

    protected void init() {
        try {
            // temporarily disable the checkbox listeners while initializing the
            // checkboxes with the input value
            this.svtChangeListener.setEnabled(false);
            cbPsv.setSelected(exceptValue.isVehicleException("psv"));
            cbBicyle.setSelected(exceptValue.isVehicleException("bicycle"));
            cbMoped.setSelected(exceptValue.isVehicleException("moped"));
            cbMotorcar.setSelected(exceptValue.isVehicleException("motorcar"));
            cbHgv.setSelected(exceptValue.isVehicleException("hgv"));
        } finally {
            this.svtChangeListener.setEnabled(true);
        }
        if (!exceptValue.isStandard()) {
            rbNonStandardException.setSelected(true);
            tfNonStandardValue.setText(exceptValue.getValue());
            setEnabledNonStandardInputPanel(true);
            setEnabledStandardInputPanel(false);
        } else {
            setEnabledNonStandardInputPanel(false);
            setEnabledStandardInputPanel(true);
        }
    }

    protected void setEnabledStandardInputPanel(boolean enabled) {
        for (Component c: pnlStandard.getComponents()) {
            c.setEnabled(enabled);
        }
    }

    protected void setEnabledNonStandardInputPanel(boolean enabled) {
        for (Component c: pnlNonStandard.getComponents()) {
            c.setEnabled(enabled);
        }
    }


    /**
     * Creates the editor
     *
     * @param model the editor model. Must not be null.
     * @throws IllegalArgumentException thrown if {@code model} is null
     */
    public VehicleExceptionEditor(TurnRestrictionEditorModel model) throws IllegalArgumentException {
        CheckParameterUtil.ensureParameterNotNull(model, "model");
        this.model = model;
        build();
        model.addObserver(this);
    }

    /* ------------------------------------------------------------------------------------ */
    /* interface Observer                                                                   */
    /* ------------------------------------------------------------------------------------ */
    @Override
    public void update(Observable o, Object arg) {
        if (!this.exceptValue.equals(model.getExcept())) {
            this.exceptValue = model.getExcept();
            build();
        }
    }

    /* ------------------------------------------------------------------------------------ */
    /* inner classes                                                                        */
    /* ------------------------------------------------------------------------------------ */
    class StandardNonStandardChangeHandler implements ItemListener {
        @Override
        public void itemStateChanged(ItemEvent e) {
            if (rbNonStandardException.isSelected()) {
                setEnabledNonStandardInputPanel(true);
                setEnabledStandardInputPanel(false);
                exceptValue.setStandard(false);
            } else {
                setEnabledNonStandardInputPanel(false);
                setEnabledStandardInputPanel(true);
                exceptValue.setStandard(true);
            }
            model.setExcept(exceptValue);
        }
    }

    class StandardVehicleTypeChangeListener implements ItemListener {
        private boolean enabled = true;

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        @Override
        public void itemStateChanged(ItemEvent e) {
            if (!enabled) return;
            exceptValue.setVehicleException("bicycle", cbBicyle.isSelected());
            exceptValue.setVehicleException("moped", cbMoped.isSelected());
            exceptValue.setVehicleException("hgv", cbHgv.isSelected());
            exceptValue.setVehicleException("psv", cbPsv.isSelected());
            exceptValue.setVehicleException("motorcar", cbMotorcar.isSelected());
            model.setExcept(exceptValue);
        }
    }

    class NonStandardVehicleTypesHandler implements ActionListener, FocusListener {
        public void persist() {
            exceptValue.setValue(tfNonStandardValue.getText());
            model.setExcept(exceptValue);
        }

        @Override
        public void focusGained(FocusEvent e) {}

        @Override
        public void focusLost(FocusEvent e) {
            persist();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            persist();
        }
    }
}
