/* 
 * Copyright (c) 2015, Netsyms Technologies
 * All rights reserved.
 * 
 * 
 * CODE LICENSE ==========
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this 
 * list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 * 
 * 3. Neither the name of the copyright holder nor the names of its contributors 
 * may be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * 4. You adhere to the Media License detailed below.  If you do not, this license
 * is automatically revoked and you must purge all copies of the software you
 * possess, in source or binary form.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * 
 * MEDIA LICENSE ==========
 * All images and other graphical files ("media") included with this
 * software are copyright (c) 2015 Netsyms Technologies.  You may not distribute
 * the graphics or any program, source code repository, or other digital storage 
 * media containing them without permission from Netsyms Technologies.
 * This ban on distribution only applies to publicly available systems.
 * A password-protected network file share, USB drive, or other storage scheme that
 * cannot be easily accessed by the public is generally allowed.  If in doubt,
 * contact Netsyms Technologies.  If Netsyms Technologies allows or denies
 * you permission, that decision is considered final and binding.
 * 
 * You may only use the media for personal, 
 * non-commercial, non-educational use unless: 
 * 1, You have paid for the software and media via the SyMAT website,
 * or 2, you are using it as part of the 15-day trial period.  
 * Other uses are prohibited without permission.
 * If any part of this license is deemed unenforcable, the remainder 
 * of the license remains in full effect.
 */
package net.apocalypselabs.symat;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import static java.awt.Frame.MAXIMIZED_BOTH;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import net.apocalypselabs.symat.plugin.LoadPlugin;
import org.pushingpixels.flamingo.api.ribbon.*;
import org.pushingpixels.flamingo.api.ribbon.resize.*;
import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.icon.*;

/**
 * This class is like the Force: A light theme, a dark theme, and it binds the
 * app together. Also like duct tape, but that's not as cool.
 *
 * Contains a bunch of important variables and constants that are used all over.
 *
 * There is only one Main per app instance, the important things are static.
 *
 * @author Skylar Ittner
 */
public class Main extends JRibbonFrame {

    /**
     * Version name, as it should be displayed.
     */
    public static final String VERSION_NAME = "2.1";

    /**
     * The word "SyMAT".
     */
    public static final String SYMAT = "SyMAT";
    /**
     * Program name, with version name
     */
    public static final String APP_NAME = SYMAT + " " + VERSION_NAME;
    /**
     * Version number, for updates and //needs in scripts
     */
    public static final double APP_CODE = 24;
    /**
     * Base URL for building API calls
     */
    public static final String API_URL = "http://apis.symatapp.com/";
    /**
     * Contains the filename argument passed to SyMAT, if any.
     */
    public static String argfile = "";

    /**
     * Ubuntu font. Loaded from Ubuntu-R.ttf in the default package at runtime.
     * Becomes default sans-serif if something bad happens.
     */
    public static Font ubuntuRegular;

    /**
     *
     */
    public static boolean skipPython = false; // Skip python init on start?

    /**
     *
     */
    public static boolean skipEditor = false; // Skip editor init on start?

    private static boolean recentItemsMinimized = false;

    /**
     *
     */
    public static boolean updateAvailable = false; // Update available?

    /**
     *
     */
    public static String updateString = "";

    /**
     *
     */
    public static boolean licValid = false; // License valid?

    /**
     * Application icon, for setting frame icons. Has different sizes.
     */
    public static ArrayList<Image> symatlogo = new ArrayList<>();

    /**
     * The http server that handles opening other instances.
     */
    public static SingleInstanceServer sisrv;

    /**
     *
     */
    public static Main maingui;

    /**
     *
     */
    public JRibbonBand pluginband;

    /**
     * Creates the main app window and does some quick things that aren't
     * threaded in SplashScreen.
     */
    public Main() {
        maingui = this;
        // Set icon
        setIconImages(symatlogo);
        initComponents();
        loadRibbon();

        // Center screen
        setLocationRelativeTo(null);

        // Run things when app closed
        addWindowListener(new ExitControl());

        // Open initial windows
        boolean loaded = false;
        if (!argfile.equals("")) {
            if (argfile.endsWith(".sytt")) {
                Tasks tt = new Tasks(new File(argfile));
                loadFrame(tt);
                argfile = "";
            } else if (argfile.endsWith(".sypl")) {
                loadFrame(new InstallPlugin(new File(argfile)));
            } else {
                Editor ed = new Editor();
                loadFrame(ed);
                ed.openFileFromName(argfile);
                argfile = "";
            }
            loaded = true;
        }

        if (!licValid) {
            licenseRestrict(true);
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    loadFrame(new License());
                }
            });
            loaded = true;
        }
        // Only load shell if nothing else is going on

        if (argfile.equals("") && !loaded) {
            loadFrame(new Interpreter());
        }
        if (updateAvailable) {
            loadFrame(new Update(updateString));
        }
        loadRecentFiles();
        updateDisplay();
        setVisible(true);
        if (PrefStorage.getSetting("framemaxed", "no").equals("yes")) {
            java.awt.EventQueue.invokeLater(() -> {
                setExtendedState(MAXIMIZED_BOTH);
            });
        }

        if (!PrefStorage.getSetting("showrecent", "").equals("")) {
            recentItemsPanel.setVisible(false);
        }

        // Pi Day easter egg
        GregorianCalendar piday = new GregorianCalendar();
        if ((piday.get(Calendar.MONTH) == 2)
                && (piday.get(Calendar.DAY_OF_MONTH) == 14)) {
            JOptionPane.showInternalMessageDialog(mainPane,
                    "Happy Pi Day from the SyMAT team!",
                    "3/14",
                    JOptionPane.PLAIN_MESSAGE);
        }
    }

    /**
     * Load plugins from disk.
     */
    public void loadPlugins() {
        pluginband = new JRibbonBand("Plugins", null);
        installpluginbtn.setActionRichTooltip(new RichTooltip("Install Plugin",
                "Install a plugin from a file and view plugin info."));
        installpluginbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new InstallPlugin());
            }
        });
        pluginband.setResizePolicies((List) Arrays.asList(
                new CoreRibbonResizePolicies.Mirror(pluginband.getControlPanel())));
        pluginband.addCommandButton(installpluginbtn, RibbonElementPriority.TOP);
        String fsep = System.getProperty("file.separator");
        File dir = new File(System.getProperty("user.home") + fsep + ".symat" + fsep + "plugins");
        dir.mkdirs();
        File[] files = dir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".sypl");
            }
        });

        Map<RibbonElementPriority, Integer> galleryVisibleButtonCounts = new HashMap<>();
        galleryVisibleButtonCounts.put(RibbonElementPriority.LOW, 4);
        galleryVisibleButtonCounts.put(RibbonElementPriority.MEDIUM, 4);
        galleryVisibleButtonCounts.put(RibbonElementPriority.TOP, 4);
        List<StringValuePair<List<JCommandToggleButton>>> appGalleryButtons = new ArrayList<>();
        List<JCommandToggleButton> appGalleryButtonsList = new ArrayList<>();
        if (files != null) {
            for (File pl : files) {
                LoadPlugin lp;
                try {
                    lp = new LoadPlugin(pl);
                    appGalleryButtonsList.add(lp.getGalleryBtn());
                } catch (Exception ex) {
                    Debug.stacktrace(ex);
                    System.err.println("Error loading plugin " + pl.getName() + ": " + ex.getMessage());
                }
            }
        }

        appGalleryButtons.add(new StringValuePair<>("Plugins",
                appGalleryButtonsList));
        pluginband.addRibbonGallery("Plugins", appGalleryButtons,
                galleryVisibleButtonCounts, 4, 3,
                RibbonElementPriority.TOP);
    }

    /**
     * Reload the Ribbon tabs and all sub-components.
     */
    public void reloadRibbon() {
        getRibbon().removeAllTasks();
        loadRibbon();
    }

    /**
     * Load the ribbon in all its glory.
     */
    private void loadRibbon() {
        ResizableIcon appico = ImageWrapperResizableIcon.getIcon(
                Main.class.getResource("32x32.png"),
                new Dimension(32, 32));
        setApplicationIcon(appico);
        int tries = 0; // no infinite loops for us!
        while (tries < 20) {
            try {
                Thread.sleep(10);
                if (getApplicationIcon().equals(appico)) {
                    break;
                }
                setApplicationIcon(appico);
            } catch (Exception ex) {
            }
            tries++;
        }
        JRibbon ribbon = getRibbon();
        JRibbonBand codeband = new JRibbonBand("Code", null);
        JRibbonBand toolsband = new JRibbonBand("Tools", null);
        JRibbonBand webband = new JRibbonBand("Web", null);
        JRibbonBand collabband = new JRibbonBand("Team", null);

        try {
            loadPlugins();
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this,
                    "An error occurred while loading plugins: "
                    + ex.getMessage());
        }

        shellbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Interpreter());
            }
        });
        editorbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Editor());
            }
        });
        graphbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Graph());
            }
        });
        notepadbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Notepad());
            }
        });
        wikibtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new WebBrowser("SyMAT Wiki",
                        "http://wiki.symatapp.com",
                        WebBrowser.WIKI_LOGO));
            }
        });
        browserbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new WebBrowser());
            }
        });
        padsbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Pads());
            }
        });
        tasksbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                loadFrame(new Tasks());
            }
        });

        shellbtn.setActionRichTooltip(new RichTooltip("Command Shell",
                "Open a window for running interactive commands."));
        editorbtn.setActionRichTooltip(new RichTooltip("Code Editor",
                "Write and run JavaScript and Python scripts."));
        graphbtn.setActionRichTooltip(new RichTooltip("Graph",
                "Plot mathematical functions on a 2D graph."));
        notepadbtn.setActionRichTooltip(new RichTooltip("Notepad",
                "Write quick notes on a virtual napkin."));
        wikibtn.setActionRichTooltip(new RichTooltip("SyMAT Wiki",
                "View and edit online documentation and tutorials."));
        browserbtn.setActionRichTooltip(new RichTooltip("Web Browser",
                "Go online and browse the web."));
        padsbtn.setActionRichTooltip(new RichTooltip("Code Pads",
                "Collaborate over the Internet on projects."));
        tasksbtn.setActionRichTooltip(new RichTooltip("Task List",
                "Manage tasks and to-do lists for projects."));

        codeband.addCommandButton(shellbtn, RibbonElementPriority.TOP);
        codeband.addCommandButton(editorbtn, RibbonElementPriority.TOP);

        toolsband.addCommandButton(graphbtn, RibbonElementPriority.MEDIUM);
        toolsband.addCommandButton(notepadbtn, RibbonElementPriority.MEDIUM);
        toolsband.addCommandButton(tasksbtn, RibbonElementPriority.MEDIUM);

        webband.addCommandButton(padsbtn, RibbonElementPriority.TOP);
        webband.addCommandButton(browserbtn, RibbonElementPriority.MEDIUM);
        webband.addCommandButton(wikibtn, RibbonElementPriority.MEDIUM);

        codeband.setResizePolicies((List) Arrays.asList(new CoreRibbonResizePolicies.None(codeband.getControlPanel()),
                new IconRibbonBandResizePolicy(codeband.getControlPanel())));
        toolsband.setResizePolicies((List) Arrays.asList(new CoreRibbonResizePolicies.None(toolsband.getControlPanel()),
                new IconRibbonBandResizePolicy(toolsband.getControlPanel())));
        webband.setResizePolicies((List) Arrays.asList(
                new CoreRibbonResizePolicies.None(webband.getControlPanel()),
                new IconRibbonBandResizePolicy(webband.getControlPanel())));
        collabband.setResizePolicies((List) Arrays.asList(
                new CoreRibbonResizePolicies.None(collabband.getControlPanel()),
                new IconRibbonBandResizePolicy(collabband.getControlPanel())));
//        getpluginband.setResizePolicies((List) Arrays.asList(
//                new CoreRibbonResizePolicies.None(appsband.getControlPanel()),
//                new IconRibbonBandResizePolicy(pluginband.getControlPanel())));
//        pluginband.setResizePolicies((List) Arrays.asList(
//                new CoreRibbonResizePolicies.None(appsband.getControlPanel()),
//                new IconRibbonBandResizePolicy(pluginband.getControlPanel())));

        RibbonTask hometask = new RibbonTask("Apps", codeband, toolsband, webband);
        //RibbonTask webtask = new RibbonTask("Tools", webband, collabband);
        RibbonTask plugintask = new RibbonTask("Plugins", pluginband);

        loadRibbonMenu(null);

        ribbon.addTask(hometask);
        //ribbon.addTask(webtask);
        ribbon.addTask(plugintask);
    }

    /**
     *
     * @param name
     * @return
     */
    public static ResizableIcon getRibbonIcon(String name) {
        return ImageWrapperResizableIcon.getIcon(
                Main.class.getResource("images/" + name + ".png"),
                new Dimension(100, 76));
    }

    /**
     *
     * @param name
     * @return
     */
    public static ResizableIcon getTinyRibbonIcon(String name) {
        int d = 32;
        return ImageWrapperResizableIcon.getIcon(
                Main.class.getResource("icons/" + name + ".png"),
                new Dimension(d, d));
    }

    /**
     *
     * @param restricted
     */
    public static void licenseRestrict(boolean restricted) {
        graphbtn.setEnabled(!restricted);
        padsbtn.setEnabled(!restricted);
        recentFileList.setEnabled(!restricted);
    }

    /**
     * (Re)load display settings.
     */
    public static void updateDisplay() {
        maingui.getRibbon().setBackground(Theme.tabColor());
        recentFileList.setForeground(Theme.textColor());
        recentFileList.setBackground(Theme.boxColor());
        recentItemsPanel.setVisible(PrefStorage.getSetting("showrecent", "")
                .equals(""));
        maingui.getRibbon().setMinimized(PrefStorage.getSetting(
                "miniribbon", "").equals("yes"));
        mainPane.paintImmediately(0, 0,
                mainPane.getWidth(), mainPane.getHeight());
    }

    /**
     *
     * @param recent
     */
    public static void loadRibbonMenu(RibbonApplicationMenuEntrySecondary[] recent) {
        RibbonApplicationMenuEntryPrimary helpbtn
                = new RibbonApplicationMenuEntryPrimary(
                        getRibbonIcon("help"),
                        "Manual",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Help());
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntryPrimary cascadebtn
                = new RibbonApplicationMenuEntryPrimary(
                        getRibbonIcon("cascade"),
                        "Arrange all",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                cascade();
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntryPrimary exitbtn
                = new RibbonApplicationMenuEntryPrimary(
                        getRibbonIcon("closeall"),
                        "Exit",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                maingui.dispatchEvent(new WindowEvent(maingui,
                                                WindowEvent.WINDOW_CLOSING));
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntrySecondary newjsbtn
                = new RibbonApplicationMenuEntrySecondary(
                        getTinyRibbonIcon("jsicon"),
                        "JavaScript",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Editor(Editor.JAVASCRIPT));
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntrySecondary newpybtn
                = new RibbonApplicationMenuEntrySecondary(
                        getTinyRibbonIcon("pyicon"),
                        "Python",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Editor(Editor.PYTHON));
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntrySecondary newjavabtn
                = new RibbonApplicationMenuEntrySecondary(
                        getTinyRibbonIcon("jaicon"),
                        "Java",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Editor(Editor.JAVA));
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntrySecondary newtaskbtn
                = new RibbonApplicationMenuEntrySecondary(
                        getTinyRibbonIcon("taskicon"),
                        "Task List",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Tasks());
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        RibbonApplicationMenuEntryPrimary newbtn
                = new RibbonApplicationMenuEntryPrimary(
                        getRibbonIcon("newfile"),
                        "New",
                        null,
                        JCommandButton.CommandButtonKind.POPUP_ONLY);
        openbtn = new RibbonApplicationMenuEntryPrimary(
                getRibbonIcon("openfile"),
                "Open",
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent evt) {
                        JFileChooser fc = new JFileChooser();
                        FileFilter script
                        = new FileNameExtensionFilter("Script"
                                + "(syjs, sypy, js, py)",
                                "syjs", "sypy", "js", "py");
                        FileFilter all
                        = new FileNameExtensionFilter("SyMAT File"
                                + "(syjs, sypy, js, py, sytt)",
                                "syjs", "sypy", "js", "py", "sytt");
                        FileFilter tasklist
                        = new FileNameExtensionFilter("Task List (sytt)",
                                "sytt");
                        fc.setFileFilter(all);
                        fc.addChoosableFileFilter(script);
                        fc.addChoosableFileFilter(tasklist);
                        int result = fc.showOpenDialog(maingui);
                        if (result == JFileChooser.APPROVE_OPTION) {
                            File f = fc.getSelectedFile();
                            if (f.getName().endsWith(".sytt")) {
                                loadFrame(new Tasks(f));
                            } else {
                                Editor ed = new Editor();
                                ed.openFileFromName(f.getAbsolutePath());
                                loadFrame(ed);
                            }
                        }
                    }
                },
                JCommandButton.CommandButtonKind.ACTION_AND_POPUP_MAIN_ACTION);

        if (recent == null) {
            RibbonApplicationMenuEntrySecondary blanksubbtn
                    = new RibbonApplicationMenuEntrySecondary(
                            null, "No recent files", null,
                            JCommandButton.CommandButtonKind.ACTION_ONLY);
            blanksubbtn.setEnabled(false);
            openbtn.addSecondaryMenuGroup("Recent Files", blanksubbtn);
        } else {
            openbtn.addSecondaryMenuGroup("Recent Files", recent);
        }
        newbtn.addSecondaryMenuGroup("Code File", newjsbtn, newpybtn, newjavabtn);
        newbtn.addSecondaryMenuGroup("Other", newtaskbtn);

        RibbonApplicationMenuEntryFooter displaybtn
                = new RibbonApplicationMenuEntryFooter(
                        getTinyRibbonIcon("settings"),
                        "Settings",
                        new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                loadFrame(new Settings());
                            }
                        });

        RibbonApplicationMenuEntryPrimary blankbtn
                = new RibbonApplicationMenuEntryPrimary(
                        null, "", null,
                        JCommandButton.CommandButtonKind.ACTION_ONLY);
        blankbtn.setEnabled(false);

        RibbonApplicationMenu menu = new RibbonApplicationMenu();
        menu.addMenuEntry(newbtn);
        menu.addMenuEntry(openbtn);
        menu.addMenuSeparator();
        menu.addMenuEntry(helpbtn);
        menu.addMenuEntry(cascadebtn);
        menu.addMenuSeparator();
        menu.addMenuEntry(exitbtn);
        menu.addFooterEntry(displaybtn);

        menu.addMenuEntry(blankbtn);
        menu.addMenuEntry(blankbtn);
        menu.addMenuEntry(blankbtn);
        menu.addMenuEntry(blankbtn);

        maingui.getRibbon().setApplicationMenu(menu);
        maingui.getRibbon().setApplicationMenuRichTooltip(
                new RichTooltip("SyMAT Menu",
                        "Create files, open documents, "
                        + "get help, and change settings")
        );
    }

    /**
     * (Re)load recent file lists from storage.
     */
    public static void loadRecentFiles() {
        String files = PrefStorage.getSetting("recentfiles");
        if (files.equals("")) {
            loadRibbonMenu(null);
            recentFileList.setListData(new String[0]);
            return;
        }
        String[] fileList = files.split("\n");
        int neededLength = 0;
        for (String file : fileList) {
            if ((new File(file)).isFile()) {
                neededLength++;
            }
        }
        if (neededLength > 10) {
            neededLength = 10;
        }
        KeyValListItem[] items = new KeyValListItem[neededLength];
        int i = 0;
        RibbonApplicationMenuEntrySecondary[] recentmenu
                = new RibbonApplicationMenuEntrySecondary[fileList.length];
        for (String f : fileList) {
            File file = new File(f);
            if (file.isFile() && i < neededLength) {
                items[i] = new KeyValListItem(file.getName(), file.getPath());
                recentmenu[i] = (new RibbonApplicationMenuEntrySecondary(
                        null,
                        file.getName(),
                        new ActionListener() {
                            final String path = file.getPath();

                            @Override
                            public void actionPerformed(ActionEvent evt) {
                                Editor edit = new Editor();
                                edit.openFileFromName(path);
                                loadFrame(edit);
                            }
                        },
                        JCommandButton.CommandButtonKind.ACTION_ONLY));
                i++;
            }
        }

        loadRibbonMenu(recentmenu);
        recentFileList.setListData(items);

        // Re-save list to remove bad entries
        String list = "";
        for (KeyValListItem item : items) {
            list += item.getValue() + "\n";
        }
        PrefStorage.saveSetting("recentfiles", list);
    }

    /**
     * Add a file to the recent files lists.
     *
     * @param file The file path.
     */
    public static void addRecentFile(String file) {
        file = (new File(file)).getAbsolutePath();
        String files = PrefStorage.getSetting("recentfiles");
        String[] fileList = files.split("\n");
        for (int i = 0; i < fileList.length; i++) {
            if (fileList[i].trim().equals(file)) {
                fileList[i] = "";
            }
        }
        files = file + "\n";
        for (String f : fileList) {
            if (!f.trim().equals("")) {
                files += f + "\n";
            }
        }
        PrefStorage.saveSetting("recentfiles", files);
        PrefStorage.save();
        loadRecentFiles();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jMenuItem1 = new javax.swing.JMenuItem();
        mainPane = mainPane = new javax.swing.JDesktopPane() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.setColor(Theme.windowColor());
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        }
        ;
        jLabel2 = new javax.swing.JLabel();
        recentItemsPanel = new javax.swing.JPanel();
        jScrollPane1 = new javax.swing.JScrollPane();
        recentFileList = new javax.swing.JList();
        recentFileBtn = new javax.swing.JButton();
        recentItemsTitle = new javax.swing.JLabel();
        appPanel = new javax.swing.JPanel();

        jMenuItem1.setText("jMenuItem1");

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("SyMAT");
        setMinimumSize(new java.awt.Dimension(640, 540));
        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentShown(java.awt.event.ComponentEvent evt) {
                formComponentShown(evt);
            }
        });

        mainPane.setBackground(new java.awt.Color(204, 204, 204));
        mainPane.setAutoscrolls(true);
        mainPane.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
        mainPane.setOpaque(false);

        jLabel2.setFont(Main.ubuntuRegular.deriveFont(48.0F));
        jLabel2.setForeground(new java.awt.Color(153, 153, 153));
        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
        jLabel2.setText("SyMAT");
        jLabel2.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM);

        recentItemsPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        recentItemsPanel.setForeground(new java.awt.Color(153, 153, 153));
        recentItemsPanel.setMaximumSize(new java.awt.Dimension(160, 273));
        recentItemsPanel.setOpaque(false);

        jScrollPane1.setMinimumSize(new java.awt.Dimension(23, 206));
        jScrollPane1.setName(""); // NOI18N

        recentFileList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        recentFileList.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                recentFileListMouseClicked(evt);
            }
        });
        recentFileList.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseMoved(java.awt.event.MouseEvent evt) {
                recentFileListMouseMoved(evt);
            }
        });
        jScrollPane1.setViewportView(recentFileList);

        recentFileBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/openfile.png"))); // NOI18N
        recentFileBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                recentFileBtnActionPerformed(evt);
            }
        });

        recentItemsTitle.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
        recentItemsTitle.setForeground(new java.awt.Color(102, 102, 102));
        recentItemsTitle.setText("  Recent Files");
        recentItemsTitle.setOpaque(true);
        recentItemsTitle.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                recentItemsTitleMouseClicked(evt);
            }
        });

        javax.swing.GroupLayout recentItemsPanelLayout = new javax.swing.GroupLayout(recentItemsPanel);
        recentItemsPanel.setLayout(recentItemsPanelLayout);
        recentItemsPanelLayout.setHorizontalGroup(
            recentItemsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(recentItemsPanelLayout.createSequentialGroup()
                .addGroup(recentItemsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(recentItemsPanelLayout.createSequentialGroup()
                        .addGap(97, 97, 97)
                        .addComponent(recentFileBtn))
                    .addGroup(recentItemsPanelLayout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 136, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addComponent(recentItemsTitle, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        recentItemsPanelLayout.setVerticalGroup(
            recentItemsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(recentItemsPanelLayout.createSequentialGroup()
                .addComponent(recentItemsTitle)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 357, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(recentFileBtn)
                .addContainerGap())
        );

        appPanel.setOpaque(false);
        appPanel.setLayout(new java.awt.GridBagLayout());

        javax.swing.GroupLayout mainPaneLayout = new javax.swing.GroupLayout(mainPane);
        mainPane.setLayout(mainPaneLayout);
        mainPaneLayout.setHorizontalGroup(
            mainPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPaneLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(appPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(mainPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                    .addComponent(recentItemsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        mainPaneLayout.setVerticalGroup(
            mainPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(mainPaneLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(mainPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(appPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(mainPaneLayout.createSequentialGroup()
                        .addComponent(recentItemsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 84, Short.MAX_VALUE)
                        .addComponent(jLabel2)))
                .addContainerGap())
        );
        mainPane.setLayer(jLabel2, javax.swing.JLayeredPane.DEFAULT_LAYER);
        mainPane.setLayer(recentItemsPanel, javax.swing.JLayeredPane.DEFAULT_LAYER);
        mainPane.setLayer(appPanel, javax.swing.JLayeredPane.DEFAULT_LAYER);

        getContentPane().add(mainPane, java.awt.BorderLayout.CENTER);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void formComponentShown(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentShown
        setLocationRelativeTo(null);
    }//GEN-LAST:event_formComponentShown

    private void recentFileBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recentFileBtnActionPerformed
        if (recentFileList.getSelectedValue() == null) {
            return;
        }
        KeyValListItem file = (KeyValListItem) recentFileList.getSelectedValue();
        if (file.isEmpty()) {
            return;
        }
        Editor edit = new Editor();
        Debug.println(file.getValue());
        edit.openFileFromName(file.getValue());
        loadFrame(edit);
    }//GEN-LAST:event_recentFileBtnActionPerformed

    private void recentFileListMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_recentFileListMouseClicked
        if (evt.getClickCount() == 2) {
            recentFileBtnActionPerformed(null);
        }
    }//GEN-LAST:event_recentFileListMouseClicked

    private void recentFileListMouseMoved(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_recentFileListMouseMoved
        try {
            ListModel m = recentFileList.getModel();
            int index = recentFileList.locationToIndex(evt.getPoint());
            if (index > -1) {
                recentFileList.setToolTipText(
                        ((KeyValListItem) m.getElementAt(index)).getValue());
            }
        } catch (Exception ex) {
            // This feature is optional.  Just skip it if it's broken.
            recentFileList.setToolTipText("");
        }
    }//GEN-LAST:event_recentFileListMouseMoved

    private void recentItemsTitleMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_recentItemsTitleMouseClicked

    }//GEN-LAST:event_recentItemsTitleMouseClicked

    /**
     * Adds the given JInternalFrame to the mainPane. Automatically does layout
     * and sets visible (if show==true).
     *
     * @param frame The frame
     * @param show Should the frame be visible?
     */
    public static void loadFrame(JInternalFrame frame, boolean show) {
        int w = frame.getWidth();
        int h = frame.getHeight();
        int pw = mainPane.getWidth();
        int ph = mainPane.getHeight();
        if (frame.isResizable()) {
            if (h > ph) {
                h = ph;
            }
            if (w > pw) {
                w = pw;
            }
            frame.setSize(w, h);
        }
        mainPane.add(frame);
        frame.setLocation(
                (pw / 2) - (w / 2),
                (ph / 2) - (h / 2));
        if (frame.getLocation().y < 0) {
            frame.setLocation(frame.getLocation().x, 0);
        }
        if (show) {
            frame.setVisible(true);
        }
    }

    /**
     * Adds the given JInternalFrame to the mainPane. Automatically does layout
     * and sets visible.
     *
     * @param frame The frame
     */
    public static void loadFrame(JInternalFrame frame) {
        loadFrame(frame, true);
    }

    /**
     * Cascade all the frames in a stack. Somehow reverses the order each time
     * around, I have no idea why but it's a "feature" now!
     */
    public static void cascade() {
        JInternalFrame[] frames = mainPane.getAllFrames();
        int x = 12;
        int y = 24;
        Debug.println("Cascading " + frames.length + " frames...");
        for (int i = 0; i < frames.length; i++) {
            if (frames[i].isVisible()) {
                Debug.println("Frame: "
                        + frames[i].getTitle()
                        + ", Times: " + i
                        + ", Xpos: " + x * i
                        + ", Ypos: " + y * i);
                frames[i].setBounds(x * i,
                        y * i,
                        frames[i].getWidth(),
                        frames[i].getHeight());
                frames[i].toFront();
            }
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */

        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException e) {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, e);
        }
        //</editor-fold>
        //</editor-fold>
        //</editor-fold>
        //</editor-fold>

        try {
            // Ubuntu font for prettifying
            ubuntuRegular = Font.createFont(Font.TRUETYPE_FONT, Main.class.
                    getResourceAsStream("/Ubuntu-R.ttf"));
        } catch (FontFormatException | IOException ex) {
            ubuntuRegular = Font.getFont(Font.SANS_SERIF);
            System.err.println("Error loading fonts: " + ex.getMessage());
        } catch (Exception ex) {
            ubuntuRegular = Font.getFont(Font.SANS_SERIF);
            System.err.println("Error loading fonts: " + ex.getMessage());
        }

        // Set icon
        String[] sizes = {"16", "24", "32", "48", "64", "96", "128", "256", "512"};
        for (String s : sizes) {
            symatlogo.add(new ImageIcon(
                    Main.class.getResource(s + "x" + s + ".png")).getImage());
        }

        // Command line args
        for (String arg : args) {
            switch (arg) {
                case "skippython":
                    skipPython = true;
                    break;
                case "skipeditor":
                    skipEditor = true;
                    break;
                case "quickstart":
                    skipPython = true;
                    skipEditor = true;
                    break;
                case "licensereset":
                    PrefStorage.saveSetting("license", "");
                    break;
                default:
                    argfile = arg;
                    break;
            }
        }

        SingleInstanceClient sicli = new SingleInstanceClient(argfile);

        try {
            new SingleInstanceServer().start();
        } catch (IOException ex) {
            Debug.printerr("Cannot start instance listener:\n\n");
            Debug.stacktrace(ex);
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JFXPanel(); // this will prepare JavaFX toolkit and environment
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        Platform.setImplicitExit(false);
                    }
                });
            }
        });


        /* Create and display the form */
        java.awt.EventQueue.invokeLater(() -> {
            new SplashScreen().setVisible(true);
        });
    }

    /**
     *
     */
    public static JCommandButton shellbtn
            = new JCommandButton("Shell", getRibbonIcon("shell"));

    /**
     *
     */
    public static JCommandButton editorbtn
            = new JCommandButton("Editor", getRibbonIcon("editor"));

    /**
     *
     */
    public static JCommandButton graphbtn
            = new JCommandButton("Graph", getRibbonIcon("graph"));

    /**
     *
     */
    public static JCommandButton notepadbtn
            = new JCommandButton("Notepad", getRibbonIcon("notepad"));

    /**
     *
     */
    public static JCommandButton wikibtn
            = new JCommandButton("Wiki", getRibbonIcon("wiki"));

    /**
     *
     */
    public static JCommandButton browserbtn
            = new JCommandButton("Browser", getRibbonIcon("browser"));

    /**
     *
     */
    public static JCommandButton padsbtn
            = new JCommandButton("Pads", getRibbonIcon("pads"));

    /**
     *
     */
    public static JCommandButton tasksbtn
            = new JCommandButton("Tasks", getRibbonIcon("tasks"));

    /**
     *
     */
    public static JCommandButton installpluginbtn
            = new JCommandButton("Install", getRibbonIcon("installplugin"));

    /**
     *
     */
    public static RibbonApplicationMenuEntryPrimary openbtn;

    // Variables declaration - do not modify//GEN-BEGIN:variables
    public static javax.swing.JPanel appPanel;
    public static javax.swing.JLabel jLabel2;
    public static javax.swing.JMenuItem jMenuItem1;
    public static javax.swing.JScrollPane jScrollPane1;
    public static javax.swing.JDesktopPane mainPane;
    public static javax.swing.JButton recentFileBtn;
    public static javax.swing.JList recentFileList;
    public static javax.swing.JPanel recentItemsPanel;
    public static javax.swing.JLabel recentItemsTitle;
    // End of variables declaration//GEN-END:variables
}
