/* 
 * AP(r) Computer Science GridWorld Case Study:
 * Copyright(c) 2002-2006 College Entrance Examination Board 
 * (http://www.collegeboard.com).
 * Modified by CBSkarmory 2017 (https://github.com/CBSkarmory)
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * @author Julie Zelenski
 * @author Chris Nevison
 * @author Cay Horstmann
 * @author CBSkarmory
 */

package info.gridworld.gui;

import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;
import info.gridworld.world.World;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
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.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;

import cbskarmory.Runner;

import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * The WorldFrame displays a World and allows manipulation of its occupants.
 * <br />
 * This code is not tested on the AP CS A and AB exams. It contains GUI
 * implementation details that are not intended to be understood by AP CS
 * students.
 */
public class WorldFrame<T> extends JFrame {
    public GUIController<T> control;
    private GridPanel display;
    private JTextArea messageArea;
    private ArrayList<JMenuItem> menuItemsDisabledDuringRun;
    private World<T> world;
    private ResourceBundle resources;
    private DisplayMap displayMap;

    private Set<Class> gridClasses;
    private JMenu newGridMenu;

    private static int count = 0;

    /**
     * Constructs a WorldFrame that displays the occupants of a world
     *
     * @param world the world to display
     */
    public WorldFrame(World<T> world) {
        this.world = world;
        count++;
        resources = ResourceBundle.getBundle(getClass().getName() + "Resources");

        try {
            System.setProperty("sun.awt.exception.handler", GUIExceptionHandler.class.getName());
        } catch (SecurityException ex) {
            // will fail in an applet
        }

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                count--;
                if (count == 0)
                    System.exit(0);
            }
        });

        displayMap = new DisplayMap();
        // String title =
        // System.getProperty("info.gridworld.gui.frametitle");XXX
        String title = "AdvanceWars GridWorld";
        if (title == null)
            title = resources.getString("frame.title");
        setTitle(title);
        setLocation(25, 15);

        // URL appIconUrl = getClass().getResource("GridWorld.gif");
        URL appIconUrl = Runner.class.getClassLoader().getResource("icon.png");
        ImageIcon appIcon = new ImageIcon(appIconUrl);
        setIconImage(appIcon.getImage());

        makeMenus();

        JPanel content = new JPanel();
        content.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
        content.setLayout(new BorderLayout());
        setContentPane(content);

        display = new GridPanel(displayMap, resources);

        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
            public boolean dispatchKeyEvent(KeyEvent event) {
                if (getFocusOwner() == null)
                    return false;
                String text = KeyStroke.getKeyStrokeForEvent(event).toString();
                final String PRESSED = "pressed ";
                int n = text.indexOf(PRESSED);
                if (n < 0)
                    return false;
                // filter out modifier keys; they are neither characters or
                // actions
                if (event.getKeyChar() == KeyEvent.CHAR_UNDEFINED && !event.isActionKey())
                    return false;
                text = text.substring(0, n) + text.substring(n + PRESSED.length());
                boolean consumed = getWorld().keyPressed(text, display.getCurrentLocation());
                if (consumed)
                    repaint();
                return consumed;
            }
        });

        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewport(new PseudoInfiniteViewport(scrollPane));
        scrollPane.setViewportView(display);
        content.add(scrollPane, BorderLayout.CENTER);

        gridClasses = new TreeSet<Class>(new Comparator<Class>() {
            public int compare(Class a, Class b) {
                return a.getName().compareTo(b.getName());
            }
        });
        for (String name : world.getGridClasses())
            try {
                gridClasses.add(Class.forName(name));
            } catch (Exception ex1) {
                ex1.printStackTrace();
            }

        Grid<T> gr = world.getGrid();
        gridClasses.add(gr.getClass());

        makeNewGridMenu();

        control = new GUIController<T>(this, display, displayMap, resources);
        content.add(control.controlPanel(), BorderLayout.SOUTH);

        messageArea = new JTextArea(3, 35);
        messageArea.setEditable(false);
        messageArea.setFocusable(false);
        messageArea.setBackground(new Color(0xFAFAD2));
        content.add(new JScrollPane(messageArea), BorderLayout.NORTH);

        pack();
        repaint(); // to show message
        display.setGrid(gr);
    }

    public void repaint() {
        String message = getWorld().getMessage();
        if (message == null)
            message = resources.getString("message.default");
        messageArea.setText(message);
        messageArea.repaint();
        display.repaint(); // for applet
        super.repaint();
    }

    /**
     * Gets the world that this frame displays
     *
     * @return the world
     */
    public World<T> getWorld() {
        return world;
    }

    /**
     * Sets a new grid for this world. Occupants are transferred from the old
     * world to the new.
     *
     * @param newGrid the new grid
     */
    public void setGrid(Grid<T> newGrid) {
        Grid<T> oldGrid = world.getGrid();
        Map<Location, T> occupants = new HashMap<Location, T>();
        for (Location loc : oldGrid.getOccupiedLocations())
            occupants.put(loc, world.remove(loc));

        world.setGrid(newGrid);
        for (Location loc : occupants.keySet()) {
            if (newGrid.isValid(loc))
                world.add(loc, occupants.get(loc));
        }

        display.setGrid(newGrid);
        repaint();
    }

    /**
     * Displays an error message
     *
     * @param t        the throwable that describes the error
     * @param resource the resource whose .text/.title strings should be used in the
     *                 dialog
     */
    public void showError(Throwable t, String resource) {
        String text;
        try {
            text = resources.getString(resource + ".text");
        } catch (MissingResourceException e) {
            text = resources.getString("error.text");
        }

        String title;
        try {
            title = resources.getString(resource + ".title");
        } catch (MissingResourceException e) {
            title = resources.getString("error.title");
        }

        String reason = resources.getString("error.reason");
        String message = text + "\n" + MessageFormat.format(reason, new Object[]{t});

        JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE);
    }

    // Creates the drop-down menus on the frame.

    private JMenu makeMenu(String resource) {
        JMenu menu = new JMenu();
        configureAbstractButton(menu, resource);
        return menu;
    }

    private JMenuItem makeMenuItem(String resource, ActionListener listener) {
        JMenuItem item = new JMenuItem();
        configureMenuItem(item, resource, listener);
        return item;
    }

    private void configureMenuItem(JMenuItem item, String resource, ActionListener listener) {
        configureAbstractButton(item, resource);
        item.addActionListener(listener);
        try {
            String accel = resources.getString(resource + ".accel");
            String metaPrefix = "@";
            if (accel.startsWith(metaPrefix)) {
                int menuMask = getToolkit().getMenuShortcutKeyMask();
                KeyStroke key = KeyStroke.getKeyStroke(
                        KeyStroke.getKeyStroke(accel.substring(metaPrefix.length())).getKeyCode(), menuMask);
                item.setAccelerator(key);
            } else {
                item.setAccelerator(KeyStroke.getKeyStroke(accel));
            }
        } catch (MissingResourceException ex) {
            // no accelerator
        }
    }

    private void configureAbstractButton(AbstractButton button, String resource) {
        String title = resources.getString(resource);
        int i = title.indexOf('&');
        int mnemonic = 0;
        if (i >= 0) {
            mnemonic = title.charAt(i + 1);
            title = title.substring(0, i) + title.substring(i + 1);
            button.setText(title);
            button.setMnemonic(Character.toUpperCase(mnemonic));
            button.setDisplayedMnemonicIndex(i);
        } else
            button.setText(title);
    }

    private void makeMenus() {
        JMenuBar mbar = new JMenuBar();
        JMenu menu;

        menuItemsDisabledDuringRun = new ArrayList<JMenuItem>();

        // mbar.add(menu = makeMenu("menu.file"));
        //
        // newGridMenu = makeMenu("menu.file.new");
        // menu.add(newGridMenu);
        // menuItemsDisabledDuringRun.add(newGridMenu);
        //
        // menu.add(makeMenuItem("menu.file.quit", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // System.exit(0);
        // }
        // }));
        //
        // mbar.add(menu = makeMenu("menu.view"));
        //
        // menu.add(makeMenuItem("menu.view.up", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.moveLocation(-1, 0);
        // }
        // }));
        // menu.add(makeMenuItem("menu.view.down", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.moveLocation(1, 0);
        // }
        // }));
        // menu.add(makeMenuItem("menu.view.left", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.moveLocation(0, -1);
        // }
        // }));
        // menu.add(makeMenuItem("menu.view.right", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.moveLocation(0, 1);
        // }
        // }));
        //
        // JMenuItem viewEditMenu;
        // menu.add(viewEditMenu = makeMenuItem("menu.view.edit",
        // new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // control.editLocation();
        // }
        // }));
        // menuItemsDisabledDuringRun.add(viewEditMenu);
        //
        // JMenuItem viewDeleteMenu;
        // menu.add(viewDeleteMenu = makeMenuItem("menu.view.delete",
        // new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // control.deleteLocation();
        // }
        // }));
        // menuItemsDisabledDuringRun.add(viewDeleteMenu);
        //
        // menu.add(makeMenuItem("menu.view.zoomin", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.zoomIn();
        // }
        // }));
        //
        // menu.add(makeMenuItem("menu.view.zoomout", new ActionListener()
        // {
        // public void actionPerformed(ActionEvent e)
        // {
        // display.zoomOut();
        // }
        // }));

        mbar.add(menu = makeMenu("menu.help"));
        JMenuItem dispDirections = new JMenuItem();
        ImageIcon dirIco = MenuMaker.get16xIcon(getClass().getClassLoader().getResource("32x/intel.png"));
        dispDirections.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showDirectionsPopup();
            }

        });
        dispDirections.setIcon(dirIco);
        dispDirections.setText("How to Play");
        menu.add(dispDirections);
        menu.addSeparator();
        menu.add(makeMenuItem("menu.help.about", new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showAboutPanel();
            }
        }));
        menu.add(makeMenuItem("menu.help.help", new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showHelp();
            }
        }));
        menu.add(makeMenuItem("menu.help.license", new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showLicense();
            }
        }));
        mbar.add(makeConfigMenu());
        setRunMenuItemsEnabled(true);
        setJMenuBar(mbar);
    }

    private JMenu makeConfigMenu() {
        JMenu ans = new JMenu("Configuration");
        JMenuItem fpsConfigItem = new JMenuItem("refresh rate settings");
        fpsConfigItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showFPSConfigPopup();
            }

        });
        ans.add(fpsConfigItem);
        ans.add(makeMenuItem("menu.view.zoomin", new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                display.zoomIn();
            }
        }));

        ans.add(makeMenuItem("menu.view.zoomout", new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                display.zoomOut();
            }
        }));
        return ans;
    }

    int newFPS = Runner.getFpsTarget();

    public void showFPSConfigPopup() {
        int sysFPS = Runner.externalFPS, currentFPS = Runner.getFpsTarget();
        JFrame fpsConfigFrame = new JFrame("Refresh Rate Config");
        JPanel fpsConfigPanel = new JPanel();
        JLabel oldFPSLabel = new JLabel("old settings: foreground: " + sysFPS + " hz | background: " + currentFPS
                + " hz (" + Runner.getMsDelay() + "ms delay)");
        JLabel newFPSLabel = new JLabel("new settings: background: ");

        // TODO
        fpsConfigFrame.setSize(400, 200);
        JComboBox<String> fpsComboBox = new JComboBox<>();
        fpsComboBox.addItem("[select target refresh rate]");
        fpsComboBox.addItem("3 hz (GridWorld default)");
        fpsComboBox.addItem("10 hz");
        fpsComboBox.addItem("24 hz (cinematic)");
        fpsComboBox.addItem("60 hz");
        fpsComboBox.addItem("120 hz");
        fpsComboBox.addItem("144 hz");
        fpsComboBox.addItem("custom fps target");
        fpsComboBox.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    switch ((String) e.getItem()) {
                        case "3 hz (GridWorld default)":
                            Runner.setFpsTarget(3);
                            break;
                        case "10 hz":
                            Runner.setFpsTarget(10);
                            break;
                        case "20 hz (recommended)":
                            Runner.setFpsTarget(20);
                            break;
                        case "60 hz":
                            Runner.setFpsTarget(60);
                            break;
                        case "120 hz":
                            Runner.setFpsTarget(120);
                            break;
                        case "144 hz":
                            Runner.setFpsTarget(144);
                            break;
                        default:
                            try {
                                Runner.setFpsTarget(
                                        Integer.parseInt(JOptionPane.showInputDialog("set custom target FPS:")));
                            } catch (Exception e2) {
                                JOptionPane.showMessageDialog(null,
                                        "Error: bad fps target\nfps target must be integer in range [1,1000]",
                                        "Bad FPS target", 0, null);
                            }
                    }
                }
                setTitle("AdvanceWars GridWorld (" + Runner.getFpsTarget() + "hz) : player "
                        + Runner.getTurnPlayer().id);
                fpsConfigFrame.dispose();
            }
        });

        fpsConfigPanel.add(oldFPSLabel);
        fpsConfigPanel.add(newFPSLabel);
        fpsConfigPanel.add(fpsComboBox);
        fpsConfigFrame.add(fpsConfigPanel);
        fpsConfigFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        fpsConfigFrame.setVisible(true);
    }

    public void showDirectionsPopup() {
        ImageIcon ico = MenuMaker.get16xIcon(getClass().getClassLoader().getResource("32x/intel.png"));
        JButton[] options = new JButton[2];
        JButton nahButton = GUIController.generateOkayButton(null, display);
        nahButton.setText("I already know how to play");
        nahButton.setFocusable(false);
        options[0] = nahButton;
        JButton openLink = new JButton(
                "<html> <a href=\"\">https://en.wikipedia.org/wiki/Advance_Wars#Gameplay</a></html>");
        openLink.setIcon(ico);
        openLink.setFocusable(false);
        openLink.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (Desktop.isDesktopSupported()) {
                    try {
                        Desktop.getDesktop().browse(new URI("https://en.wikipedia.org/wiki/Advance_Wars#Gameplay"));
                    } catch (Exception exept) {
                        /* TODO: error handling */
                    }

                } else {
					/* TODO: error handling */
                }

            }
        });
        options[1] = openLink;

        JOptionPane.showOptionDialog(this, "Please see the following page (click link below):\n" + ""
                + "\nI added quite a few more Units to the game." + "\nCommanding Officers are not yet implemented\n"
                + "\nIn short, click a unit you wish to move, where to move, and what to do"
                + "\n To win, capture the enemy HQ or destroy all enemy units!", "How to Play", 0, 0, ico, options, 0);

    }

    private void makeNewGridMenu() {
        // newGridMenu.removeAll();
        // MenuMaker<T> maker = new MenuMaker<T>(this, resources, displayMap);
        // maker.addConstructors(newGridMenu, gridClasses);
    }

    /**
     * Sets the enabled status of those menu items that are disabled when
     * running.
     *
     * @param enable true to enable the menus
     */
    public void setRunMenuItemsEnabled(boolean enable) {
        for (JMenuItem item : menuItemsDisabledDuringRun)
            item.setEnabled(enable);
    }

    /**
     * Brings up a simple dialog with some general information.
     */
    private void showAboutPanel() {
        String html = MessageFormat.format(resources.getString("dialog.about.text"),
                new Object[]{resources.getString("version.id")});
        String[] props = {"java.version", "java.vendor", "java.home", "os.name", "os.arch", "os.version", "user.name",
                "user.home", "user.dir"};
        html += "<table border='1'>";
        for (String prop : props) {
            try {
                String value = System.getProperty(prop);
                html += "<tr><td>" + prop + "</td><td>" + value + "</td></tr>";
            } catch (SecurityException ex) {
                // oh well...
            }
        }
        html += "</table>";
        html = "<html>" + html + "</html>";
        JOptionPane.showMessageDialog(this, new JLabel(html), resources.getString("dialog.about.title"),
                JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Brings up a window with a scrolling text pane that display the help
     * information.
     */
    private void showHelp() {
        JDialog dialog = new JDialog(this, resources.getString("dialog.help.title"));
        final JEditorPane helpText = new JEditorPane();
        try {
            URL url = getClass().getResource("GridWorldHelp.html");

            helpText.setPage(url);
        } catch (Exception eh) {
            helpText.setText(resources.getString("dialog.help.error"));
        }
        helpText.setEditable(false);
        helpText.addHyperlinkListener(new HyperlinkListener() {
            public void hyperlinkUpdate(HyperlinkEvent ev) {
                if (ev.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
                    try {
                        helpText.setPage(ev.getURL());
                    } catch (Exception ex) {
                    }
            }
        });
        JScrollPane sp = new JScrollPane(helpText);
        sp.setPreferredSize(new Dimension(650, 500));
        dialog.getContentPane().add(sp);
        dialog.setLocation(getX() + getWidth() - 200, getY() + 50);
        dialog.pack();
        dialog.setVisible(true);
    }

    /**
     * Brings up a dialog that displays the license.
     */
    private void showLicense() {
        JDialog dialog = new JDialog(this, resources.getString("dialog.license.title"));
        final JEditorPane text = new JEditorPane();
        try {
            URL url = getClass().getResource("GNULicense.txt");

            text.setPage(url);
        } catch (Exception el) {
            text.setText(resources.getString("dialog.license.error"));
        }
        text.setEditable(false);
        JScrollPane sp = new JScrollPane(text);
        sp.setPreferredSize(new Dimension(650, 500));
        dialog.getContentPane().add(sp);
        dialog.setLocation(getX() + getWidth() - 200, getY() + 50);
        dialog.pack();
        dialog.setVisible(true);
    }

    /**
     * Nested class that is registered as the handler for exceptions on the
     * Swing event thread. The handler will put up an alert panel and dump the
     * stack trace to the console.
     */
    public class GUIExceptionHandler {
        public void handle(Throwable e) {
            e.printStackTrace();

            JTextArea area = new JTextArea(10, 40);
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            area.setText(writer.toString());
            area.setCaretPosition(0);
            String copyOption = resources.getString("dialog.error.copy");
            JOptionPane pane = new JOptionPane(new JScrollPane(area), JOptionPane.ERROR_MESSAGE,
                    JOptionPane.YES_NO_OPTION, null, new String[]{copyOption, resources.getString("cancel")});
            pane.createDialog(WorldFrame.this, e.toString()).setVisible(true);
            if (copyOption.equals(pane.getValue())) {
                area.setSelectionStart(0);
                area.setSelectionEnd(area.getText().length());
                area.copy(); // copy to clipboard
            }
        }
    }
}