package com.srbenoit.font;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.swing.ButtonGroup;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import com.srbenoit.log.LoggedObject;
import com.srbenoit.util.ResourceLoader;

/**
 * An application to render all glyphs for a font, and a specified point size. The glyph's numeric
 * value is shown below each glyph.
 */
public class GlyphViewer extends LoggedObject implements ActionListener, ViewerInt {

    /** the menu items for the font sizes */
    private static final String[] SIZES = {
            "10 point", "12 point", "14 point", "16 point", "18 point", "20 point", "24 point",
            "30 point", "48 point"
        };

    /** the viewer frame */
    private final transient JFrame frame;

    /** the map of font names to menu names */
    private final transient Properties nameMap;

    /** the font manager from which to gather fonts */
    private final transient BundledFontManager mgr;

    /** the font size menu items */
    private transient JMenuItem[] actions = null;

    /** a scroll pane in which the grid of glyphs is rendered */
    private transient JScrollPane scroll = null;

    /** the point size to use when rendering fonts */
    private transient int size = 0;

    /** the name of the font whose glyphs are being viewed */
    private transient String name = null;

    /** the glyph panel used to render the fonts */
    private transient GlyphPanel panel = null;

    /** the named submenus created so far */
    private final transient Map<String, JMenu> submenus;

    /**
     * Constructs a new <code>GlyphViewer</code>.
     *
     * @param  manager  the font manager from which to retrieve fonts
     */
    public GlyphViewer(final BundledFontManager manager) {

        this.frame = new JFrame("Glyph Viewer");
        this.mgr = manager;
        this.submenus = new HashMap<String, JMenu>(20);
        this.nameMap = new Properties();

        loadNameMap();
        buildUI();
    }

    /**
     * Gets the frame.
     *
     * @return  the frame
     */
    public JFrame getFrame() {

        return this.frame;
    }

    /**
     * Loads the map from font names to menu names.
     */
    private void loadNameMap() {

        InputStream input;

        input = ResourceLoader.getInputStream(GlyphViewer.class, "beken/font/fontnames.properties");

        if (input != null) {

            try {
                this.nameMap.load(input);
                input.close();
            } catch (Exception e) {
                LOG.warning("Failed to load beken/font/fontnames.properties");
                this.nameMap.clear();
            }
        }
    }

    /**
     * Constructs the user interface, which consists of a menu dropdown of the available fonts, a
     * menu dropdown of point sizes, and a panel that shows the selected font's glyphs in the
     * selected size, with the character codes below each glyph.
     */
    private void buildUI() {

        JMenuBar bar;
        JMenu menu;
        String[] names;
        int inx;
        ButtonGroup grp;
        JMenuItem[] fontSizes;
        Dimension screen;

        bar = new JMenuBar();

        menu = new JMenu("Fonts");
        names = this.mgr.fontNames();

        for (inx = 0; inx < names.length; inx++) {
            addFontToMenu(menu, names[inx]);
        }

        bar.add(menu);

        fontSizes = new JMenuItem[SIZES.length];
        menu = new JMenu("Sizes");
        grp = new ButtonGroup();

        for (inx = 0; inx < SIZES.length; inx++) {
            fontSizes[inx] = new JRadioButtonMenuItem(SIZES[inx], false); // NOPMD SRB
            fontSizes[inx].addActionListener(this);
            menu.add(fontSizes[inx]);
            grp.add(fontSizes[inx]);
        }

        fontSizes[3].setSelected(true);
        this.size = 16;
        bar.add(menu);

        this.actions = new JMenuItem[2];
        menu = new JMenu("Actions");
        this.actions[0] = new JMenuItem("Export As Image...");
        this.actions[0].setActionCommand("Export");
        this.actions[0].addActionListener(this);
        this.actions[0].setEnabled(false);
        menu.add(this.actions[0]);
        this.actions[1] = new JMenuItem("Toggle bounds boxes");
        this.actions[1].setActionCommand("ToggleBounds");
        this.actions[1].addActionListener(this);
        menu.add(this.actions[1]);
        bar.add(menu);

        this.frame.setJMenuBar(bar);

        this.panel = new GlyphPanel(this);
        this.scroll = new JScrollPane(this.panel);
        this.scroll.getVerticalScrollBar().setUnitIncrement(36);
        this.scroll.setWheelScrollingEnabled(true);
        this.frame.setContentPane(this.scroll);

        // Center on screen.
        this.frame.pack();
        screen = Toolkit.getDefaultToolkit().getScreenSize();
        this.frame.setLocation((screen.width - this.frame.getWidth()) / 2,
            (screen.height - this.frame.getHeight()) / 2);
        this.frame.setVisible(true);
    }

    /**
     * Create the menu item for a single font.
     *
     * @param  menu      the menu to which to add the font item
     * @param  fontName  the name of the font to add
     */
    private void addFontToMenu(final JMenu menu, final String fontName) {

        JMenuItem menuItem;
        String menuName;
        JMenu submenu;
        Enumeration<?> names;
        String key;

        menuItem = new JRadioButtonMenuItem(fontName, false);
        menuItem.addActionListener(this);

        submenu = menu;
        menuName = fontName;

        names = this.nameMap.propertyNames();

        while (names.hasMoreElements()) {
            key = (String) names.nextElement();

            if (fontName.startsWith(key)) {
                menuName = this.nameMap.getProperty(key);

                // See if we already have this menu
                submenu = this.submenus.get(menuName);

                break;
            }
        }

        if (submenu == null) {
            submenu = new JMenu(menuName);
            this.submenus.put(menuName, submenu);
            menu.add(submenu);
        }

        submenu.add(menuItem);
    }

    /**
     * Handler for menu item selections.
     *
     * @param  evt  the action event
     */
    public void actionPerformed(final ActionEvent evt) {

        String cmd;
        String num;
        int pickedSize;
        JFileChooser chooser;
        File file;

        cmd = evt.getActionCommand();

        if (cmd.endsWith(" point")) {
            num = cmd.substring(0, cmd.length() - 6);
            pickedSize = Integer.parseInt(num);

            if (this.size != pickedSize) {
                this.size = pickedSize;

                if (this.name != null) {
                    rebuildFont();
                }
            }
        } else if ("Export".equals(cmd)) {
            chooser = new JFileChooser();
            chooser.setMultiSelectionEnabled(false);
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

            file = new File(chooser.getCurrentDirectory(), this.name + ".png");
            chooser.setSelectedFile(file);

            if (chooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) {
                this.panel.export(chooser.getSelectedFile());
            }
        } else if ("ToggleBounds".equals(cmd)) {
            this.panel.setDrawBoundsBoxes(!this.panel.isDrawingBoundsBoxes());
        } else {
            this.name = cmd;

            if (this.size != 0) {
                rebuildFont();
            }
        }
    }

    /**
     * Regenerates the font and glyphs display.
     */
    private void rebuildFont() {

        Font font;

        font = this.mgr.getFont(this.name, this.size, Font.PLAIN);
        this.panel.setTheFont(font);

        // Enable export as image
        this.actions[0].setEnabled(true);
    }

    /**
     * Tells the scroll pane that something inside it has changed.
     *
     * @param  jump  the vertical size of boxes
     */
    public void updateScroller(final int jump) {

        this.scroll.getVerticalScrollBar().setUnitIncrement(jump);
        this.scroll.revalidate();

        this.frame.repaint();
    }

    /**
     * Main method to launch the application.
     *
     * @param  args  command-line arguments
     */
    public static void main(final String... args) {

        GlyphViewer viewer;

        viewer = new GlyphViewer(BundledFontManager.getInstance());
        viewer.getFrame().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}
