package com.glitchcog.fontificator.gui.controls;

import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.KeyStroke;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

import org.apache.log4j.Logger;

import com.glitchcog.fontificator.bot.ChatViewerBot;
import com.glitchcog.fontificator.bot.MessageType;
import com.glitchcog.fontificator.bot.TwitchPrivmsg;
import com.glitchcog.fontificator.config.FontificatorProperties;
import com.glitchcog.fontificator.config.loadreport.LoadConfigErrorType;
import com.glitchcog.fontificator.config.loadreport.LoadConfigReport;
import com.glitchcog.fontificator.gui.AssetIndexLoader;
import com.glitchcog.fontificator.gui.chat.ChatPanel;
import com.glitchcog.fontificator.gui.chat.ChatWindow;
import com.glitchcog.fontificator.gui.component.MenuComponent;
import com.glitchcog.fontificator.gui.controls.messages.MessageDialog;
import com.glitchcog.fontificator.gui.controls.panel.ControlPanelDebug;
import com.glitchcog.fontificator.gui.controls.panel.ControlPanelFont;
import com.glitchcog.fontificator.gui.controls.panel.ControlTabs;
import com.glitchcog.fontificator.gui.controls.panel.LogBox;

/**
 * This is the window for containing all the options of the chat display window
 * 
 * @author Matt Yanos
 */
public class ControlWindow extends JDialog
{
    private static final Logger logger = Logger.getLogger(ControlWindow.class);

    private static final long serialVersionUID = 1L;

    private final String DEFAULT_CONFIG_FILE_EXTENSION = "cgf";

    private final String DEFAULT_SCREENSHOT_FILE_EXTENSION = "png";

    private ControlTabs controlTabs;

    private ChatViewerBot bot;

    private ChatWindow chatWindow;

    private MessageDialog messageDialog;

    private FontificatorProperties fProps;

    private JEditorPane aboutPane;

    // @formatter:off
    private static String ABOUT_CONTENTS = "<html><table bgcolor=#EEEEEE width=100% border=1><tr><td>" + 
        "<center><font face=\"Arial, Helvetica\"><b>Chat Game Fontificator</b> is a Twitch chat display that makes<br />" + 
        "the chat look like the text boxes from various video games.<br /><br />" + 
        "It is free, open source, and in the public domain to the furthest<br />" + 
        "extent I am permitted to forfeit my copyright over this software.<br /><br />" + 
        "Please enjoy!<br /><br />" + 
        "By Matt Yanos<br /><br />" + 
        "<a href=\"www.github.com/GlitchCog/ChatGameFontificator\">www.github.com/GlitchCog/ChatGameFontificator</a>" + 
        "</font></center>" + 
        "</td></tr></table></html>";
    // @formatter:on

    private String helpText;

    public static ControlWindow me;

    private JDialog help;

    private JFileChooser opener;

    private JFileChooser configSaver;

    private JFileChooser screenshotSaver;

    private ScreenshotOptions screenshotOptions;

    public ControlWindow(JFrame parent, FontificatorProperties fProps, LogBox logBox)
    {
        super(parent);

        BufferedReader br = null;
        try
        {

            InputStream is = getClass().getClassLoader().getResourceAsStream("help.html");
            br = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuilder helpBuilder = new StringBuilder();
            while ((line = br.readLine()) != null)
            {
                helpBuilder.append(line);
            }
            helpText = helpBuilder.toString();
        }
        catch (Exception e)
        {
            helpText = "Unable to load help file";
            logger.error(helpText, e);
            ChatWindow.popup.handleProblem(helpText);
        }
        finally
        {
            if (br != null)
            {
                try
                {
                    br.close();
                }
                catch (Exception e)
                {
                    logger.error(e.toString(), e);
                }
            }
        }

        ControlWindow.me = this;

        setTitle("Fontificator Configuration");
        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);

        this.fProps = fProps;

        FileFilter cgfFileFilter = new FileNameExtensionFilter("Chat Game Fontificator Configuration (*." + DEFAULT_CONFIG_FILE_EXTENSION + ")", DEFAULT_CONFIG_FILE_EXTENSION.toLowerCase());
        FileFilter pngFileFilter = new FileNameExtensionFilter("PNG Image (*." + DEFAULT_SCREENSHOT_FILE_EXTENSION + ")", DEFAULT_SCREENSHOT_FILE_EXTENSION.toLowerCase());

        this.opener = new JFileChooser();
        this.opener.setFileFilter(cgfFileFilter);

        this.configSaver = new JFileChooser();
        this.configSaver.setFileFilter(cgfFileFilter);

        this.screenshotOptions = new ScreenshotOptions();

        this.screenshotSaver = new JFileChooser();
        this.screenshotSaver.setFileFilter(pngFileFilter);
        this.screenshotSaver.setAccessory(screenshotOptions);
    }

    public void loadLastData(ChatWindow chatWindow)
    {
        this.chatWindow = chatWindow;

        ChatWindow.setupHideOnEscape(this);

        LoadConfigReport report = new LoadConfigReport();

        fProps.clear();
        try
        {
            report = fProps.loadLast();
        }
        catch (Exception e)
        {
            final String errorMsg = "Unknown error loading last config file";
            logger.error(errorMsg, e);
            report.addError(errorMsg, LoadConfigErrorType.UNKNOWN_ERROR);
        }

        if (!report.isErrorFree())
        {
            final boolean overwriteExistingValues = report.isProblem();
            if (overwriteExistingValues)
            {
                fProps.forgetLastConfigFile();
            }
            fProps.loadDefaultValues(overwriteExistingValues);
        }
    }

    /**
     * Called separately from construction.
     * 
     * @param logBox
     */
    public void build(LogBox logBox)
    {
        constructAboutPopup();

        this.messageDialog = new MessageDialog(fProps, chatWindow, this, logBox);

        this.bot = new ChatViewerBot();
        this.bot.setUsername(fProps.getIrcConfig().getUsername());

        this.controlTabs = new ControlTabs(fProps, bot, messageDialog.getCensorPanel(), logBox);
        this.controlTabs.build(chatWindow, this);

        this.bot.setChatPanel(chatWindow.getChatPanel());

        setLayout(new GridLayout(1, 1));
        add(controlTabs);

        initMenus();

        // This wasn't built before the config was loaded into the chat control
        // tab, so set it here
        setAlwaysOnTopMenu(fProps.getChatConfig().isAlwaysOnTop());
        setRememberPositionMenu(fProps.getChatConfig().isRememberPosition());
        setAntiAliasMenu(fProps.getChatConfig().isAntiAlias());

        setupHelp();

        pack();
        setMinimumSize(getSize());
        setResizable(false);
    }

    private void setupHelp()
    {
        final String helpTitle = "Chat Game Fontificator Help";

        help = new JDialog(this, true);
        help.setTitle(helpTitle);
        help.setSize(640, 480);
        help.setLayout(new GridBagLayout());

        JEditorPane helpPane = new JEditorPane();
        helpPane.setContentType("text/html");
        helpPane.setText(helpText);
        helpPane.setEditable(false);
        JScrollPane scrollHelp = new JScrollPane(helpPane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        JButton ok = new JButton("Close");
        ok.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                help.setVisible(false);
            }
        });

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridy = 0;
        gbc.weightx = 1.0;
        gbc.weighty = 0.0;
        gbc.fill = GridBagConstraints.NONE;
        gbc.anchor = GridBagConstraints.NORTH;
        help.add(new JLabel("The function of each option available in the Control Window tabs is explained below"), gbc);

        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.CENTER;
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        gbc.gridy = 1;
        help.add(scrollHelp, gbc);

        gbc.gridy++;
        gbc.fill = GridBagConstraints.NONE;
        gbc.anchor = GridBagConstraints.SOUTH;
        gbc.weighty = 0.0;
        help.add(ok, gbc);

        help.setResizable(false);
    }

    /**
     * Builds the menus from the static arrays
     */
    private void initMenus()
    {
        JMenuBar menuBar = new JMenuBar();

        final String[] mainMenuText = { "File", "Presets", "View", "Message", "Help" };
        final int[] mainMnomonics = { KeyEvent.VK_F, KeyEvent.VK_P, KeyEvent.VK_V, KeyEvent.VK_M, KeyEvent.VK_H };

        JMenu[] menus = new JMenu[mainMenuText.length];

        for (int i = 0; i < mainMenuText.length; i++)
        {
            menus[i] = new JMenu(mainMenuText[i]);
            menus[i].setMnemonic(mainMnomonics[i]);
        }

        /* File Menu Item Text */
        final String strFileOpen = "Open Configuration";
        final String strFileSave = "Save Configuration";
        final String strFileRestore = "Restore Default Configuration";
        final String strScreenshot = "Screenshot";
        final String strFileExit = "Exit";
        // @formatter:off
        final MenuComponent[] fileComponents = new MenuComponent[] { 
            new MenuComponent(strFileOpen, KeyEvent.VK_O, KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK)), 
            new MenuComponent(strFileSave, KeyEvent.VK_S, KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK)), 
            new MenuComponent(strFileRestore, KeyEvent.VK_R, null), 
            new MenuComponent(strScreenshot, KeyEvent.VK_C, KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0)), 
            new MenuComponent(strFileExit, KeyEvent.VK_X, KeyStroke.getKeyStroke(KeyEvent.VK_Q, Event.CTRL_MASK)) 
        };
        // @formatter:on

        /* View Menu Item Text */
        final String strAntiAlias = "Anti-Aliased";
        final String strViewTop = "Always On Top";
        final String strRememberPos = "Remember Chat Window Position";
        final String strViewHide = "Hide Control Window";
        final MenuComponent[] viewComponents = new MenuComponent[] { new MenuComponent(strAntiAlias, KeyEvent.VK_A, null, true), null, new MenuComponent(strViewTop, KeyEvent.VK_T, null, true), new MenuComponent(strRememberPos, KeyEvent.VK_P, null, true), new MenuComponent(strViewHide, KeyEvent.VK_H, KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.CTRL_MASK)) };

        /* Message Menu Item Text */
        final String strMsgMsg = "Message Management";
        final MenuComponent[] messageComponents = new MenuComponent[] { new MenuComponent(strMsgMsg, KeyEvent.VK_M, KeyStroke.getKeyStroke(KeyEvent.VK_M, Event.CTRL_MASK)) };

        /* Help Menu Item Text */
        final String strHelpHelp = "Help";
        final String strHelpDebug = "Debug Mode";
        final String strHelpAbout = "About";
        final MenuComponent[] helpComponents = new MenuComponent[] { new MenuComponent(strHelpHelp, KeyEvent.VK_R, null), new MenuComponent(strHelpDebug, KeyEvent.VK_D, null, true), null, new MenuComponent(strHelpAbout, KeyEvent.VK_A, null) };

        /* All menu components, with a placeholder for the Presets menu */
        final MenuComponent[][] allMenuComponents = new MenuComponent[][] { fileComponents, new MenuComponent[] {}, viewComponents, messageComponents, helpComponents };

        ActionListener mal = new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                JMenuItem mi = (JMenuItem) e.getSource();
                if (strFileOpen.equals(mi.getText()))
                {
                    open();
                }
                else if (strFileSave.equals(mi.getText()))
                {
                    saveConfig();
                }
                else if (strFileRestore.equals(mi.getText()))
                {
                    restoreDefaults(true);
                    controlTabs.refreshUiFromConfig(fProps);
                }
                else if (strScreenshot.equals(mi.getText()))
                {
                    saveScreenshot();
                }
                else if (strFileExit.equals(mi.getText()))
                {
                    attemptToExit();
                }
                else if (strAntiAlias.equals(mi.getText()))
                {
                    JCheckBoxMenuItem checkBox = (JCheckBoxMenuItem) e.getSource();
                    controlTabs.setAntiAlias(checkBox.isSelected());
                    chatWindow.getChatPanel().repaint();
                }
                else if (strViewTop.equals(mi.getText()))
                {
                    JCheckBoxMenuItem checkBox = (JCheckBoxMenuItem) e.getSource();
                    ((JFrame) getParent()).setAlwaysOnTop(checkBox.isSelected());
                    controlTabs.setAlwaysOnTopConfig(checkBox.isSelected());
                }
                else if (strRememberPos.equals(mi.getText()))
                {
                    JCheckBoxMenuItem checkBox = (JCheckBoxMenuItem) e.getSource();
                    controlTabs.setRememberChatWindowPosition(checkBox.isSelected());
                    if (checkBox.isSelected())
                    {
                        final int sx = (int) chatWindow.getLocationOnScreen().getX();
                        final int sy = (int) chatWindow.getLocationOnScreen().getY();
                        fProps.getChatConfig().setChatWindowPositionX(sx);
                        fProps.getChatConfig().setChatWindowPositionY(sy);
                    }
                }
                else if (strViewHide.equals(mi.getText()))
                {
                    setVisible(false);
                }
                else if (strMsgMsg.equals(mi.getText()))
                {
                    messageDialog.showDialog();
                }
                else if (strHelpHelp.equals(mi.getText()))
                {
                    help.setVisible(true);
                }
                else if (strHelpDebug.equals(mi.getText()))
                {
                    toggleDebugTab();
                }
                else if (strHelpAbout.equals(mi.getText()))
                {
                    showAboutPane();
                }
            }
        };

        /* Set all menu items but presets */
        JMenuItem item = null;
        for (int i = 0; i < allMenuComponents.length; i++)
        {
            for (int j = 0; j < allMenuComponents[i].length; j++)
            {
                MenuComponent mc = allMenuComponents[i][j];
                if (mc == null)
                {
                    menus[i].add(new JSeparator());
                }
                else
                {
                    item = mc.checkbox ? new JCheckBoxMenuItem(mc.label) : new JMenuItem(mc.label);
                    item.addActionListener(mal);
                    item.setMnemonic(mc.mnemonic);
                    if (mc.accelerator != null)
                    {
                        item.setAccelerator(mc.accelerator);
                    }
                    menus[i].add(item);
                }
            }
            menuBar.add(menus[i]);
        }

        Map<String, List<String[]>> presetMapSubmenuToItem = AssetIndexLoader.loadPresets();

        List<String[]> allPresets = new ArrayList<String[]>();
        for (String key : presetMapSubmenuToItem.keySet())
        {
            for (String[] value : presetMapSubmenuToItem.get(key))
            {
                allPresets.add(value);
            }
        }

        ActionListener presetListener = new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                String sourceText = ((JMenuItem) e.getSource()).getText();
                for (String[] presets : allPresets)
                {
                    if (presets[0].equals(sourceText))
                    {
                        loadPreset(presets[0], presets[1]);
                        break;
                    }
                }
            }
        };

        for (String submenuKey : presetMapSubmenuToItem.keySet())
        {
            List<String[]> submenuItems = presetMapSubmenuToItem.get(submenuKey);
            if (submenuKey != null)
            {
                JMenu submenu = new JMenu(submenuKey);
                for (String[] itemAndFilename : submenuItems)
                {
                    JMenuItem submenuItem = new JMenuItem(itemAndFilename[0]);
                    submenuItem.addActionListener(presetListener);
                    submenu.add(submenuItem);
                }
                menus[1].add(submenu);
            }
            else
            {
                for (String[] submenuRootItemAndFilename : submenuItems)
                {
                    JMenuItem submenuRootItem = new JMenuItem(submenuRootItemAndFilename[0]);
                    submenuRootItem.addActionListener(presetListener);
                    menus[1].add(submenuRootItem);
                }
            }
        }

        for (int i = 0; i < menus.length; i++)
        {
            menuBar.add(menus[i]);
        }

        setJMenuBar(menuBar); // add the whole menu bar
    }

    /**
     * Restore default values to the configuration
     * 
     * @param overrideExistingValues
     */
    private void restoreDefaults(boolean overrideExistingValues)
    {
        boolean okayToProceed = fProps.checkForUnsavedProps(this, this);

        if (okayToProceed)
        {
            int result = JOptionPane.showConfirmDialog(this, "Reset to default configuration?", "Confirm", JOptionPane.YES_NO_OPTION);
            if (result == JOptionPane.YES_OPTION)
            {
                fProps.loadDefaultValues(overrideExistingValues);
                controlTabs.refreshUiFromConfig(fProps);
                chatWindow.getChatPanel().repaint();
            }
        }
    }

    private void open()
    {
        boolean okayToProceed = fProps.checkForUnsavedProps(this, this);

        if (okayToProceed)
        {
            int result = opener.showOpenDialog(me);
            if (result == JFileChooser.APPROVE_OPTION)
            {
                try
                {
                    LoadConfigReport report = fProps.loadFile(opener.getSelectedFile());
                    if (report.isProblem())
                    {
                        throw new Exception("Configuration file open error");
                    }
                    controlTabs.refreshUiFromConfig(fProps);
                    chatWindow.getChatPanel().repaint();
                }
                catch (Exception ex)
                {
                    final String errorMsg = "Unable to open file " + (opener.getSelectedFile() == null ? "null" : opener.getSelectedFile().getName());
                    logger.error(errorMsg, ex);
                    ChatWindow.popup.handleProblem(errorMsg);
                }
            }
        }
    }

    private void loadPreset(String presetName, String presetFilename)
    {
        boolean okayToProceed = fProps.checkForUnsavedProps(this, this);
        if (okayToProceed)
        {
            try
            {
                LoadConfigReport report = fProps.loadFile(presetFilename);
                if (report.isProblem())
                {
                    logger.error("Unsuccessful call to FontificatorProperties.loadFile(String)");
                    throw new Exception();
                }
            }
            catch (Exception ex)
            {
                logger.error(ex.toString(), ex);
                ChatWindow.popup.handleProblem("Unable to load preset " + presetName + " (" + presetFilename + ")");
            }
            controlTabs.refreshUiFromConfig(fProps);
            chatWindow.getChatPanel().repaint();
        }
    }

    /**
     * Gets a file to save to from the given file chooser, including options to overwrite
     * 
     * @param chooser
     *            the chooser to use to get the file
     * @param extension
     *            the extension of the type of file being saved, or null if there is no default extension
     * @return file or null if selection is canceled
     */
    public static File getTargetSaveFile(JFileChooser chooser, String extension)
    {
        int overwrite = JOptionPane.YES_OPTION;
        // Do while overwrite is no, so it loops back around to try again if someone says they don't want to
        // overwrite an existing file, but if they select cancel it just breaks out of the loop
        do
        {
            int result = chooser.showSaveDialog(me);

            // Default to yes, so it writes even if there's no existing file
            overwrite = JOptionPane.YES_OPTION;

            if (result == JFileChooser.APPROVE_OPTION)
            {
                File saveFile = chooser.getSelectedFile();

                if (chooser.getFileFilter() instanceof FileNameExtensionFilter)
                {
                    String[] exts = ((FileNameExtensionFilter) (chooser.getFileFilter())).getExtensions();
                    boolean endsInExt = false;
                    for (String ext : exts)
                    {
                        if (saveFile.getName().toLowerCase().endsWith(ext.toLowerCase()))
                        {
                            endsInExt = true;
                            break;
                        }
                    }
                    if (extension != null && !endsInExt)
                    {
                        saveFile = new File(saveFile.getPath() + "." + extension);
                    }
                }

                if (saveFile.exists())
                {
                    overwrite = JOptionPane.showConfirmDialog(me, "File " + saveFile.getName() + " already exists. Overwrite?", "Overwrite?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
                }

                if (overwrite == JOptionPane.YES_OPTION)
                {
                    return saveFile;
                }
            }
        } while (overwrite == JOptionPane.NO_OPTION);
        return null;
    }

    /**
     * Save configuration to a file
     * 
     * @return whether the file was saved
     */
    public boolean saveConfig()
    {
        final boolean configReadyToSave = controlTabs.refreshConfigFromUi();
        File saveFile = null;
        if (configReadyToSave)
        {
            saveFile = getTargetSaveFile(configSaver, DEFAULT_CONFIG_FILE_EXTENSION);
        }
        if (saveFile != null)
        {
            try
            {
                fProps.saveFile(saveFile);
                return true;
            }
            catch (Exception ex)
            {
                logger.error("Configuration file save error", ex);
                return false;
            }
        }
        return false;
    }

    /**
     * Takes and saves a screenshot of the current chat window
     * 
     * @return whether the screenshot was saved
     */
    private boolean saveScreenshot()
    {
        // Take the screenshot before the save file chooser is shown
        ChatPanel chat = chatWindow.getChatPanel();
        BufferedImage chatImage = new BufferedImage(chat.getWidth(), chat.getHeight(), screenshotOptions.isTransparencyEnabled() ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
        Graphics chatGraphics = chatImage.getGraphics();
        chat.paint(chatGraphics);

        final boolean chromaEnabled = Boolean.toString(true).equalsIgnoreCase(fProps.getProperty(FontificatorProperties.KEY_CHAT_CHROMA_ENABLED));
        if (screenshotOptions.isTransparencyEnabled() && chromaEnabled)
        {
            final int chromaKey = new Color(Integer.parseInt(fProps.getProperty(FontificatorProperties.KEY_COLOR_CHROMA_KEY), 16)).getRGB();
            final int transparentPixel = new Color(0, true).getRGB();

            for (int r = 0; r < chatImage.getHeight(); r++)
            {
                for (int c = 0; c < chatImage.getWidth(); c++)
                {
                    if (chatImage.getRGB(c, r) == chromaKey)
                    {
                        chatImage.setRGB(c, r, transparentPixel);
                    }
                }
            }
        }

        File saveFile = getTargetSaveFile(screenshotSaver, DEFAULT_SCREENSHOT_FILE_EXTENSION);
        if (saveFile != null)
        {
            try
            {
                if (screenshotOptions.isMetadataEnabled())
                {
                    ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
                    ImageOutputStream stream = ImageIO.createImageOutputStream(saveFile);
                    writer.setOutput(stream);

                    IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(chatImage), writer.getDefaultWriteParam());

                    IIOMetadataNode title = generateMetadataNode("Title", "CGF Screenshot");
                    IIOMetadataNode software = generateMetadataNode("Software", "Chat Game Fontificator");
                    final String fontGameName = ControlPanelFont.getFontGameName(fProps.getProperty(FontificatorProperties.KEY_FONT_FILE_FONT));
                    final String borderGameName = ControlPanelFont.getBorderGameName(fProps.getProperty(FontificatorProperties.KEY_FONT_FILE_BORDER));
                    IIOMetadataNode description = generateMetadataNode("Description", fontGameName + " Font / " + borderGameName + " Border");

                    IIOMetadataNode text = new IIOMetadataNode("tEXt");
                    text.appendChild(title);
                    text.appendChild(software);
                    text.appendChild(description);

                    final String metadataFormatStr = "javax_imageio_png_1.0";
                    IIOMetadataNode root = new IIOMetadataNode(metadataFormatStr);
                    root.appendChild(text);
                    metadata.mergeTree(metadataFormatStr, root);

                    writer.write(metadata, new IIOImage(chatImage, null, metadata), writer.getDefaultWriteParam());

                    stream.close();
                }
                else
                {
                    ImageIO.write(chatImage, "png", saveFile);
                }

                return true;
            }
            catch (Exception e)
            {
                logger.error("Unable to save screenshot", e);
                return false;
            }
        }
        return false;
    }

    private IIOMetadataNode generateMetadataNode(String key, String value)
    {
        IIOMetadataNode node = new IIOMetadataNode("tEXtEntry");
        node.setAttribute("keyword", key);
        node.setAttribute("value", value);
        return node;
    }

    public void setAlwaysOnTopMenu(boolean alwaysOnTop)
    {
        ((JCheckBoxMenuItem) (getJMenuBar().getMenu(2).getItem(2))).setSelected(alwaysOnTop);
    }

    public void setRememberPositionMenu(boolean rememberPosition)
    {
        ((JCheckBoxMenuItem) (getJMenuBar().getMenu(2).getItem(3))).setSelected(rememberPosition);
    }

    public void setAntiAliasMenu(boolean antiAlias)
    {
        ((JCheckBoxMenuItem) (getJMenuBar().getMenu(2).getItem(0))).setSelected(antiAlias);
    }

    public void clearUsernameCases()
    {
        bot.clearUsernameCases();
    }

    public void addManualMessage(String username, String message)
    {
        bot.sendMessageToChat(MessageType.MANUAL, message, new TwitchPrivmsg(username));
    }

    public void disconnect()
    {
        bot.disconnect();
    }

    public void attemptToExit()
    {
        attemptToExit(this);
    }

    /**
     * Any program exit should call this method to do so
     */
    public void attemptToExit(Component parent)
    {
        boolean okayToProceed = fProps.checkForUnsavedProps(this, parent);
        if (okayToProceed)
        {
            disconnect();
            System.exit(0);
        }
    }

    /**
     * Construct the popup dialog containing the About message
     */
    private void constructAboutPopup()
    {
        aboutPane = new JEditorPane("text/html", ABOUT_CONTENTS);
        aboutPane.addHyperlinkListener(new HyperlinkListener()
        {
            public void hyperlinkUpdate(HyperlinkEvent e)
            {
                if (EventType.ACTIVATED.equals(e.getEventType()))
                {
                    if (Desktop.isDesktopSupported())
                    {
                        try
                        {
                            Desktop.getDesktop().browse(URI.create("https://" + e.getDescription()));
                        }
                        catch (IOException e1)
                        {
                            e1.printStackTrace();
                        }
                    }
                }
            }
        });
        aboutPane.setEditable(false);
    }

    private void toggleDebugTab()
    {
        controlTabs.toggleDebugTab();
    }

    private void showAboutPane()
    {
        JOptionPane.showMessageDialog(this, aboutPane, "About", JOptionPane.PLAIN_MESSAGE);
    }

    public MessageDialog getMessageDialog()
    {
        return messageDialog;
    }

    public void loadAfterInit()
    {
        controlTabs.setChatWindowPosition();
    }

    public ControlPanelDebug getDebugPanel()
    {
        return controlTabs.getDebugTab();
    }

}