
package uk.co.wingpath.modbusgui;

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.lang.reflect.*;
import uk.co.wingpath.modbus.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.gui.*;
import uk.co.wingpath.io.*;
import uk.co.wingpath.registration.*;
import uk.co.wingpath.xml.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;

public class FrontendT
    implements Frontend
{
    private final Product product;

    private boolean fullyRegistered = false;
    private boolean autoTest = false;
    private boolean autoTesting = false;
    private boolean debugging = false;

    private WFrame mainFrame;
    private ConfigurationFile configurationFile;
    private WFileChooser traceFileChooser;
    private StatusBar statusBar;
    private TreePanel treePanel;
    private CommandTable commandTable;
    private CommandEdit commandEdit;
    private int lastSelectedRow = 1;

    private Action exitAction;
    private Action runAction;
    private JToggleButton runButton;
    private Action traceAction;
    private Action loadAction;
    private Action saveAction;
    private Action useBrowserAction;

    private JMenu viewMenu;

    private Action viewLogAction;

    private Settings settings;
    private Variable<Boolean> traceSetting;
    private Variable<Boolean> rawTraceSetting;
    private Variable<Boolean> intTraceSetting;
    private Variable<Boolean> readTraceSetting;
    private Variable<Boolean> writeTraceSetting;
    private Variable<Boolean> useBrowser;

    private HelpViewer helpViewer;
    private Backend backend;
    private BroadcastReporter reporterNSB;      // doesn't include status bar
    private BroadcastReporter reporter;         // includes status bar
    private StderrReporter stderrReporter;
    private WindowReporter windowReporter;
    private FileReporter fileReporter;
    private Thread.UncaughtExceptionHandler exceptionHandler;

    private BackendState backendState =
        new BackendState (false, true, true, false);
    private BackendState.Source backendListeners = new BackendState.Source ();

    public FrontendT (Product product)
    {
        this.product = product;
    }

    private void buildSettings ()
    {
        settings = new Settings (product, debugging);
        traceSetting = settings.getGeneral ().getTracingSetting ();
        intTraceSetting = settings.getGeneral ().getIntTraceSetting ();
        rawTraceSetting = settings.getGeneral ().getRawTraceSetting ();
        readTraceSetting = settings.getGeneral ().getReadTraceSetting ();
        writeTraceSetting = settings.getGeneral ().getWriteTraceSetting ();
        useBrowser = settings.getGeneral ().getUseBrowser ();
    }

    private void buildGui ()
    {
        mainFrame = WFrame.create (null);
        mainFrame.setIconImages ("image/wingsn");

        helpViewer = new HelpViewer (mainFrame, product.getName () + " - Help",
            useBrowser);
        helpViewer.setErrorPrefix ("troubleshoot#");

        windowReporter = new WindowReporter (this,
            settings.getLogging ().getTerminalLevel ());
        windowReporter.setIncludeMillis (true);
        reporterNSB.addReporter (windowReporter);

        buildActions ();
        buildFrame ();
        reporter.addReporter (statusBar);

        setupListeners ();

        traceFileChooser = new WFileChooser (mainFrame);

        configurationFile = new ConfigurationFile (settings, product, this,
            reporter);
    }

    public void addBackendStateListener (BackendState.Listener listener)
    {
        backendListeners.addListener (listener);
        listener.stateChanged (backendState);
    }

    public void removeBackendStateListener (BackendState.Listener listener)
    {
        backendListeners.removeListener (listener);
    }

    private void buildBackend ()
    {
        backend = new Backend (this, settings, !product.hasSlaveMode (),
            exceptionHandler, reporter, reporterNSB);
    }

    public boolean isRunning ()
    {
        return backendState.isRunning;
    }

    public boolean isStopped ()
    {
        return backendState.isStopped;
    }

    public boolean isMaster ()
    {
        return backendState.isMaster;
    }

    public boolean isSlave ()
    {
        return backendState.isSlave;
    }

    public WFrame getMainFrame ()
    {
        return mainFrame;
    }

    public Product getProduct ()
    {
        return product;
    }

    public HelpViewer getHelpViewer ()
    {
        return helpViewer;
    }

    public Action getHelpAction (String id)
    {
        return helpViewer.getAction (id);
    }

    public StatusBar getStatusBar ()
    {
        return statusBar;
    }

    public void selectPanel (String tag)
    {
        treePanel.setSelected (tag);
    }

    private void setEnabled ()
    {
        if (backendState.isRunning || backendState.isStopped)
            runButton.setSelected (backendState.isRunning);
        runAction.setEnabled (backendState.isRunning || backendState.isStopped);
        loadAction.setEnabled (backendState.isStopped);
    }

    private void setupListeners ()
    {
        backend.addStateListener (new BackendState.Listener ()
            {
                public void stateChanged (BackendState state)
                {
                    backendState = state;
                    setEnabled ();
                    backendListeners.reportState (state);

                    if (autoTest && backendState.isRunning && !autoTesting)
                    {
                        autoTesting = true;
                        treePanel.setSelected ("command.table");
                        commandTable.sendAllCommands (null);
                    }
                }
            });
    }

    private void buildActions ()
    {
        exitAction = new AbstractAction ("Exit")
            {
                public void actionPerformed (ActionEvent e)
                {
                    backend.stop ();
                    System.exit (0);
                }
            };
        exitAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Exit from program");
        exitAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_X);
        exitAction.putValue (AbstractAction.ACCELERATOR_KEY,
            KeyStroke.getKeyStroke (KeyEvent.VK_Q, InputEvent.CTRL_MASK));

        runAction = new AbstractAction ("Run")
            {
                public void actionPerformed (ActionEvent e)
                {
                    if (backendState.isStopped)
                    {
                        statusBar.clear ();
                        backend.start ();
                    }
                    else if (backendState.isRunning)
                    {
                        backend.stop ();
                    }
                }
            };
        runAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Connect to slave");
        Gui.addShortCut (mainFrame.getRootPane (), "run_action",
            KeyEvent.VK_R, InputEvent.CTRL_MASK, runAction);

        traceAction = new AbstractAction ("Trace")
            {
                public void actionPerformed (ActionEvent e)
                {
                    traceSetting.setValue (!traceSetting.getValue ());
                }
            };
        traceAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Turn tracing on or off");
        Gui.addShortCut (mainFrame.getRootPane (), "trace_action",
            KeyEvent.VK_T, InputEvent.CTRL_MASK, traceAction);

        useBrowserAction = new AbstractAction ("Use Browser")
            {
                public void actionPerformed (ActionEvent e)
                {
                    AbstractButton button = (AbstractButton) e.getSource ();
                    useBrowser.setValue (button.isSelected ());
                }
            };
        useBrowserAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Use browser for viewing help");

        loadAction = new AbstractAction ("Load Configuration...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    configurationFile.loadConfiguration ();
                }
            };
        loadAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Load configuration from file");
        loadAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_L);
        loadAction.putValue (AbstractAction.ACCELERATOR_KEY,
            KeyStroke.getKeyStroke (KeyEvent.VK_L, InputEvent.CTRL_MASK));

        saveAction = new AbstractAction ("Save Configuration...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    configurationFile.saveConfiguration ();
                }
            };
        saveAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Save configuration to file");
        saveAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_S);
        saveAction.putValue (AbstractAction.ACCELERATOR_KEY,
            KeyStroke.getKeyStroke (KeyEvent.VK_S, InputEvent.CTRL_MASK));

        viewLogAction = new AbstractAction ("Log")
            {
                public void actionPerformed (ActionEvent e)
                {
                    windowReporter.showDialog ();
                }
            };
        viewLogAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Display log window");
    }

    private TreePanel buildSettingsPanel ()
    {
        Action helpAction = helpViewer.getAction ("settings");
        treePanel = new TreePanel (helpAction, false);
        treePanel.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    String tag = treePanel.getSelectedTag ();
                    int row = treePanel.getSelectedRow ();

                    if (tag.equalsIgnoreCase ("settings"))
                    {
                        treePanel.setSelected (row + 1);
                        return;
                    }
                    if (tag.equalsIgnoreCase ("commands"))
                    {
                        treePanel.setSelected (
                            row > lastSelectedRow ? row + 1 : row - 1);
                        return;
                    }

                    lastSelectedRow = row;
                    Action ha = treePanel.getHelpAction ();
                    Gui.addHelpShortCut (mainFrame.getRootPane (), ha);
                    mainFrame.getRootPane ().setDefaultButton (
                        treePanel.getDefaultButton ());
                }
            });

        TreePanel.Pane group;
        TreePanel.Pane pane;
        TextCard card;

        card = new TextCard ("settings", "Settings",
            helpViewer.getAction ("settings"),
            "<html><br>This is where you define settings to be " +
            "used by " + product.getName () +
            ".<br><br><br>");
        group = treePanel.addPane (card);
        group.addPane (new GeneralSettingsPanel (this, settings));
        group.addPane (new InterfaceSettingsPanel (this, false,
            settings.getSlaveInterface (), null));
        group.addPane (new BigValueSettingsPanel (this,
            settings.getBigValue (), null, null));
        group.addPane (
            new LogPanel (this, settings.getLogging (), debugging, false));
        group.addPane (new TracingPanel (this, settings, true));

        card = new TextCard ("commands", "Commands", null, "");
        group = treePanel.addPane (card);
        commandTable = new CommandTable (this, backend, settings);
        group.addPane (commandTable);
        CommandDefine commandDefine =
            new CommandDefine (this, backend, settings, reporterNSB);
        group.addPane (commandDefine);
        commandEdit = new CommandEdit (this, backend, settings, reporterNSB,
            commandDefine);
        group.addPane (commandEdit);

        treePanel.setSelected ("general");
        treePanel.expandAll ();
        treePanel.setRootVisible (false);

        return treePanel;
    }

    private void buildFrame ()
    {
        mainFrame.setTitle (product.getName () + " - " +
            product.getDescription () + " - Wingpath");
        Container contentPane = mainFrame.getContentPane ();

        // Build menus

        buildMenus ();

        // Build tool bar

        contentPane.add (buildToolBar (), BorderLayout.NORTH);

        // Build status bar
        statusBar = new StatusBar ("main", helpViewer);

        contentPane.add (statusBar, BorderLayout.SOUTH);

        // Build settings panel

        TreePanel settingsPanel = buildSettingsPanel ();
        contentPane.add (settingsPanel.getPanel (), BorderLayout.CENTER);

        // Add listener to shut down application cleanly

        mainFrame.setDefaultCloseOperation (
            WindowConstants.DO_NOTHING_ON_CLOSE);
        mainFrame.addWindowListener (new WindowAdapter ()
            {
                public void windowClosing (WindowEvent e)
                {
                    backend.stop ();
                    System.exit (0);
                }
            });

        // Set size of frame

        mainFrame.pack ();

        // Prevent user from making window too small.

        mainFrame.addComponentListener (
            new ComponentAdapter ()
            {
                public void componentResized (ComponentEvent e)
                {
                    Dimension d = mainFrame.getSize ();
                    Dimension mind = mainFrame.getPreferredSize ();
                    if (d.width < mind.width)
                        d.width = mind.width;
                    if (d.height < mind.height)
                        d.height = mind.height;
                    mainFrame.setSize (d);
                }
            });

/*
System.out.println ("frame " + mainFrame.getWidth () + " " + mainFrame.getHeight ());
treePanel.printSizes ();
commandEdit.printSizes ();
*/

        // Position frame in centre of screen if not under WM control

        if (!mainFrame.isLocationByPlatform ())
            mainFrame.setLocationRelativeTo (null);
    }

    private JToolBar buildToolBar ()
    {
        JToolBar tb = new JToolBar ();

        runButton = new JToggleButton (runAction);
        runButton.setFocusable (false);
        tb.add (runButton);

        final JToggleButton traceButton = new JToggleButton (traceAction);
        traceButton.setFocusable (false);
        traceSetting.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    traceButton.setSelected (traceSetting.getValue ());
                }
            });
        tb.add (traceButton);

        return tb;
    }

    private JMenu buildFileMenu ()
    {
        JMenu menu = new JMenu ("File");
        menu.setMnemonic (KeyEvent.VK_F);
        menu.add (loadAction);
        menu.add (saveAction);
        menu.add (exitAction);
        return menu;
    }

    private JMenu buildViewMenu ()
    {
        JMenu menu = new JMenu ("View");
        menu.setMnemonic (KeyEvent.VK_V);

        JMenuItem mi = new JMenuItem (viewLogAction);
        mi.setMnemonic (KeyEvent.VK_L);
        menu.add (mi);

        JCheckBoxMenuItem cmi = new JCheckBoxMenuItem (Gui.toolTipsAction);
        cmi.setSelected (true);
        cmi.setAccelerator (KeyStroke.getKeyStroke (
            KeyEvent.VK_F1, InputEvent.SHIFT_MASK));
        cmi.setModel (new DefaultButtonModel ()
            {
                @Override
                public boolean isSelected ()
                {
                    return ToolTipManager.sharedInstance ().isEnabled ();
                }

                @Override
                public void setSelected (boolean b)
                {
                    ToolTipManager.sharedInstance ().setEnabled (b);
                }
            });
        menu.add (cmi);

        return menu;
    }

    private JMenuItem registerMenuItem;

    private JMenu buildHelpMenu ()
    {
        JMenu menu = new JMenu ("Help");
        menu.setMnemonic (KeyEvent.VK_H);

        Action action = new AbstractAction ("Manual")
            {
                public void actionPerformed (ActionEvent e)
                {
                    helpViewer.view (product.getName ().toLowerCase ());
                }
            };
        JMenuItem mi = new JMenuItem (action);
        mi.setMnemonic (KeyEvent.VK_M);
        menu.add (mi);

        action = new AbstractAction ("Troubleshooting")
            {
                public void actionPerformed (ActionEvent e)
                {
                    helpViewer.view ("troubleshoot");
                }
            };
        mi = new JMenuItem (action);
        mi.setMnemonic (KeyEvent.VK_T);
        menu.add (mi);

        action = new AbstractAction ("Release Notes")
            {
                public void actionPerformed (ActionEvent e)
                {
                    helpViewer.view ("releases");
                }
            };
        mi = new JMenuItem (action);
        mi.setMnemonic (KeyEvent.VK_N);
        menu.add (mi);

/*
        final JCheckBoxMenuItem useBrowserItem =
            new JCheckBoxMenuItem (useBrowserAction);
        useBrowser.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    useBrowserItem.setSelected (useBrowser.getValue ());
                }
            });
        menu.add (useBrowserItem);
*/

        action = new AbstractAction ("Register...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    Registration.showDialog (product.getCode (),
                        product.getVersion (),
                        product.getName (),
                        new Registration.Callback ()
                        {
                            public void registered (boolean full)
                            {
                                fullyRegistered = full;
                                if (full)
                                    registerMenuItem.setEnabled (false);
                            }
                        });
                }
            };
        registerMenuItem = new JMenuItem (action);
        if (fullyRegistered)
            registerMenuItem.setEnabled (false);
        registerMenuItem.setMnemonic (KeyEvent.VK_R);
        menu.add (registerMenuItem);

        action = new AbstractAction ("About")
            {
                public void actionPerformed (ActionEvent e)
                {
                    SimpleDialog dialog = About.createDialog (FrontendT.this);
                    dialog.showDialog ();
                }
            };
        mi = new JMenuItem (action);
        mi.setMnemonic (KeyEvent.VK_A);
        menu.add (mi);

        return menu;
    }

    private void buildMenus ()
    {
        // Build menu bar

        JMenuBar menuBar = new JMenuBar ();
        mainFrame.setJMenuBar (menuBar);

        menuBar.add (buildFileMenu ());
        viewMenu = buildViewMenu ();
        menuBar.add (viewMenu);
        menuBar.add (buildHelpMenu ());
    }

    private boolean loadFiles (String [] args)
    {
        for (String arg : args)
        {
            if (arg.startsWith ("-"))
                continue;
            File f = new File (arg);
            try
            {
                f = f.getCanonicalFile ();
            }
            catch (IOException e)
            {
                statusBar.showError ("Can't load configuration\n" +
                    Exceptions.getMessage (e));
                return false;
            }
            if (!configurationFile.loadAndCheckConfigurationFile (f))
                return false;
        }

        return true;
    }

    private void setLookAndFeel (String laf)
    {
        try
        {
// System.out.println ("laf '" + laf + "'");
            if (laf == null || laf == "default")
                return;
            if (laf.equals ("metal"))
                laf = UIManager.getCrossPlatformLookAndFeelClassName ();
            else if (laf.equals ("system"))
                laf = UIManager.getSystemLookAndFeelClassName ();
            else if (laf.equals ("gtk"))
                laf = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
            else if (laf.equals ("windows"))
                laf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
            else if (laf.equals ("nimbus"))
                laf = "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel";
            else
                return;
// System.out.println ("laf " + laf);
            UIManager.setLookAndFeel (laf);
        }
        catch (Exception e)
        {
// System.out.println (e);
        }
    }

    private void updateFileLogging ()
    {
        LogSettings logSettings = settings.getLogging ();
        String filename = logSettings.getFileName ();
        try
        {
            fileReporter.setFile (filename);
        }
        catch (FileNotFoundException e)
        {
            logSettings.setFileName (fileReporter.getFile ());
            reporter.error (null, "Can't open log file %s",
                Exceptions.getMessage (e));
        }
    }

    private void setupFileLogging ()
    {
        LogSettings logSettings = settings.getLogging ();
        fileReporter = new FileReporter (logSettings.getFileLevel (),
            logSettings.getFileSize ());
        fileReporter.setIncludeMillis (true);
        fileReporter.setIncludeLevel (true);
        reporterNSB.addReporter (fileReporter);
        updateFileLogging ();
        logSettings.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    updateFileLogging ();
                }
            });
    }

    public void topLevel (final String [] args)
    {
        System.setProperty ("java.awt.Window.locationByPlatform", "true");

        // Initially log to stderr.
        reporterNSB = new BroadcastReporter ();
        reporter = new BroadcastReporter (reporterNSB);
        stderrReporter = StderrReporter.getInstance ();
        reporterNSB.addReporter (stderrReporter);

        exceptionHandler = new UncaughtExceptionHandler (reporterNSB);

        SwingUtilities.invokeLater (new Runnable ()
        {
            public void run ()
            {
                Thread.currentThread ().setUncaughtExceptionHandler (
                    exceptionHandler);
                String laf = null;

                for (String arg : args)
                {
                    if (arg.startsWith ("-laf="))
                        laf = arg.substring (5);
                    else if (arg.equals ("-auto"))
                        autoTest = true;
                    else if (arg.equals ("-debug"))
                        debugging = true;
                }

                setLookAndFeel (laf);
                ToolTipManager ttm = ToolTipManager.sharedInstance ();
                ttm.setDismissDelay (Integer.MAX_VALUE);
                Gui.setAppClassName (product.getName ());
                Registration.check (product.getCode (),
                    product.getVersion (),
                    product.getName (),
                    new Registration.Callback ()
                    {
                        public void registered (boolean full)
                        {
                            fullyRegistered = full;
                            buildSettings ();
                            setupFileLogging ();
                            buildBackend ();
                            buildGui ();
                            fileReporter.setReporter (statusBar);
                            reporterNSB.removeReporter (stderrReporter);
                            mainFrame.setVisible (true);
                            if (!loadFiles (args))
                                autoTest = false;
                            if (autoTest)
                                backend.start ();
                        }
                    });
            }
        });
    }
}


