package gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.*;
import java.io.InputStream;
import java.net.URL;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.plaf.synth.SynthLookAndFeel;
import javax.swing.text.Document;
import functional.*;

/**
 * Simplification of the general uses of certain swing objects. Using these
 * functions, GUIs can be built recursively, which is convenient because it fits
 * well with the underlying tree nature of the architecture.
 * 
 * Part of the idea of this class is that most functions have a return value so
 * that even when constructs are created recursively, they can also be accessed
 * through their return value.
 * 
 * @author Peter Goodman
 */
abstract public class SimpleGUI {
    
    /**
     * Create and return a JFrame object. Requires a Delegate to
     * initialize the frame with any content before being shown.
     * 
     * @param title The title of the window.
     * @param init Delegate to initialize the frame.
     * @return JFrame
     */
    public static JFrame frame(String title) {
        return frame(title, null);
    }
    public static JFrame frame(String title, D<JFrame> init) {
        
        JFrame frame = new JFrame(title);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // initialize and display the frame
        if(null != init)
            init.call(frame);
        
        frame.pack();
        frame.setVisible(true);

        return frame;
    }
    
    /**
     * Create and return a JPanel object. JPanel's are lightweight.
     */
    public static JPanel panel() {
        return panel(null);
    }
    public static JPanel panel(D<JPanel> init) {
        JPanel panel = new JPanel();
        
        if(null != init)
            init.call(panel);
        
        return panel;
    }
    
    /**
     * Create a simple button.
     */
    public static JButton button() {
        return button_hook(new JButton(), null);
    }
    public static JButton button(D<JButton> on_click) {
        return button_hook(new JButton(), on_click);
    }
    public static JButton button(String name) {
        return button(name, null);
    }
    public static JButton button(String name, D<JButton> on_click) {
        return button_hook(new JButton(name), on_click);
    }
    protected static <B extends AbstractButton> B button_hook(final B btn, final D<B> on_click) {
        btn.setEnabled(on_click != null);
        
        // add an on-click event listener if one was passed
        if (btn.isEnabled()) {
            btn.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    on_click.call(btn);
                }
            });
        }
        
        return btn;
    }
    
    /**
     * Generic method to add a set of components to another.
     * @return
     */
    protected static <C extends JComponent, I extends JComponent> C add_items_to_component(C component, I items[]) {
        for(I item : items) {
            if(null != item)
                component.add(item);
        }
        return component;
    }

    /**
     * Create a menu bar. Accepts a variable number of menus.
     * 
     * @param itms
     * @return JMenuBar
     */
    public static JMenuBar menu_bar(final JFrame frame, JMenu ... menus) {
        JMenuBar bar = add_items_to_component(new JMenuBar(), menus);
        frame.setJMenuBar(bar);
        return bar;
    }

    /**
     * Create a menu. Accepts a variable number of menu items.
     * 
     * @return JMenu
     */
    public static JMenu menu(String name, JMenuItem ... items) {
        return add_items_to_component(new JMenu(name), items);
    }
    
    /**
     * Create a menu that doesn't have any items and instead is a button itself.
     */
    public static JMenu menu(String name, final D<JMenu> on_click) {
        final JMenu menu = new JMenu(name);
        menu.addMenuListener(new MenuListener() {
            public void menuCanceled(MenuEvent a) {
                //menu.setArmed(false);
            }
            public void menuDeselected(MenuEvent a) {
                //menu.setArmed(false);
                //
            }
            public void menuSelected(MenuEvent a) {
                //menu.doClick();
                //menu.setFocusable(false);
                on_click.call(menu);
                menu.transferFocus();
            }
        });
        return menu;
    }

    /**
     * Create a menu item.
     * 
     * @param args
     */
    public static JMenuItem menu_item(final String text) {
        return menu_item(text, null);
    }

    public static JMenuItem menu_item(String text, final D<JMenuItem> on_click) {
        return button_hook(new JMenuItem(text), on_click);
    }
    
    /**
     * Create a simple label.
     * 
     * @param args
     */
    public static JLabel label(final String label_name) {
        return label(label_name, JLabel.LEFT);
    }
    public static JLabel label(final String label_name, int text_align) {
        return new JLabel(label_name, text_align);
    }
    
    /**
     * Get the content pane for a container. This is one example of where the 
     * Swing libraries really fail; the developers should have isolated the 
     * getContentPane method in an interface so that it's not guess work.
     */
    protected static Container get_content_pane(Container c) {
        if(c instanceof JFrame)
            return((JFrame) c).getContentPane();
        else if(c instanceof JDialog)
            return ((JDialog) c).getContentPane();
        
        return c;
    }
    
    /**
     * Add something to the content pane of a component.
     */
    public static <T extends JComponent> T content_add(Container parent, T child) {
        get_content_pane(parent).add(child);
        return child;
    }
    
    /**
     * Remove something from the content pane of a component.
     */
    public static <T extends JComponent> void content_remove(Container parent, T child) {
        get_content_pane(parent).remove(child);
    }
    public static void content_remove(Container parent) {
        get_content_pane(parent).removeAll();
    }
    
    /**
     * Create a grid bag layout.
     */
    public static JPanel grid(GridCell ... cells) {
        return grid(new JPanel(), cells);
    }
    public static <T extends Container> T grid(T pane, GridCell ... cells) {
        Container c = get_content_pane(pane);
        
        c.setLayout(new GridBagLayout());
        
        for(GridCell cell : cells) {
            if(null != cell)
                c.add(cell.first(), cell.second());
        }
        
        return pane;
    }
    public static JPanel grid(GridCell[] cells_first, GridCell ... cells_rest) {
        JPanel pane = grid(cells_first);
        
        for(GridCell cell : cells_rest) {
            if(null != cell)
                pane.add(cell.first(), cell.second());
        }
        
        return pane;
    }
    
    /**
     * Create and return a simple grid cell. A Simple grid cell knows how much it spans
     * to the left and right.
     */
    public static GridCell grid_cell(Component item) {
        return grid_cell(1, item, 1);
    }
    
    public static GridCell grid_cell(int width, Component item) {
        return grid_cell(width, item, 1);
    }
    
    public static GridCell grid_cell(Component item, int height) {
        return grid_cell(1, item, height);
    }
    
    public static GridCell grid_cell(int width, Component item, int height) {
        GridBagConstraints c = new GridBagConstraints();
        c.gridwidth = width;
        c.gridheight = height;
        return new GridCell(item, c);
    }
    
    /**
     * Create an icon. By default, the icon location will be relative to the
     * SimpleGUI class; however, a Class object can be passed in to make it
     * relative to whatever class one chooses.
     */
    public static JLabel icon(String loc) {
        return icon(loc, SimpleGUI.class);
    }
    public static <T> JLabel icon(String loc, Class<T> c) {
        URL icon_url = c.getResource(loc);
        if (null != icon_url)
            return new JLabel(new ImageIcon(icon_url));
        
        System.err.println("Couldn't find icon: " + loc);
        return new JLabel();
    }
    
    /**
     * Create an alert dialog box.
     */
    public static void alert(JFrame frame, String message) {
        JOptionPane.showMessageDialog(frame, message);
    }
    
    /**
     * Create a prompt.
     */
    public static String question(Component parent, String question) {
        try {
            return JOptionPane.showInputDialog(
                parent,
                question,
                "",
                JOptionPane.QUESTION_MESSAGE
            );
        } catch(HeadlessException e) {
            return "";
        }
    }
    
    /**
     * Create a custom dialog box. The dialog takes a function of one argument
     * that can further initialize the dialog box and also must return what is
     * to be the dialog's content pane.
     */
    public static JDialog dialog(JFrame frame, String title, F<JDialog,Container> content_init) {
        final JDialog dialog = new JDialog(frame, title);
        
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setContentPane(content_init.call(dialog));
        dialog.pack();
        dialog.setVisible(true);

        return dialog;
    }
    public static JDialog modal(JFrame frame, String title, F<JDialog,Container> content_init) {
        JDialog d = dialog(frame, title, content_init);
        d.setModal(true);
        return d;
    }
    
    /**
     * Create a text box.
     */
    public static JTextField text_field() {
        return new JTextField();
    }
    public static JTextField text_field(int columns) {
        return new JTextField(columns);
    }
    public static JTextField text_field(int columns, Document doc) {
        return new JTextField(doc, "", columns);
    }
    
    /**
     * Set the look and feel.
     */
    public static void laf_theme(JFrame frame, String class_loc) {
        try {
            UIManager.setLookAndFeel(class_loc);
            SwingUtilities.updateComponentTreeUI(frame);
            frame.pack();
        } catch(Exception e) {
            alert(frame, 
                "An error occured while trying to switch the look and feel."
            );
        }
    }
    
    /**
     * Load up a Synth skin given an XML file location. By default the file 
     * location will be relative to the SimpleGUI class; however, one can pass a 
     * Class instance to change what class the location is relative to.
     */
    public static void laf_skin(JFrame frame, String xml_loc) {
        laf_skin(frame, xml_loc, SimpleGUI.class);
    }
    public static <T> void laf_skin(JFrame frame, String xml_loc, Class<T> c) {
        try {
            SynthLookAndFeel synth = new SynthLookAndFeel();
            InputStream stream = c.getResourceAsStream(xml_loc);
            synth.load(stream, c);
            UIManager.setLookAndFeel(synth);
            SwingUtilities.updateComponentTreeUI(frame);
            frame.pack();
        } catch(Exception e) {
            alert(frame, 
                "An error occured while trying to switch the look and feel."
            );
        }
    }
    
    /**
     * Create the GUI in a thread.
     * 
     * @param args
     */
    public static <T> void gui_run(final T gui, final D<T> fn) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fn.call(gui);
            }
        });
    }
}