
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 FrontendR
    implements Frontend
{
    private final Product product;

    private boolean fullyRegistered = false;
    private boolean autoStart = false;
    private boolean debugging = false;

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

    private RegisterTable registerTable;
    private FileRegisterTable fileRegisterTable;

    private Action exitAction;
    private Action runAction;
    private JToggleButton runButton;
    private Action pollAction;
    private Action traceAction;
    private Action loadAction;
    private Action saveAction;
    private Action csvSaveRegistersAction;
    private Action csvLoadRegistersAction;
    private Action csvSaveFileRegistersAction;
    private Action csvLoadFileRegistersAction;
    private Action useBrowserAction;

    private JMenu viewMenu;

    private Action viewLogAction;

    private Registers registers;
    private FileRegisters fileRegisters;
    private Settings settings;
    private Variable<Boolean> traceSetting;
    private Variable<Boolean> pollSetting;
    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 FrontendR (Product product)
    {
        this.product = product;
    }

    private void buildSettings ()
    {
        settings = new Settings (product, debugging);
        registers = settings.getRegisters ();
        fileRegisters = settings.getFileRegisters ();
        traceSetting = settings.getGeneral ().getTracingSetting ();
        pollSetting = settings.getGeneral ().getPoll ();
        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);
        csvFileChooser = 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);
        pollAction.setEnabled (backendState.isMaster);
        loadAction.setEnabled (backendState.isStopped);
        csvLoadRegistersAction.setEnabled (backendState.isStopped);
    }

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

        if (product.hasSlaveMode ())
        {
            final Variable<Backend.Mode> mode =
                settings.getGeneral ().getMode ();
            mode.addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        backend.setMode (mode.getValue ());
                    }
                });
        }
    }

    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)
                        startBackend ();
                    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);
        if (product.hasSlaveMode ())
        {
            settings.getGeneral ().getMode ().addValueListener (
                new ValueListener ()
                {
                    public void valueChanged (ValueEvent e)
                    {
                        String tooltip;
                        switch (settings.getGeneral ().getMode ().getValue ())
                        {
                            case master:
                                tooltip = "Connect to slave";
                                break;
                            case slave:
                                tooltip = "Accept requests from masters";
                                break;
                            case monitor:
                                tooltip = "Connect to slave and " +
                                    "accept requests from masters";
                                break;
                            default:
                                tooltip = "Connect to slave";
                                break;
                        }
                        runAction.putValue (AbstractAction.SHORT_DESCRIPTION,
                            tooltip);
                    }
                });
        }

        pollAction = new AbstractAction ("Poll")
            {
                public void actionPerformed (ActionEvent e)
                {
                    pollSetting.setValue (!pollSetting.getValue ());
                }
            };
        pollAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Enable automatic polling of all registers");
        Gui.addShortCut (mainFrame.getRootPane (), "poll_action",
            KeyEvent.VK_P, InputEvent.CTRL_MASK, pollAction);


        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)
                {
                    registerTable.cancelEditing ();
                    fileRegisterTable.cancelEditing ();
                    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)
                {
                    if (!registerTable.stopEditing ())
                        return;
                    if (!fileRegisterTable.stopEditing ())
                        return;
                    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));

        csvLoadRegistersAction = new AbstractAction ("Import Registers...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    registerTable.cancelEditing ();
                    csvLoad (registers);
                }
            };
        csvLoadRegistersAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Import registers from CSV file");
        csvLoadRegistersAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_I);

        csvSaveRegistersAction = new AbstractAction ("Export Registers...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    if (!registerTable.stopEditing ())
                        return;
                    if (!fileRegisterTable.stopEditing ())
                        return;
                    csvSave (registers);
                }
            };
        csvSaveRegistersAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Export registers to CSV file");
        csvSaveRegistersAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_E);

        csvLoadFileRegistersAction = new AbstractAction ("Import File Registers...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    fileRegisterTable.cancelEditing ();
                    csvLoad (fileRegisters);
                }
            };
        csvLoadFileRegistersAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Import file registers from CSV file");
        // csvLoadFileRegistersAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_I);

        csvSaveFileRegistersAction = new AbstractAction ("Export File Registers...")
            {
                public void actionPerformed (ActionEvent e)
                {
                    if (!fileRegisterTable.stopEditing ())
                        return;
                    csvSave (fileRegisters);
                }
            };
        csvSaveFileRegistersAction.putValue (AbstractAction.SHORT_DESCRIPTION,
            "Export file registers to CSV file");
        // csvSaveFileRegistersAction.putValue (AbstractAction.MNEMONIC_KEY, KeyEvent.VK_E);

        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") ||
                        tag.equalsIgnoreCase ("registers") ||
                        tag.equalsIgnoreCase ("fileRegisters"))
                    {
                        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));
        if (product.hasSlaveMode ())
        {
            group.addPane (new InterfaceSettingsPanel (this, true,
                settings.getMasterInterface (), null));
        }
        group.addPane (new AddressSettingsPanel (this,
            settings.getAddress (), registers));
        group.addPane (new BigValueSettingsPanel (this,
            settings.getBigValue (), registers, fileRegisters));
        group.addPane (new PollingSettingsPanel (this, settings));
        if (product.hasSlaveMode ())
        {
            group.addPane (new DeviceIdSettingsPanel (this, settings));
        }
        group.addPane (
            new LogPanel (this, settings.getLogging (), debugging, false));
        group.addPane (new TracingPanel (this, settings, false));

        card = new TextCard ("registers", "Registers", null, "");
        group = treePanel.addPane (card);
        group.addPane (registerTable);
        group.addPane (new RegisterAdd (this, registers, false));
        group.addPane (new RegisterEdit (this, registers, false));

        card = new TextCard ("fileRegisters", "File Registers", null, "");
        group = treePanel.addPane (card);
        group.addPane (fileRegisterTable);
        group.addPane (new RegisterAdd (this, fileRegisters, true));
        group.addPane (new RegisterEdit (this, fileRegisters, 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 csvLoad (final CsvSavable csvSavable)
    {
        statusBar.clear ("3");
        final File file = csvFileChooser.chooseLoadFile (
            product.getName () + ": Import " + csvSavable.csvName (),
            "Import",
            "Import " + csvSavable.csvName () + " from selected file");
        if (file != null)
        {
            statusBar.clear ();
            statusBar.showMessage (
                "Importing " + csvSavable.csvName () + " ...");
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        mainFrame.setCursor (
                            Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));
                        try
                        {
                            if (loadAndCheckCsvFile (file, csvSavable))
                            {
                                statusBar.showMessage (
                                    csvSavable.csvName () + " imported");
                            }
                        }
                        finally
                        {
                            mainFrame.setCursor (Cursor.getDefaultCursor ());
                        }
                    }
                });
        }
    }

    private boolean loadAndCheckCsvFile (File file, CsvSavable csvSavable)
    {
        File tempFile = configurationFile.backupConfiguration ();
        try
        {
            csvLoadFile (file, csvSavable);
            csvSavable.csvCheck ();
        }
        catch (ValueException e)
        {
            configurationFile.restoreConfiguration (tempFile);
            statusBar.showError (
                "Can't import from " + file.getPath () + "\n" +
                e.getMessage ());
            return false;
        }
        catch (IOException e)
        {
            // IOExceptions (sometimes?) include the filename in their messages,
            // so don't repeat it.
            configurationFile.restoreConfiguration (tempFile);
            statusBar.showError (
                "Can't import " + csvSavable.csvName () + "\n" +
                Exceptions.getMessage (e));
            return false;
        }
        finally
        {
            if (tempFile != null)
                tempFile.delete ();
        }

        return true;
    }

    private void csvLoadFile (File file, CsvSavable csvSavable)
        throws ValueException, IOException
    {
        Utf8Reader in = null;
        try
        {
            in = new Utf8Reader (file);
            csvSavable.csvLoad (in);
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close ();
                }
                catch (IOException e)
                {
                }
            }
        }
    }

    private void csvSave (final CsvSavable csvSavable)
    {
        statusBar.clear ("4");
        final File file = csvFileChooser.chooseSaveFile (
            product.getName () + ": Export " + csvSavable.csvName (),
            "Export",
            "Export " + csvSavable.csvName () + " to selected file");
        if (file != null)
        {
            statusBar.clear ();
            statusBar.showMessage (
                "Exporting " + csvSavable.csvName () + " ...");
            EventQueue.invokeLater (new Runnable ()
                {
                    public void run ()
                    {
                        mainFrame.setCursor (
                            Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));
                        try
                        {
                            if (csvSaveToFile (file, csvSavable))
                            {
                                statusBar.showMessage (
                                    csvSavable.csvName () + " exported");
                            }
                        }
                        finally
                        {
                            mainFrame.setCursor (Cursor.getDefaultCursor ());
                        }
                    }
                });
        }
    }

    private boolean csvSaveToFile (File file, CsvSavable csvSavable)
    {
        BufferedWriter out = null;
        try
        {
            out = new BufferedWriter (new OutputStreamWriter (
                new FileOutputStream (file), "UTF-8"));
            csvSavable.csvSave (out);
            return true;
        }
        catch (IOException e)
        {
            statusBar.showError (
                "Can't export " + csvSavable.csvName () + "\n" +
                Exceptions.getMessage (e));
        }
        finally
        {
            if (out != null)
            {
                try
                {
                    out.close ();
                }
                catch (IOException e)
                {
                }
            }
        }

        return false;
    }

    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 register table

        registerTable = new RegisterTable (this, backend, settings, registers);
        viewMenu.add (registerTable.buildColumnMenu ("Columns", KeyEvent.VK_C));

        // Build file register table

        fileRegisterTable = new FileRegisterTable (this, backend, settings,
            fileRegisters);
        viewMenu.add (fileRegisterTable.buildColumnMenu ("File Columns",
            KeyEvent.VK_F));

        // 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 () + " min: " + mainFrame.getMinimumSize ());
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 pollButton = new JToggleButton (pollAction);
        pollButton.setFocusable (false);
        pollSetting.addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    pollButton.setSelected (pollSetting.getValue ());
                }
            });
        tb.add (pollButton);

        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 (csvLoadRegistersAction);
        menu.add (csvSaveRegistersAction);
        menu.add (csvLoadFileRegistersAction);
        menu.add (csvSaveFileRegistersAction);
        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 (FrontendR.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 startBackend ()
    {
        statusBar.clear ("6");
        if (!registerTable.stopEditing () || !fileRegisterTable.stopEditing ())
        {
            return false;
        }
        try
        {
            registers.checkModel ();
            fileRegisters.checkRegisters ();
        }
        catch (ValueException ex)
        {
            statusBar.showError (ex.getHelpId (),
                "Inconsistent register settings\n" +
                ex.getMessage ());
            return false;
        }
        backend.start ();
        return true;
    }

    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"))
                        autoStart = 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))
                                autoStart = false;
                            if (autoStart)
                                backend.start ();
                        }
                    });
            }
        });
    }
}


