
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.util.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.gui.*;

public class GeneralSettingsPanel
    implements TreeCard
{
    private final Variable<Backend.Mode> mode;
    private final Variable<Integer> slaveId;
    private final Variable<Integer> retries;
    private final Variable<Integer> replyTimeout;
    private final Variable<Integer> requestDelay;
    private final Variable<Integer> responseDelay;
    private final Variable<Integer> maxPdu;
    private final Variable<Boolean> checkCountLimits;
    private final Variable<Boolean> strictChecking;
    private final Variable<Boolean> allowLongMessages;
    private final Variable<Boolean> singleWrite;

    private final Frontend frontend;

    private final WComponent<Backend.Mode> modeSelector;
    private final WComponent<Integer> slaveIdField;
    private final WComponent<Integer> retriesField;
    private final WComponent<Integer> replyTimeoutField;
    private final WComponent<Integer> requestDelayField;
    private final WComponent<Integer> responseDelayField;
    private final WComponent<Integer> maxPduField;
    private final WComponent<Boolean> checkCountLimitsField;
    private final WComponent<Boolean> strictCheckingField;
    private final WComponent<Boolean> allowLongMessagesField;
    private final WComponent<Boolean> singleWriteField;

    private final JButton applyButton;
    private final JButton resetButton;
    private final JPanel outerPanel;
    private final StatusBar statusBar;
    private final ValueEventSource statusListeners;

    public GeneralSettingsPanel (Frontend frontend, Settings settings)
    {
        this.frontend = frontend;
        GeneralSettings gs = settings.getGeneral ();
        statusListeners = new ValueEventSource ();

        slaveId = gs.getSlaveId ();
        retries = gs.getRetries ();
        replyTimeout = gs.getReplyTimeout ();
        requestDelay = gs.getRequestDelay ();
        maxPdu = gs.getMaxPdu ();
        checkCountLimits = gs.getCheckCountLimits ();
        strictChecking = gs.getStrictChecking ();
        allowLongMessages = gs.getAllowLongMessages ();

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

        outerPanel = new JPanel ();
        outerPanel.setLayout (new BorderLayout ());
        JLabel heading = Gui.createDialogHeading ("General");
        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);

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

        if (frontend.getProduct ().hasSlaveMode ())
        {
            mode = gs.getMode ();
            Backend.Mode [] modeTags = new Backend.Mode []
                {
                    Backend.Mode.master,
                    Backend.Mode.slave,
                    Backend.Mode.monitor
                };
            String [] modeItems = new String []
                {
                    "Master",
                    "Slave",
                    "Monitor"
                };
            modeSelector = new WRadioButtons<Backend.Mode> ("Mode", modeTags,
                modeItems, mode.getValue (), true);
            modeSelector.setToolTipText ("Mode of operation");
            modeSelector.setMnemonic (KeyEvent.VK_M);
            panel.addComponent (modeSelector);
        }
        else
        {
            mode = null;
            modeSelector = null;
        }

        slaveIdField = new WIntegerField (statusBar, "Slave ID",
            GeneralSettings.MIN_SLAVE_ID, GeneralSettings.MAX_SLAVE_ID,
            slaveId.getValue ());
        if (frontend.getProduct ().isTester ())
            slaveIdField.setToolTipText ("Default slave identifier");
        else
            slaveIdField.setToolTipText ("Slave identifier");
        slaveIdField.setMnemonic (KeyEvent.VK_S);
        slaveIdField.setWidthChars (8);
        panel.addComponent (slaveIdField);

        retriesField = new WIntegerField (statusBar, "Number of Retries",
            GeneralSettings.MIN_RETRIES,
            GeneralSettings.MAX_RETRIES, 2);
        retriesField.setToolTipText (
            "Number of retries after request failure");
        retriesField.setMnemonic (KeyEvent.VK_N);
        retriesField.setWidthChars (10);
        panel.addComponent (retriesField);

        replyTimeoutField = new WIntegerField (statusBar,
            "Response Timeout",
            GeneralSettings.MIN_REPLY_TIMEOUT,
            GeneralSettings.MAX_REPLY_TIMEOUT, 1000);
        replyTimeoutField.setToolTipText (
            "Response timeout in milliseconds");
        replyTimeoutField.setMnemonic (KeyEvent.VK_T);
        replyTimeoutField.setWidthChars (10);
        panel.addComponent (replyTimeoutField);

        requestDelayField = new WIntegerField (statusBar, "Request Delay",
            GeneralSettings.MIN_REQUEST_DELAY,
            GeneralSettings.MAX_REQUEST_DELAY, 0);
        requestDelayField.setToolTipText (
            "Delay (in milliseconds) between transfer requests");
        requestDelayField.setMnemonic (KeyEvent.VK_Q);
        requestDelayField.setWidthChars (10);
        panel.addComponent (requestDelayField);

        if (frontend.getProduct ().hasSlaveMode ())
        {
            responseDelay = gs.getResponseDelay ();
            responseDelayField = new WIntegerField (statusBar, "Response Delay",
                GeneralSettings.MIN_RESPONSE_DELAY,
                GeneralSettings.MAX_RESPONSE_DELAY, 0);
            responseDelayField.setToolTipText (
                "Delay (in milliseconds) before sending response");
            responseDelayField.setMnemonic (KeyEvent.VK_D);
            responseDelayField.setWidthChars (10);
            panel.addComponent (responseDelayField);
        }
        else
        {
            responseDelay = null;
            responseDelayField = null;
        }

        maxPduField = new WIntegerField (statusBar, "Max PDU Size",
            Modbus.MIN_PDU_SIZE, Modbus.MAX_IMP_PDU_SIZE,
            maxPdu.getValue ());
        maxPduField.setToolTipText (
            "<html>Maximum number of bytes in Modbus message PDU<br>" +
            "(standard maximum is 253 bytes)");
        maxPduField.setMnemonic (KeyEvent.VK_P);
        maxPduField.setWidthChars (10);
        panel.addComponent (maxPduField);

        checkCountLimitsField = new WCheckBox ("Check Count Limits",
            checkCountLimits.getValue ());
        checkCountLimitsField.setToolTipText (
            "Check arbitrary count limits for Modbus requests");
        checkCountLimitsField.setMnemonic (KeyEvent.VK_E);
        panel.addComponent (checkCountLimitsField);

        strictCheckingField = new WCheckBox ("Strict Checking",
            strictChecking.getValue ());
        strictCheckingField.setToolTipText (
            "Check Modbus messages for strict conformance to the standard");
        strictCheckingField.setMnemonic (KeyEvent.VK_C, 7);
        panel.addComponent (strictCheckingField);

        allowLongMessagesField = new WCheckBox ("Allow Long Messages",
            allowLongMessages.getValue ());
        allowLongMessagesField.setToolTipText (
            "Allow Modbus messages to contain more than 255 data bytes");
        allowLongMessagesField.setMnemonic (KeyEvent.VK_L, 6);
        panel.addComponent (allowLongMessagesField);

        if (!frontend.getProduct ().isTester ())
        {
            singleWrite = gs.getSingleWrite ();
            singleWriteField = new WCheckBox ("Use Single-Write Functions",
                singleWrite.getValue ());
            singleWriteField.setToolTipText (
                "Use function codes 5 & 6 instead of 15 & 16");
            singleWriteField.setMnemonic (KeyEvent.VK_W, 6);
            panel.addComponent (singleWriteField);
        }
        else
        {
            singleWrite = null;
            singleWriteField = null;
        }

        if (modeSelector != null)
        {
            modeSelector.addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        boolean isSlave =
                            modeSelector.getValue () == Backend.Mode.slave;
                        retriesField.setEnabled (!isSlave);
                        replyTimeoutField.setEnabled (!isSlave);
                        requestDelayField.setEnabled (!isSlave);
                        if (responseDelayField != null)
                            responseDelayField.setEnabled (isSlave);
                        if (singleWriteField != null)
                            singleWriteField.setEnabled (!isSlave);
                        statusBar.clear ();
                        setButtonsEnabled (hasUnappliedChanges ());
                    }
                });
        }

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

        slaveIdField.addValueListener (guiListener);
        retriesField.addValueListener (guiListener);
        replyTimeoutField.addValueListener (guiListener);
        requestDelayField.addValueListener (guiListener);
        if (responseDelayField != null)
            responseDelayField.addValueListener (guiListener);
        maxPduField.addValueListener (guiListener);
        checkCountLimitsField.addValueListener (guiListener);
        strictCheckingField.addValueListener (guiListener);
        allowLongMessagesField.addValueListener (guiListener);
        if (singleWriteField != null)
            singleWriteField.addValueListener (guiListener);

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

        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)
                {
                    if (modeSelector != null)
                    {
                        modeSelector.setValue (mode.getValue ());
                        modeSelector.setEnabled (state.isStopped);
                        retriesField.setEnabled (state.isStopped &&
                            !state.isSlave);
                        replyTimeoutField.setEnabled (state.isStopped &&
                            !state.isSlave);
                        requestDelayField.setEnabled (state.isStopped &&
                            !state.isSlave);
                        if (responseDelayField != null)
                        {
                            responseDelayField.setEnabled (state.isStopped &&
                                state.isSlave);
                        }
                        maxPduField.setEnabled (state.isStopped);
                        checkCountLimitsField.setEnabled (state.isStopped);
                        strictCheckingField.setEnabled (state.isStopped);
                        allowLongMessagesField.setEnabled (state.isStopped);
                        if (singleWriteField != null)
                        {
                            singleWriteField.setEnabled (state.isStopped &&
                                !state.isSlave);
                        }
                    }
                    setButtonsEnabled (hasUnappliedChanges ());
                }
            });

        if (modeSelector != null)
        {
            mode.addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        modeSelector.setValue (mode.getValue ());
                    }
                });
        }

        slaveId.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    slaveIdField.setValue (slaveId.getValue ());
                }
            });

        retries.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    retriesField.setValue (retries.getValue ());
                }
            });

        replyTimeout.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    replyTimeoutField.setValue (replyTimeout.getValue ());
                }
            });

        requestDelay.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    requestDelayField.setValue (requestDelay.getValue ());
                }
            });

        if (responseDelay != null)
        {
            responseDelay.addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        responseDelayField.setValue (responseDelay.getValue ());
                    }
                });
        }

        maxPdu.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    maxPduField.setValue (maxPdu.getValue ());
                }
            });

        checkCountLimits.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    checkCountLimitsField.setValue (
                        checkCountLimits.getValue ());
                }
            });

        strictChecking.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    strictCheckingField.setValue (
                        strictChecking.getValue ());
                }
            });

        allowLongMessages.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    allowLongMessagesField.setValue (
                        allowLongMessages.getValue ());
                }
            });

        if (singleWrite != null)
        {
            singleWrite.addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        singleWriteField.setValue (
                            singleWrite.getValue ());
                    }
                });
        }

        getValuesFromModel ();
    }

    public JPanel getPanel ()
    {
        return outerPanel;
    }

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

    public String getName ()
    {
        return "General";
    }

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

    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;
        if (modeSelector != null &&
            modeSelector.hasValueChanged (mode.getValue ()))
        {
            changed = true;
        }
        if (responseDelayField != null &&
            responseDelayField.hasValueChanged (responseDelay.getValue ()))
        {
            changed = true;
        }
        if (singleWriteField != null &&
            singleWriteField.hasValueChanged (singleWrite.getValue ()))
        {
            changed = true;
        }
        if (slaveIdField.hasValueChanged (slaveId.getValue ()))
            changed = true;
        if (retriesField.hasValueChanged (retries.getValue ()))
            changed = true;
        if (replyTimeoutField.hasValueChanged (replyTimeout.getValue ()))
            changed = true;
        if (requestDelayField.hasValueChanged (requestDelay.getValue ()))
            changed = true;
        if (maxPduField.hasValueChanged (maxPdu.getValue ()))
            changed = true;
        if (checkCountLimitsField.hasValueChanged (
            checkCountLimits.getValue ()))
        {
            changed = true;
        }
        if (strictCheckingField.hasValueChanged (strictChecking.getValue ()))
            changed = true;
        if (allowLongMessagesField.hasValueChanged (
            allowLongMessages.getValue ()))
        {
            changed = true;
        }
        return changed;
    }

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

    public boolean checkValues ()
    {
        if (responseDelayField != null && !responseDelayField.checkValue ())
            return false;
        if (singleWriteField != null && !singleWriteField.checkValue ())
            return false;
        return slaveIdField.checkValue () &&
            retriesField.checkValue () &&
            replyTimeoutField.checkValue () &&
            requestDelayField.checkValue () &&
            maxPduField.checkValue () &&
            checkCountLimitsField.checkValue () &&
            strictCheckingField.checkValue () &&
            allowLongMessagesField.checkValue ();
    }

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

    public void getValuesFromModel ()
    {
        statusBar.clear ();
        if (modeSelector != null)
            modeSelector.setValue (mode.getValue ());
        slaveIdField.setValue (slaveId.getValue ());
        retriesField.setValue (retries.getValue ());
        replyTimeoutField.setValue (replyTimeout.getValue ());
        requestDelayField.setValue (requestDelay.getValue ());
        if (responseDelay != null)
            responseDelayField.setValue (responseDelay.getValue ());
        maxPduField.setValue (maxPdu.getValue ());
        checkCountLimitsField.setValue (checkCountLimits.getValue ());
        strictCheckingField.setValue (strictChecking.getValue ());
        allowLongMessagesField.setValue (allowLongMessages.getValue ());
        if (singleWrite != null)
            singleWriteField.setValue (singleWrite.getValue ());
        setButtonsEnabled (false);
    }

    public void putValuesToModel ()
    {
        if (modeSelector != null)
            mode.setValue (modeSelector.getValue ());
        slaveId.setValue (slaveIdField.getValue ());
        retries.setValue (retriesField.getValue ());
        replyTimeout.setValue (replyTimeoutField.getValue ());
        requestDelay.setValue (requestDelayField.getValue ());
        if (responseDelay != null)
            responseDelay.setValue (responseDelayField.getValue ());
        maxPdu.setValue (maxPduField.getValue ());
        checkCountLimits.setValue (checkCountLimitsField.getValue ());
        strictChecking.setValue (strictCheckingField.getValue ());
        allowLongMessages.setValue (allowLongMessagesField.getValue ());
        if (singleWrite != null)
            singleWrite.setValue (singleWriteField.getValue ());
        setButtonsEnabled (false);
    }

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

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

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


