/*
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware;

import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.Smack;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.StanzaBuilder;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.ui.ChatFrame;
import org.jivesoftware.spark.ui.RawPacketSender;
import org.jivesoftware.spark.util.*;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.alerts.InputTextAreaDialog;
import org.jivesoftware.sparkimpl.plugin.layout.LayoutSettingsManager;
import org.jivesoftware.sparkimpl.plugin.manager.Enterprise;
import org.jivesoftware.sparkimpl.settings.JiveInfo;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jivesoftware.sparkimpl.updater.CheckUpdates;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * The <code>MainWindow</code> class acts as both the DockableHolder and the proxy
 * to the Workspace in Spark.
 */
public final class MainWindow extends ChatFrame {
	private final CopyOnWriteArrayList<MainWindowListener> listeners = new CopyOnWriteArrayList<>();
    private final LocalPreferences pref = SettingsManager.getLocalPreferences();

    private final JMenu connectMenu = new JMenu();
    private final JMenu contactsMenu = new JMenu();
    private final JMenu actionsMenu = new JMenu();
    private final JMenu pluginsMenu = new JMenu();
    private final JMenu helpMenu = new JMenu();

    private JMenuItem preferenceMenuItem;
    private JCheckBoxMenuItem alwaysOnTopItem;

    private final JMenuItem menuAbout = new JMenuItem(SparkRes.getImageIcon(SparkRes.Icon.INFORMATION_IMAGE));
    private final JMenuItem sparkforumItem = new JMenuItem();
    private final JMenuItem sparkTranslationItem = new JMenuItem();

    private final JMenuBar mainWindowBar = new JMenuBar();

    private boolean focused;
    private final JToolBar topToolbar = new JToolBar();
    private JSplitPane splitPane;
    private static MainWindow singleton;

    /**
     * Returns the singleton instance of <CODE>MainWindow</CODE>,
     * creating it if necessary.
     * <p/>
     *
     * @return the singleton instance of <Code>MainWindow</CODE>
     */
    public static synchronized MainWindow getInstance() {
        if (null == singleton) {
            singleton = new MainWindow(JiveInfo.getName(), SparkManager.getApplicationImage());
        }
        return singleton;
    }


    /**
     * Constructs the UI for the MainWindow. The MainWindow UI is the container for the
     * entire Spark application.
     *
     * @param title the title of the frame.
     * @param icon  the icon used in the frame.
     */
    private MainWindow(String title, ImageIcon icon) {
        // Initialize and dock the menus
        buildMenu();
        // Add Workspace Container
        getContentPane().setLayout(new BorderLayout());

        setMinimumSize( new Dimension( 100, 200 ) );
        final Rectangle mainWindowBounds = LayoutSettingsManager.getLayoutSettings().getMainWindowBounds();
        if ( mainWindowBounds == null || mainWindowBounds.width <= 0 || mainWindowBounds.height <= 0 )
        {
            // Use default settings.
            setSize(500, 520);
            GraphicUtils.centerWindowOnScreen(this);
        }
        else
        {
            setBounds( mainWindowBounds );
        }

        // Add menubar
        this.setJMenuBar(mainWindowBar);
        this.getContentPane().add(topToolbar, BorderLayout.NORTH);

        setTitle(title);
        setIconImage(icon.getImage());

        addComponentListener( new ComponentAdapter()
        {
            @Override
            public void componentResized( ComponentEvent e )
            {
                LayoutSettingsManager.getLayoutSettings().setMainWindowBounds( getBounds() );
            }

            @Override
            public void componentMoved( ComponentEvent e )
            {
                LayoutSettingsManager.getLayoutSettings().setMainWindowBounds( getBounds() );
            }
        } );

        // Setup WindowListener to be the proxy to the actual window listener
        // which cannot normally be used outside of the Window component because
        // of protected access.
        addWindowListener(new WindowAdapter() {

            /**
             * This event fires when the application is closing.
             * This allows Plugins to do any persistence or other
             * work before exiting.
             *
             * @param e WindowEvent is never used.
             */
            @Override
			public void windowClosing(WindowEvent e) {
                setVisible(false);
            }
        });

        this.addWindowFocusListener(new MainWindowFocusListener());
    }

    /**
     * Adds a MainWindow listener to {@link MainWindow}. The
     * listener will be called when either the MainWindow has been minimized, maximized,
     * or is shutting down.
     */
    public void addMainWindowListener(MainWindowListener listener) {
        listeners.addIfAbsent(listener);
    }

    /**
     * Removes the specified {@link MainWindowListener}.
     */
    public void removeMainWindowListener(MainWindowListener listener) {
        listeners.remove(listener);
    }

    /**
     * Notifies all {@link MainWindowListener}s that the <code>MainWindow</code>
     * is shutting down.
     */
    private void fireWindowShutdown()
    {
        for ( MainWindowListener listener : listeners )
        {
            try
            {
                listener.shutdown();
            }
            catch ( Exception e )
            {
                Log.error( "A MainWindowListener (" + listener + ") threw an exception while processing a 'shutdown' event.", e );
            }
        }
    }

    /**
     * Prepares Spark for shutting down by first calling all {@link MainWindowListener}s and
     * setting the Agent to be offline.
     */
    public void shutdown() {
        final AbstractXMPPConnection con = SparkManager.getConnection();
        if (con.isConnected()) {
            // Send disconnect.
            con.disconnect();
        }
        // Notify all MainWindowListeners
        try {
            fireWindowShutdown();
        }
        catch (Exception ex) {
            Log.error(ex);
        }
        // Close application.
        System.exit(0);
    }

    /**
     * Prepares Spark for shutting down by first calling all {@link MainWindowListener}s and
     * setting the Agent to be offline.
     *
     * @param sendStatus true if Spark should send a presence with a status message.
     */
    public void logout(boolean sendStatus) {
        final XMPPConnection con = SparkManager.getConnection();
        String status = null;
        if (con.isConnected() && sendStatus) {
            final InputTextAreaDialog inputTextDialog = new InputTextAreaDialog();
            status = inputTextDialog.getInput(Res.getString("title.status.message"), Res.getString("message.current.status"),
                SparkRes.getImageIcon(SparkRes.Icon.USER1_MESSAGE_24x24), this);
        }

        if (status != null || !sendStatus)
        {
	        // Notify all MainWindowListeners
	        try {
	            // Set auto-login to false;
	            pref.setAutoLogin(false);
	            fireWindowShutdown();
	            setVisible(false);
	        }
	        finally {
	            closeConnectionAndInvoke(status);
	        }
        }
    }

    /**
     * Closes the current connection and restarts Spark.
     *
     * @param reason the reason for logging out. This can be if user gave no reason.
     */
    public void closeConnectionAndInvoke(String reason) {
        final AbstractXMPPConnection con = SparkManager.getConnection();
        if (con.isConnected()) {
            if (reason != null) {
                Presence byePresence = StanzaBuilder.buildPresence()
                    .ofType(Presence.Type.unavailable)
                    .setStatus(reason)
                    .setPriority(-1)
                    .build();
                try
                {
                    con.disconnect(byePresence);
                }
                catch ( SmackException.NotConnectedException e )
                {
                    Log.error( "Unable to sign out with presence.", e);
                    con.disconnect();
                }
            }
            else {
                con.disconnect();
            }
        }
        if (!restartApplicationWithScript()) {
            restartApplicationWithJava();
        }
    }
    
    private File getLibDirectory() {
        File jarFile;
        try{
            jarFile = new File(Spark.class.getProtectionDomain().getCodeSource().getLocation().toURI());
        } catch(Exception e) {
            Log.error("Cannot get jar file containing the startup class", e);
            return null;
        }
        if ( !jarFile.getName().endsWith(".jar") ) {
            Log.error("The startup class is not packaged in a jar file");
            return null;
        }
        return jarFile.getParentFile();
    }
    
    private String getClasspath() throws IOException {
        File libDir = getLibDirectory();
        String libPath = null;
        String[] files = new String[0];
        if (libDir != null) {
            libPath = libDir.getCanonicalPath();
            files = libDir.list((dir, name) -> name.endsWith(".jar"));
        }
        if (files == null) {
            return "";
        }
        StringBuilder classpath = new StringBuilder();
        for (String file : files) {
            classpath.append(libPath).append(File.separatorChar).append(file).append(File.pathSeparatorChar);
        }
        return classpath.toString();
    }

    private String getCommandPath() throws IOException{
        File libDir = getLibDirectory();
        if (libDir == null) {
            return null;
        }
        return libDir.getParentFile().getCanonicalPath();
    }
    
    public boolean restartApplicationWithScript() {
        String command = null;
        try {
            String commandPath = getCommandPath();
            if (commandPath == null) {
                return false;
            }
            if (Spark.isWindows()) {
                String sparkExe = commandPath + File.separator + Default.getString(Default.SHORT_NAME) + ".exe";
                if (!new File(sparkExe).exists()) {
                    Log.warning("Client EXE file does not exist");
                    return false;
                }
                String starterExe = commandPath + File.separator + "starter.exe";
                if (!new File(starterExe).exists()) {
                    Log.warning("Starter EXE file does not exist");
                    return false;
                }
                command = starterExe + " \"" + sparkExe + "\"";
            } else if (Spark.isLinux()) {
                command = commandPath + File.separator + Default.getString(Default.SHORT_NAME);
                if (!new File(command).exists()) {
                    Log.warning("Client startup script does not exist");
                    return false;
                }
            } else if (Spark.isMac()) {
                command = "open -a " + Default.getString(Default.SHORT_NAME);
            }

            Runtime.getRuntime().exec(command);
            System.exit(0);
            return true;
        } catch (Exception e) {
            Log.error("Error trying to restart application with script", e);
            return false;
        }
    }
    
    public boolean restartApplicationWithJava() {
        String javaBin = System.getProperty("java.home") + File.separatorChar + "bin" + File.separatorChar + "java";
        try {
            String[] toExec = new String[] {
                    javaBin, "-cp", getClasspath(), "org.jivesoftware.Spark"};
            Runtime.getRuntime().exec(toExec);
        } catch (Exception e) {
            Log.error("Error trying to restart application with java", e);
            return false;
        }
        System.exit(0);
        return true;
    }

    /**
     * Set up the Main Toolbar with File, Tools and Help.
     */
    private void buildMenu() {
        // setup file menu
        final JMenuItem exitMenuItem = new JMenuItem();
        // Setup ResourceUtils
        ResourceUtils.resButton(connectMenu, "&" + Res.getString("menuitem.connect"));
        ResourceUtils.resButton(contactsMenu, Res.getString("menuitem.contacts"));
        ResourceUtils.resButton(actionsMenu, Res.getString("menuitem.actions"));
        ResourceUtils.resButton(exitMenuItem, Res.getString("menuitem.exit"));
        ResourceUtils.resButton(pluginsMenu, Res.getString("menuitem.plugins"));

        exitMenuItem.setIcon(null);

        mainWindowBar.add(connectMenu);
        mainWindowBar.add(contactsMenu);
        mainWindowBar.add(actionsMenu);
        mainWindowBar.add(helpMenu);

        preferenceMenuItem = new JMenuItem(SparkRes.getImageIcon(SparkRes.Icon.PREFERENCES_IMAGE));
        preferenceMenuItem.setText(Res.getString("title.spark.preferences"));
        preferenceMenuItem.addActionListener(e -> {
            SparkManager.getPreferenceManager().showPreferences();
        });

        // Show the "Preferences" menu item ONLY in Maintenance Mode or when DISABLE_PREFERENCES_MENU_ITEM = false and Client Control allows it.
        File myMaintFile = new File(Default.getString(Default.MAINTENANCE_FILE_PATH));
        final boolean maintMode = (myMaintFile.exists() && !myMaintFile.isDirectory());
        final boolean prefsAllowed = (!Default.getBoolean(Default.DISABLE_PREFERENCES_MENU_ITEM) && Enterprise.containsFeature(Enterprise.PREFERENCES_MENU_FEATURE));
        if (maintMode || prefsAllowed) {
            connectMenu.add(preferenceMenuItem);
        }

        alwaysOnTopItem = new JCheckBoxMenuItem();
        ResourceUtils.resButton(alwaysOnTopItem, Res.getString("menuitem.always.on.top"));
        alwaysOnTopItem.addActionListener( actionEvent -> {
        	pref.setMainWindowAlwaysOnTop(alwaysOnTopItem.isSelected());
        	MainWindow.getInstance().setAlwaysOnTop(alwaysOnTopItem.isSelected());
        } );

        alwaysOnTopItem.setSelected(pref.isMainWindowAlwaysOnTop());
        this.setAlwaysOnTop(pref.isMainWindowAlwaysOnTop());

        connectMenu.add(alwaysOnTopItem);

        // Set up the Logout and Exit menus...
        if (!Default.getBoolean(Default.DISABLE_EXIT) && Enterprise.containsFeature(Enterprise.LOGOUT_EXIT_FEATURE)) {
        	connectMenu.addSeparator();
        	if(!Default.getBoolean(Default.HIDE_SAVE_PASSWORD_AND_AUTO_LOGIN) && pref.isPswdAutologin()) {
        		JMenuItem logoutMenuItem = new JMenuItem();
        		ResourceUtils.resButton(logoutMenuItem, Res.getString("menuitem.logout.no.status"));
        		logoutMenuItem.addActionListener( e -> logout(false) );

        		JMenuItem logoutWithStatus = new JMenuItem();
        		ResourceUtils.resButton(logoutWithStatus, Res.getString("menuitem.logout.with.status"));
        		logoutWithStatus.addActionListener( e -> logout(true) );

        		connectMenu.add(logoutMenuItem);
        		connectMenu.add(logoutWithStatus);
        		connectMenu.addSeparator();
        	}
        	connectMenu.add(exitMenuItem);
        }

        JMenuItem updateMenu= new JMenuItem("", SparkRes.getImageIcon(SparkRes.Icon.CHECK_UPDATE));
        ResourceUtils.resButton(updateMenu, Res.getString("menuitem.check.for.updates"));
        updateMenu.addActionListener( e -> checkForUpdates(true) );

        // Add Error Dialog Viewer
        final Action viewErrors = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent e) {
                File logFile = Log.getLogFilePath();
                if (logFile == null) {
                    JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "See console for logs.", "Error Log", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                if (!logFile.exists()) {
                    JOptionPane.showMessageDialog(SparkManager.getMainWindow(), "No error logs found.", "Error Log", JOptionPane.INFORMATION_MESSAGE);
                    return;
                }
                showErrorLog();
            }
        };

        viewErrors.putValue(Action.NAME, Res.getString("menuitem.view.logs"));

        final Action viewHelpGuideAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                BrowserLauncher.openURL(Default.getString(Default.HELP_USER_GUIDE));
            }
        };

        if (!Default.getBoolean(Default.HELP_USER_GUIDE_DISABLED) && Enterprise.containsFeature(Enterprise.HELP_USERGUIDE_FEATURE)) {
        	viewHelpGuideAction.putValue(Action.NAME,
        			Res.getString("menuitem.user.guide"));
        	viewHelpGuideAction.putValue(Action.SMALL_ICON,
        			SparkRes.getImageIcon(SparkRes.Icon.SMALL_QUESTION));
        	helpMenu.add(viewHelpGuideAction);
        }

        if (!Default.getBoolean(Default.HELP_FORUM_DISABLED) && Enterprise.containsFeature(Enterprise.HELP_FORUMS_FEATURE)) {
            helpMenu.add(sparkforumItem);
            helpMenu.add(sparkTranslationItem);
        }

	// Build Help Menu
	if (!Default.getBoolean(Default.DISABLE_UPDATES) && Enterprise.containsFeature(Enterprise.UPDATES_FEATURE)) helpMenu.add(updateMenu);

	helpMenu.addSeparator();
	helpMenu.add(viewErrors);
	helpMenu.add(menuAbout);

	// ResourceUtils - Adds mnemonics
	ResourceUtils.resButton(preferenceMenuItem, Res.getString("menuitem.preferences"));
	ResourceUtils.resButton(helpMenu, Res.getString("menuitem.help"));
	ResourceUtils.resButton(menuAbout, Res.getString("menuitem.about"));

	if (!Default.getString(Default.HELP_FORUM_TEXT).isEmpty()) {
	    ResourceUtils.resButton(sparkforumItem, Default.getString(Default.HELP_FORUM_TEXT));
	} else {
	    ResourceUtils.resButton(sparkforumItem, Res.getString("menuitem.online.help"));
	    ResourceUtils.resButton(sparkTranslationItem, Res.getString("menuitem.translation"));
	}
        // Register shutdown with the exit menu.
	exitMenuItem.addActionListener(new AbstractAction() {
	    @Override
		public void actionPerformed(ActionEvent e) {
		shutdown();
	    }
	});

        sparkforumItem.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                BrowserLauncher.openURL(Default.getString(Default.HELP_FORUM));
            }
        });

        sparkTranslationItem.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                BrowserLauncher.openURL("https://explore.transifex.com/igniterealtime/spark/");
            }
        });

        // Show About Box
	menuAbout.addActionListener(new AbstractAction() {
	    @Override
		public void actionPerformed(ActionEvent e) {
		showAboutBox();
	    }
	});

	if (!Default.getBoolean(Default.DISABLE_UPDATES) && Enterprise.containsFeature(Enterprise.UPDATES_FEATURE)) {
		// Execute spark update checker after one minute.
		final TimerTask task = new SwingTimerTask() {
			@Override
			public void doRun() {
				checkForUpdates(false);
			}
		};

		TaskEngine.getInstance().schedule(task, 60000);
	}

	if(pref.isDebuggerEnabled())
	{
	    JMenuItem rawPackets = new JMenuItem(SparkRes.getImageIcon(SparkRes.Icon.TRAY_IMAGE));
	    rawPackets.setText("Send Packets");
	    rawPackets.addActionListener( e -> new RawPacketSender() );
	    connectMenu.add(rawPackets,2);
	}
    }

    /**
     * Returns the JMenuBar for the MainWindow. You would call this if you
     * wished to add or remove menu items to the main menubar. (File | Tools | Help)
     */
    public JMenuBar getMenu() {
        return mainWindowBar;
    }

    /**
     * Returns the Menu in the JMenuBar by its name. For example:
     * <pre>
     * JMenu toolsMenu = getMenuByName("Tools");
     * </pre>
     */
    public JMenu getMenuByName(String name) {
        for (int i = 0; i < getMenu().getMenuCount(); i++) {
            JMenu menu = getMenu().getMenu(i);
            if (menu.getText().equals(name)) {
                return menu;
            }
        }
        return null;
    }

    /**
     * Returns true if the Spark window is in focus.
     */
    @Override
	public boolean isInFocus() {
        return focused;
    }

    private class MainWindowFocusListener implements WindowFocusListener {

        @Override
		public void windowGainedFocus(WindowEvent e) {
            focused = true;
        }

        @Override
		public void windowLostFocus(WindowEvent e) {
            focused = false;
        }
    }

    /**
     * Return the top toolbar in the Main Window to allow for customization.
     */
    public JToolBar getTopToolBar() {
        return topToolbar;
    }

    /**
     * Checks for the latest update on the server.
     *
     * @param forced true if you want to bypass the normal checking security.
     */
    private void checkForUpdates(final boolean forced) {
        final CheckUpdates updater = new CheckUpdates();
        try {
            final SwingWorker updateThread = new SwingWorker() {
                @Override
				public Object construct() {
                    try {
                        Thread.sleep(50);
                    }
                    catch (InterruptedException e) {
                        Log.error(e);
                    }
                    return "ok";
                }

                @Override
				public void finished() {
                    try {
                        updater.checkForUpdate(forced);
                    }
                    catch (Exception e) {
                        Log.error("There was an error while checking for a new update.", e);
                    }
                }
            };

            updateThread.start();
        }
        catch (Exception e) {
            Log.warning("Error updating.", e);
        }
    }

    /**
     * Returns About Box Pane.
     * @return JEditorPane About Box
     */
    private JEditorPane getAboutBoxPane() {
        // Get values from default.properties file
        final String APPLICATION_INFO1 = Default.getString(Default.APPLICATION_INFO1);
        final String APPLICATION_INFO2 = Default.getString(Default.APPLICATION_INFO2);
        final String APPLICATION_INFO3 = Default.getString(Default.APPLICATION_INFO3);
        final String APPLICATION_LICENSE_LINK = Default.getString(Default.APPLICATION_LICENSE_LINK);
        final String APPLICATION_LICENSE_LINK_TXT = Default.getString(Default.APPLICATION_LICENSE_LINK_TXT);
        final String APPLICATION_INFO4 = Default.getString(Default.APPLICATION_INFO4);
        final String APPLICATION_LINK = Default.getString(Default.APPLICATION_LINK);
        final String APPLICATION_LINK_TXT = Default.getString(Default.APPLICATION_LINK_TXT);
        final boolean DISPLAY_DEV_INFO = Default.getBoolean(Default.DISPLAY_DEV_INFO);
        final String JAVA_VERSION = Default.getString(Default.JAVA_VERSION);

        // Construct About Box text
        StringBuilder aboutBoxText = new StringBuilder();
        aboutBoxText.append(JiveInfo.getName()).append(" ").append(JiveInfo.getVersion());
        // Add APPLICATION_INFO1 if not empty
        if (!("".equals(APPLICATION_INFO1))) {
            aboutBoxText.append("<br/>").append(APPLICATION_INFO1);
        }
        // Add APPLICATION_INFO2 if not empty
        if (!("".equals(APPLICATION_INFO2))) {
            aboutBoxText.append("<br/>").append(APPLICATION_INFO2);
        }
        // Add APPLICATION_INFO3 if not empty
        if (!("".equals(APPLICATION_INFO3))) {
            aboutBoxText.append("<br/>").append(APPLICATION_INFO3);
        }
        // Add APPLICATION_LICENSE_LINK if not empty
        if (!("".equals(APPLICATION_LICENSE_LINK))) {
            aboutBoxText.append("<br/><a href=\"").append(APPLICATION_LICENSE_LINK).append("\">")
                .append(APPLICATION_LICENSE_LINK_TXT).append("</a>");
        }
        // Add APPLICATION_LINK if not empty
        if (!("".equals(APPLICATION_LINK))) {
            aboutBoxText.append("<br/><a href=\"").append(APPLICATION_LINK).append("\">")
                .append(APPLICATION_LINK_TXT).append("</a>");
        }
        // Add APPLICATION_INFO4 if not empty
        if (!("".equals(APPLICATION_INFO4))) {
            aboutBoxText.append("<br/>").append(APPLICATION_INFO4);
        }
        aboutBoxText.append("<br/>Smack Version: ").append(Smack.getVersion());
        if (DISPLAY_DEV_INFO) {
            // Add Java JRE Version if is empty
            if ("".equals(JAVA_VERSION)) {
                aboutBoxText.append("<br/>JRE Version: ").append(System.getProperty("java.version"));
                aboutBoxText.append(" ").append(System.getProperty("os.arch"));
            }
        }

        // copy window style
        JPanel p = new JPanel();
        Font font = p.getFont();

        // create some css from the JPanel's font
        String style = ("font-family:" + font.getFamily() + ";") +
            "font-weight:" + (font.isBold() ? "bold" : "normal") + ";" +
            "font-size:" + font.getSize() + "pt;";

        // assemble html
        JEditorPane ep = new JEditorPane("text/html",
            "<html><body style=\"" + style + "\">" + aboutBoxText + "</body></html>");
        // handle link events
        ep.addHyperlinkListener(e -> {
            if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
                BrowserLauncher.openURL(e.getURL().toString());
            }
        });
        ep.setEditable(false);
        ep.setBackground(p.getBackground());
        return ep;
    }

    /**
     * Displays the About Box for Spark.
     */
    private static void showAboutBox() {
        JOptionPane.showMessageDialog(SparkManager.getMainWindow(), SparkManager.getMainWindow().getAboutBoxPane(),
            Res.getString("title.about"), JOptionPane.INFORMATION_MESSAGE, SparkRes.getImageIcon(SparkRes.Icon.MAIN_IMAGE));
    }

    /**
     * Displays the Spark error log.
     */
    private void showErrorLog() {
        final File logDir = new File(Spark.getLogDirectory(), "errors.log");
        // Read file and show
        final String errorLogs = URLFileSystem.getContents(logDir);

        final JFrame frame = new JFrame(Res.getString("title.client.logs"));
        frame.setLayout(new BorderLayout());
        frame.setIconImage(SparkManager.getApplicationImage().getImage());

        final JTextPane pane = new JTextPane();
        pane.setBackground(Color.white);
        pane.setFont(new Font("Dialog", Font.PLAIN, 12));
        pane.setEditable(false);
        pane.setText(errorLogs);
        
        final JButton copyButton = new JButton(Res.getString("button.copy.to.clipboard"));
        copyButton.setIcon(SparkRes.getImageIcon(SparkRes.Icon.COPY_16x16));
        final JButton openFolder = new JButton(Res.getString("open.folder"));
        openFolder.setIcon(SparkRes.getImageIcon(SparkRes.Icon.FOLDER));

        final JPanel panel = new JPanel();
        panel.add(openFolder, BorderLayout.LINE_START);
        panel.add(copyButton, BorderLayout.LINE_END);

        frame.add(new JScrollPane(pane), BorderLayout.CENTER);
        frame.add(panel, BorderLayout.SOUTH);


        copyButton.addActionListener( e -> {
            SparkManager.setClipboard(errorLogs);
            copyButton.setEnabled(false);
        } );

        openFolder.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                openFolder.setCursor(new Cursor(Cursor.HAND_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                openFolder.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }

            @Override
            public void mousePressed(MouseEvent event) {
                BrowserLauncher.openFolder(Spark.getLogDirectory());
            }
        });

        frame.pack();
        frame.setSize(530, 500);

        GraphicUtils.centerWindowOnScreen(frame);
        frame.setVisible(true);
    }

    /**
     * Return true if the MainWindow is docked.
     */
    public boolean isDocked() {
        return pref.isDockingEnabled();
    }

    /**
     * Returns the inner split pane.
     */
    public JSplitPane getSplitPane() {
        // create the split pane only if required.
        if (splitPane == null) {
            splitPane = new JSplitPane();
        }
        return this.splitPane;
    }

    @Override
    public void bringFrameIntoFocus() {
        super.bringFrameIntoFocus();
        // When the isHideInTaskbar is enabled, we also need to bring the chat frame to the front.
        ChatFrame chatFrame = SparkManager.getChatManager().getChatContainer().getChatFrame();
        if (chatFrame != null) {
            chatFrame.toFront();
        }
    }
}
