
package uk.co.wingpath.modbusgui;

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;
import uk.co.wingpath.util.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.gui.*;

public class AddressSettingsPanel
    implements TreeCard
{
    private static final int MIN_BASE = 0;
    private static final int MAX_BASE = Integer.MAX_VALUE;
    private static final int MIN_SIZE = 0;
    private static final int MAX_SIZE = 0x10000;

    private final Registers registers;
    private final Frontend frontend;
    private final AddressSettings addressSettings;
    private final ValueEventSource statusListeners;

    private final WComponent<Boolean> byteSwapCheckBox;
    private final WComponent<Boolean> bitReverseCheckBox;
    private final JButton applyButton;
    private final JButton resetButton;
    private final WComponent<Integer> coilBaseField;
    private final WComponent<Integer> coilSizeField;
    private final WComponent<Integer> discreteBaseField;
    private final WComponent<Integer> discreteSizeField;
    private final WComponent<Integer> holdingBaseField;
    private final WComponent<Integer> holdingSizeField;
    private final WComponent<Integer> inputBaseField;
    private final WComponent<Integer> inputSizeField;
    private final WComponent<Integer> exceptionStatusField;
    private final WComponent<Integer> diagnosticField;
    private final JLabel baseLabel;
    private final JLabel sizeLabel;
    private final JLabel holdingLabel;
    private final JLabel inputLabel;
    private final JLabel coilLabel;
    private final JLabel discreteLabel;
    private final JPanel outerPanel;
    private final StatusBar statusBar;

    public AddressSettingsPanel (final Frontend frontend,
        final AddressSettings addressSettings, final Registers registers)
    {
        this.frontend = frontend;
        this.addressSettings = addressSettings;
        this.registers = registers;
        statusListeners = new ValueEventSource ();

        statusBar = new StatusBar ("addrmap", frontend.getHelpViewer ());
        statusBar.addStatusListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    fireStatusChanged ();
                }
            });

        outerPanel = new JPanel ();
        outerPanel.setLayout (new BorderLayout ());
        JLabel heading = Gui.createDialogHeading ("Address Mapping");
        outerPanel.add (heading, BorderLayout.NORTH);
        outerPanel.add (statusBar, BorderLayout.SOUTH);

        JPanel mainPanel = new JPanel ();
        outerPanel.add (mainPanel, BorderLayout.CENTER);
        mainPanel.setLayout (new BorderLayout ());
        ButtonPanel buttonPanel = new ButtonPanel ();
        mainPanel.add (buttonPanel, BorderLayout.SOUTH);

        ValueListener guiListener = new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    statusBar.clear ("ad3");
                    setButtonsEnabled (hasUnappliedChanges ());
                }
            };

        AddressMap map = addressSettings.getAddressMap ();
        coilBaseField = buildBaseField (statusBar,
            map.getCoilMap ().getBase (), "Coil", guiListener);
        coilSizeField = buildSizeField (statusBar,
            map.getCoilMap ().getSize (), "Coil", guiListener);
        discreteBaseField = buildBaseField (statusBar,
            map.getDiscreteInputMap ().getBase (),
            "Discrete Input", guiListener);
        discreteSizeField = buildSizeField (statusBar,
            map.getDiscreteInputMap ().getSize (),
            "Discrete Input", guiListener);
        holdingBaseField = buildBaseField (statusBar,
            map.getHoldingRegisterMap ().getBase (),
            "Holding Register", guiListener);
        holdingSizeField = buildSizeField (statusBar,
            map.getHoldingRegisterMap ().getSize (),
            "Holding Register", guiListener);
        inputBaseField = buildBaseField (statusBar,
            map.getInputRegisterMap ().getBase (),
            "Input Register", guiListener);
        inputSizeField = buildSizeField (statusBar,
            map.getInputRegisterMap ().getSize (),
            "Input Register", guiListener);

        coilLabel = new JLabel ("Coil:");
        coilLabel.setDisplayedMnemonic (KeyEvent.VK_C);
        coilLabel.setLabelFor (coilBaseField.getComponent ());

        discreteLabel = new JLabel ("Discrete Input:");
        discreteLabel.setDisplayedMnemonic (KeyEvent.VK_D);
        discreteLabel.setLabelFor (discreteBaseField.getComponent ());

        holdingLabel = new JLabel ("Holding Register:");
        holdingLabel.setDisplayedMnemonic (KeyEvent.VK_O);
        holdingLabel.setLabelFor (holdingBaseField.getComponent ());

        inputLabel = new JLabel ("Input Register:");
        inputLabel.setDisplayedMnemonic (KeyEvent.VK_I);
        inputLabel.setLabelFor (inputBaseField.getComponent ());

        baseLabel = new JLabel ("<html>Base<br>Address");
        baseLabel.setToolTipText ("Base address of range");
        sizeLabel = new JLabel ("<html>Number of<br>Addresses");
        sizeLabel.setToolTipText ("Number of addresses in range");

        byteSwapCheckBox = new WCheckBox (
            "Swap Bytes for Coils/Discrete Inputs", true);
        byteSwapCheckBox.setToolTipText (
            "<html>Whether to swap bytes within each 16-bit word<br>" +
            "for Coils/Discrete Inputs");
        byteSwapCheckBox.setMnemonic (KeyEvent.VK_S);
        byteSwapCheckBox.addValueListener (guiListener);

        bitReverseCheckBox = new WCheckBox (
            "Reverse Bits for Coils/Discrete Inputs", false);
        bitReverseCheckBox.setToolTipText (
            "<html>Whether to reverse bits within each byte<br>" +
            "for Coils/Discrete Inputs");
        bitReverseCheckBox.setMnemonic (KeyEvent.VK_R);
        bitReverseCheckBox.addValueListener (guiListener);

        exceptionStatusField = new WIntegerField (statusBar,
            "Exception Status Register", -1, Integer.MAX_VALUE, 0);
        exceptionStatusField.setWidthChars (8);
        exceptionStatusField.setAlignment (SwingConstants.RIGHT);
        exceptionStatusField.setToolTipText (
            "Model address of exception status register");
        exceptionStatusField.setMnemonic (KeyEvent.VK_E);
        exceptionStatusField.addValueListener (guiListener);

        diagnosticField = new WIntegerField (statusBar,
            "Diagnostic Register", -1, Integer.MAX_VALUE, 0);
        diagnosticField.setWidthChars (8);
        diagnosticField.setAlignment (SwingConstants.RIGHT);
        diagnosticField.setToolTipText (
            "Model address of diagnostic register");
        diagnosticField.setMnemonic (KeyEvent.VK_G);
        diagnosticField.addValueListener (guiListener);

        GridBagPanel panel = new GridBagPanel ();
        mainPanel.add (panel, BorderLayout.CENTER);

        GridBagConstraints constraints = panel.createConstraints ();

        constraints.gridx = 1;
        panel.add (baseLabel, constraints);
        constraints.gridx = 2;
        panel.add (sizeLabel, constraints);

        constraints = panel.createConstraints ();
        panel.add (holdingLabel, constraints);
        constraints.gridx = 1;
        panel.add (holdingBaseField.getComponent (), constraints);
        constraints.gridx = 2;
        panel.add (holdingSizeField.getComponent (), constraints);

        constraints = panel.createConstraints ();
        panel.add (inputLabel, constraints);
        constraints.gridx = 1;
        panel.add (inputBaseField.getComponent (), constraints);
        constraints.gridx = 2;
        panel.add (inputSizeField.getComponent (), constraints);

        constraints = panel.createConstraints ();
        panel.add (coilLabel, constraints);
        constraints.gridx = 1;
        panel.add (coilBaseField.getComponent (), constraints);
        constraints.gridx = 2;
        panel.add (coilSizeField.getComponent (), constraints);

        constraints = panel.createConstraints ();
        panel.add (discreteLabel, constraints);
        constraints.gridx = 1;
        panel.add (discreteBaseField.getComponent (), constraints);
        constraints.gridx = 2;
        panel.add (discreteSizeField.getComponent (), constraints);

        if (frontend.getProduct ().hasSlaveMode ())
        {
            panel.addComponent (exceptionStatusField);
            panel.addComponent (diagnosticField);
        }

        constraints = panel.createConstraints ();
        constraints.gridwidth = 3;
        panel.add (byteSwapCheckBox.getComponent (), constraints);

        constraints = panel.createConstraints ();
        constraints.gridwidth = 3;
        panel.add (bitReverseCheckBox.getComponent (), constraints);

        applyButton = buttonPanel.addButton ("Apply", null,
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    if (!checkValues ())
                        return;

                    try
                    {
                        AddressMap map = buildAddressMap ();
                        registers.checkAddressMap (map);
                        addressSettings.setAddressMap (map);
                        statusBar.clear ("ad4");
                        setButtonsEnabled (false);
                    }
                    catch (ValueException me)
                    {
                        statusBar.showException (me);
                    }
                }
            });

        resetButton = buttonPanel.addButton ("Reset", null,
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    reset ();
                }
            });
        resetButton.setMnemonic (KeyEvent.VK_R);

        buttonPanel.addButton (getHelpAction ());

        frontend.addBackendStateListener (new BackendState.Listener ()
            {
                public void stateChanged (BackendState state)
                {
                    setLabelEnabled (baseLabel, state.isStopped);
                    setLabelEnabled (sizeLabel, state.isStopped);
                    holdingLabel.setEnabled (state.isStopped);
                    inputLabel.setEnabled (state.isStopped);
                    coilLabel.setEnabled (state.isStopped);
                    discreteLabel.setEnabled (state.isStopped);
                    coilBaseField.setEnabled (state.isStopped);
                    coilSizeField.setEnabled (state.isStopped);
                    discreteBaseField.setEnabled (state.isStopped);
                    discreteSizeField.setEnabled (state.isStopped);
                    holdingBaseField.setEnabled (state.isStopped);
                    holdingSizeField.setEnabled (state.isStopped);
                    inputBaseField.setEnabled (state.isStopped);
                    inputSizeField.setEnabled (state.isStopped);
                    byteSwapCheckBox.setEnabled (state.isStopped);
                    bitReverseCheckBox.setEnabled (state.isStopped);
                    exceptionStatusField.setEnabled (state.isStopped &&
                        state.isSlave && !state.isMaster);
                    diagnosticField.setEnabled (state.isStopped &&
                        state.isSlave && !state.isMaster);
                    setButtonsEnabled (state.isStopped &&
                        hasUnappliedChanges ());
                }
            });

        addressSettings.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    getValuesFromModel ();
                }
            });

        registers.addValueListener (statusBar.getClearListener ("ad1"));

        getValuesFromModel ();
    }

    private WComponent<Integer> buildBaseField (StatusBar statusBar,
        int value, String label, ValueListener guiListener)
    {
        WComponent<Integer> field = new WIntegerField (statusBar, null,
            MIN_BASE, MAX_BASE, value);
        field.setWidthChars (8);
        field.setAlignment (SwingConstants.RIGHT);
        field.setToolTipText (
            "Base address of " + label + " address range");
        field.addValueListener (guiListener);
        return field;
    }

    private WComponent<Integer> buildSizeField (StatusBar statusBar,
        int value, String label, ValueListener guiListener)
    {
        WComponent<Integer> field = new WIntegerField (statusBar, null,
            MIN_SIZE, MAX_SIZE, value);
        field.setWidthChars (8);
        field.setAlignment (SwingConstants.RIGHT);
        field.setToolTipText (
            "Number of addresses in " + label + " address range");
        field.addValueListener (guiListener);
        return field;
    }

    private void setLabelEnabled (JLabel label, boolean enabled)
    {
        label.setForeground ((Color) UIManager.getDefaults ().get (
            enabled ? "Label.foreground" : "Label.disabledForeground"));
    }

    public JPanel getPanel ()
    {
        return outerPanel;
    }

    public String getTag ()
    {
        return "address_mapping";
    }

    public String getName ()
    {
        return "Address Mapping";
    }

    public Action getHelpAction ()
    {
        return frontend.getHelpAction ("address_mapping");
    }

    public JButton getDefaultButton ()
    {
        return applyButton;
    }

    public String getToolTipText ()
    {
        return null;
    }

    @Override
    public void selected ()
    {
    }

    @Override
    public void reset ()
    {
        getValuesFromModel ();
    }

    public boolean hasUnappliedChanges ()
    {
        boolean changed = false;
        AddressMap map = addressSettings.getAddressMap ();
        if (byteSwapCheckBox.hasValueChanged (map.getByteSwap ()))
        {
            changed = true;
        }
        if (bitReverseCheckBox.hasValueChanged (map.getBitReverse ()))
        {
            changed = true;
        }
        if (exceptionStatusField.hasValueChanged (
            map.getExceptionStatusRegister ()))
        {
            changed = true;
        }
        if (diagnosticField.hasValueChanged (
            map.getDiagnosticRegister ()))
        {
            changed = true;
        }
        if (coilBaseField.hasValueChanged (
            map.getCoilMap ().getBase ()))
        {
            changed = true;
        }
        if (coilSizeField.hasValueChanged (
            map.getCoilMap ().getSize ()))
        {
            changed = true;
        }
        if (discreteBaseField.hasValueChanged (
            map.getDiscreteInputMap ().getBase ()))
        {
            changed = true;
        }
        if (discreteSizeField.hasValueChanged (
            map.getDiscreteInputMap ().getSize ()))
        {
            changed = true;
        }
        if (holdingBaseField.hasValueChanged (
            map.getHoldingRegisterMap ().getBase ()))
        {
            changed = true;
        }
        if (holdingSizeField.hasValueChanged (
            map.getHoldingRegisterMap ().getSize ()))
        {
            changed = true;
        }
        if (inputBaseField.hasValueChanged (
            map.getInputRegisterMap ().getBase ()))
        {
            changed = true;
        }
        if (inputSizeField.hasValueChanged (
            map.getInputRegisterMap ().getSize ()))
        {
            changed = true;
        }
        return changed;
    }

    public boolean hasError ()
    {
        return statusBar.hasError ();
    }

    private boolean checkValues ()
    {
        return coilBaseField.checkValue () &&
            coilSizeField.checkValue () &&
            discreteBaseField.checkValue () &&
            discreteSizeField.checkValue () &&
            holdingBaseField.checkValue () &&
            holdingSizeField.checkValue () &&
            inputBaseField.checkValue () &&
            inputSizeField.checkValue () &&
            exceptionStatusField.checkValue () &&
            diagnosticField.checkValue () &&
            byteSwapCheckBox.checkValue () &&
            bitReverseCheckBox.checkValue ();
    }

    private void setButtonsEnabled (boolean enabled)
    {
        Gui.setEnabled (applyButton, enabled);
        Gui.setEnabled (resetButton, enabled);
        fireStatusChanged ();
    }

    private void getValuesFromModel ()
    {
        statusBar.clear ("ad2");
        AddressMap map = addressSettings.getAddressMap ();
        coilBaseField.setValue (map.getCoilMap ().getBase ());
        coilSizeField.setValue (map.getCoilMap ().getSize ());
        discreteBaseField.setValue (map.getDiscreteInputMap ().getBase ());
        discreteSizeField.setValue (map.getDiscreteInputMap ().getSize ());
        holdingBaseField.setValue (map.getHoldingRegisterMap ().getBase ());
        holdingSizeField.setValue (map.getHoldingRegisterMap ().getSize ());
        inputBaseField.setValue (map.getInputRegisterMap ().getBase ());
        inputSizeField.setValue (map.getInputRegisterMap ().getSize ());
        byteSwapCheckBox.setValue (map.getByteSwap ());
        bitReverseCheckBox.setValue (map.getBitReverse ());
        exceptionStatusField.setValue (map.getExceptionStatusRegister ());
        diagnosticField.setValue (map.getDiagnosticRegister ());
        setButtonsEnabled (false);
    }

    private AddressMap buildAddressMap ()
    {
        AddressMap map = new AddressMap ();
        map.getCoilMap ().setBase (coilBaseField.getValue ());
        map.getCoilMap ().setSize (coilSizeField.getValue ());
        map.getDiscreteInputMap ().setBase (discreteBaseField.getValue ());
        map.getDiscreteInputMap ().setSize (discreteSizeField.getValue ());
        map.getHoldingRegisterMap ().setBase (holdingBaseField.getValue ());
        map.getHoldingRegisterMap ().setSize (holdingSizeField.getValue ());
        map.getInputRegisterMap ().setBase (inputBaseField.getValue ());
        map.getInputRegisterMap ().setSize (inputSizeField.getValue ());
        map.setExceptionStatusRegister (exceptionStatusField.getValue ());
        map.setDiagnosticRegister (diagnosticField.getValue ());
        map.setByteSwap (byteSwapCheckBox.getValue ());
        map.setBitReverse (bitReverseCheckBox.getValue ());
        return map;
    }

    private void fireStatusChanged ()
    {
        statusListeners.fireValueChanged (this);
    }

    public void addStatusListener (ValueListener l)
    {
        statusListeners.addListener (l);
    }

    public void removeStatusListener (ValueListener l)
    {
        statusListeners.removeListener (l);
    }
}

