/*
 * SimplyHTML, a word processor based on Java, HTML and CSS
 * Copyright (C) 2002 Ulrich Hilger
 *
 * This program 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; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.lightdev.app.shtm;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.CSS;

/**
 * A panel for showing and manipulating font information.
 *
 * <p><code>FontPanel</code> shows and manipulates CSS attributes. To
 * set it to HTML attributes, methods setAttributes and getAttributes
 * have to be overridden.</p>
 *
 * @author Ulrich Hilger
 * @author Light Development
 * @author <a href="http://www.lightdev.com">http://www.lightdev.com</a>
 * @author <a href="mailto:info@lightdev.com">info@lightdev.com</a>
 * @author published under the terms and conditions of the
 *      GNU General Public License,
 *      for details see file gpl.txt in the distribution
 *      package of this software
 *
 *
 */
class FontPanel extends JPanel implements TitledPickList.TitledPickListListener {
    /** a text field to show a sample of the selected font attributes */
    final JTextField sample = new JTextField();
    /** table for automatic font component value read/write */
    private final Vector<AttributeComponent> fontComponents = new Vector(0);

    public FontPanel() {
        setLayout(new BorderLayout(5, 5));
        /* create a label for previewing font selections */
        sample.setText("");
        sample.setEditable(false);
        sample.setPreferredSize(new Dimension(200, 50));
        sample.setHorizontalAlignment(SwingConstants.CENTER);
        sample.setText(Util.getResourceString("previewText"));
        final JPanel previewPanel = new JPanel(new BorderLayout());
        previewPanel.add(sample, BorderLayout.CENTER);
        previewPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), Util
            .getResourceString("previewLabel")));
        /*
         * create a pick list for family filled with
         * available font family names
         */
        final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        final FamilyPickList family = new FamilyPickList(ge.getAvailableFontFamilyNames(),
            Util.getResourceString("familyLabel"));
        family.addTitledPickListListener(this);
        fontComponents.add(family);
        /* create a pick list for font size */
        final SizePickList size = new SizePickList(SHTMLPanelImpl.FONT_SIZES, Util.getResourceString("sizeLabel"));
        size.addTitledPickListListener(this);
        fontComponents.add(size);
        /* wrap together family and size */
        final JPanel familySizePanel = new JPanel(new BorderLayout(5, 5));
        familySizePanel.add(family, BorderLayout.CENTER);
        familySizePanel.add(size, BorderLayout.EAST);
        /* create a panel to put font parts family, size and style in */
        final JPanel fontPartsPanel = new JPanel(new BorderLayout(5, 5));
        fontPartsPanel.add(familySizePanel, BorderLayout.CENTER);
        final String[] fontStyles = new String[] { Util.getResourceString("plainName"),
                Util.getResourceString("boldName"), Util.getResourceString("italicName"),
                Util.getResourceString("boldItalicName") };
        final StylePickList style = new StylePickList(fontStyles, Util.getResourceString("styleLabel"));
        style.addTitledPickListListener(this);
        fontPartsPanel.add(style, BorderLayout.EAST);
        fontComponents.add(style);
        /* create a panel for underline / line through */
        final EffectPanel linePanel = new EffectPanel();
        fontComponents.add(linePanel);
        /* create a panel for color choices */
        final JPanel colorPanel = new JPanel(new GridLayout(2, 1, 3, 3));
        colorPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), Util
            .getResourceString("colorLabel")));
        ColorPanel fCol = new ColorPanel(Util.getResourceString("foregroundLabel"));
        fCol.addColorPanelListener( e -> sample.setForeground(fCol.getColor()));
        fontComponents.add(new ColorAttributePanel(fCol, CSS.Attribute.COLOR));
        colorPanel.add(fCol);
        sample.setForeground(Color.black);
        ColorPanel bCol = new ColorPanel(Util.getResourceString("backgroundLabel"));
        bCol.addColorPanelListener( e -> sample.setBackground(bCol.getColor()));
        fontComponents.add(new ColorAttributePanel(bCol, CSS.Attribute.BACKGROUND_COLOR));
        colorPanel.add(bCol);
        sample.setBackground(Color.white);
        /* create a panel to combine line and color choices */
        final JPanel eastPanel = new JPanel(new BorderLayout());
        eastPanel.add(linePanel, BorderLayout.NORTH);
        eastPanel.add(colorPanel, BorderLayout.SOUTH);
        /* add all font controls to our font panel */
        add(fontPartsPanel, BorderLayout.CENTER);
        add(eastPanel, BorderLayout.EAST);
        add(previewPanel, BorderLayout.SOUTH);
        add(new JPanel(), BorderLayout.NORTH);
        add(new JPanel(), BorderLayout.WEST);
    }

    /**
     * construct a FontPanel and display a set of attributes
     *
     * @param a  the set of attributes to display
     */
    public FontPanel(final AttributeSet a) {
        this();
        setAttributes(a);
    }
    /**
     * set all components of this FontPanel to reflect a set of
     * attributes.
     *
     * @param a  the set of attributes to show
     */
    public void setAttributes(final AttributeSet a) {
        final Enumeration components = fontComponents.elements();
        while (components.hasMoreElements()) {
            ((AttributeComponent) components.nextElement()).setValue(a);
        }
    }

    /**
     * get the set of attributes resulting from the settings on
     * this FontPanel.
     *
     * @return the set of attributes set in this FontPanel
     */
    public AttributeSet getAttributes() {
        final SimpleAttributeSet attributes = new SimpleAttributeSet();
        final Enumeration components = fontComponents.elements();
        while (components.hasMoreElements()) {
            attributes.addAttributes(((AttributeComponent) components.nextElement()).getValue());
        }
        return attributes;
    }

    public AttributeSet getAttributes(final boolean includeUnchanged) {
        if (includeUnchanged) {
            final SimpleAttributeSet attributes = new SimpleAttributeSet();
            final Enumeration components = fontComponents.elements();
            while (components.hasMoreElements()) {
                attributes.addAttributes(((AttributeComponent) components.nextElement()).getValue(includeUnchanged));
            }
            return attributes;
        }
        else {
            return getAttributes();
        }
    }

    public void reset() {
        Object c;
        for (int i = 0; i < fontComponents.size(); i++) {
            c = fontComponents.get(i);
            if (c instanceof FamilyPickList) {
                ((FamilyPickList) c).reset();
            }
            else if (c instanceof SizePickList) {
                ((SizePickList) c).reset();
            }
            else if (c instanceof StylePickList) {
                ((StylePickList) c).reset();
            }
        }
    }

    /**
     * if another value was picked from a list, update the
     * sample
     */
    public void valueChanged(final TitledPickList.TitledPickListEvent e) {
        final Object source = e.getSource();
        final Font saveFont = sample.getFont();
        if (source instanceof FamilyPickList) {
            sample.setFont(new Font(((FamilyPickList) source).getFamily(), saveFont.getStyle(), saveFont.getSize()));
        }
        else if (source instanceof SizePickList) {
            sample.setFont(new Font(saveFont.getFamily(), saveFont.getStyle(), Integer
                .parseInt((String) ((SizePickList) source).getSelection())));
            /*adjustFontSize(Integer.parseInt((String) ((SizePickList) source).getSelection()))));*/
        }
        else if (source instanceof StylePickList) {
            sample.setFont(new Font(saveFont.getFamily(), ((StylePickList) source).getFontStyle(), saveFont.getSize()));
        }
    }

    /* TODO
     * fix for bug id 4765271
     * (see http://developer.java.sun.com/developer/bugParade/bugs/4765271.html)
     */
    /**
     * extend <code>TitledPickList</code> with a way to set values
     * special to font family values
     */
    static class FamilyPickList extends TitledPickList implements AttributeComponent {
        private int setValCount = 0;
        private Object originalValue;

        /**
         * constructor
         *
         * @param options  the options to be selectable in this list
         * @param titleText  the title for the pick list
         */
        FamilyPickList(final String[] options, final String titleText) {
            super(options, titleText);
        }

        /**
         * set the value of this <code>TitledPickList</code>
         *
         * @param a  the set of attributes possibly having a
         *          font family attribute this pick list could display
         *
         * @return true, if the set of attributes had a font family attribute,
         *            false if not
         */
        public boolean setValue(final AttributeSet a) {
            ignoreTextChanges = true;
            final String newSelection = Util.styleSheet().getFont(a).getFamily();
            setSelection(newSelection);
            ignoreTextChanges = false;
            if (++setValCount < 2) {
                originalValue = newSelection;
            }
            return true;
        }

        public AttributeSet getValue(final boolean includeUnchanged) {
            final SimpleAttributeSet set = new SimpleAttributeSet();
            final Object value = getSelection();
            if (includeUnchanged
                    || ((originalValue == null) && (value != null))
                    || ((originalValue != null) && (value != null) && (!originalValue.toString().equalsIgnoreCase(
                        value.toString())))) {
                Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_FAMILY, value.toString());
            }
            return set;
        }

        public AttributeSet getValue() {
            return getValue(false);
        }

        public String getFamily() {
            return (String) getSelection();
        }

        public void reset() {
            setValCount = 0;
            originalValue = null;
        }
    }

    /**
     * extend <code>TitledPickList</code> with a way to set values
     * special to font size values
     */
    static class SizePickList extends TitledPickList implements AttributeComponent {
        private int setValCount = 0;
        private String originalValue;

        /**
         * constructor
         *
         * @param options  the options to be selectable in this list
         * @param titleText  the title for the pick list
         */
        SizePickList(final String[] options, final String titleText) {
            super(options, titleText);
        }

        /**
         * set the value of this <code>TitledPickList</code>
         *
         * @param a  the set of attributes possibly having a
         *          font size attribute this pick list could display
         *
         * @return true, if the set of attributes had a font size attribute,
         *            false if not
         */
        public boolean setValue(final AttributeSet a) {
            ignoreTextChanges = true;
            final int size = Util.styleSheet().getFont(a).getSize();
            final String newSelection = Integer.toString(size);
            setSelection(newSelection);
            if (++setValCount < 2) {
                originalValue = newSelection;
            }
            return true;
        }

        public AttributeSet getValue(final boolean includeUnchanged) {
            final SimpleAttributeSet set = new SimpleAttributeSet();
            final String value = (String) getSelection();
            if ((includeUnchanged || (originalValue == null) && (value != null))
                    || ((originalValue != null) && (!originalValue.equalsIgnoreCase(value)))) {
                Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_SIZE, value + "pt");
            }
            return set;
        }

        public AttributeSet getValue() {
            return getValue(false);
        }

        public void reset() {
            setValCount = 0;
            originalValue = null;
        }
    }

    /**
     * extend <code>TitledPickList</code> with a way to set values
     * special to font style values
     */
    static class StylePickList extends TitledPickList implements AttributeComponent {
        private int originalValue;
        private int setValCount = 0;

        /**
         * constructor
         *
         * @param options  the options to be selectable in this list
         * @param titleText  the title for the pick list
         */
        StylePickList(final String[] options, final String titleText) {
            super(options, titleText);
        }

        /**
         * set the value of this <code>TitledPickList</code>
         *
         * @param a  the set of attributes possibly having a
         *          font style attribute this pick list could display
         *
         * @return true, if the set of attributes had a font style attribute,
         *            false if not
         */
        public boolean setValue(final AttributeSet a) {
            ignoreTextChanges = true;
            final boolean success = false;
            int styleNo = 0;
            String value;
            if (a.isDefined(CSS.Attribute.FONT_WEIGHT)) {
                value = a.getAttribute(CSS.Attribute.FONT_WEIGHT).toString();
                if (value.equalsIgnoreCase(StyleConstants.Bold.toString())) {
                    styleNo++;
                }
            }
            if (a.isDefined(CSS.Attribute.FONT_STYLE)) {
                value = a.getAttribute(CSS.Attribute.FONT_STYLE).toString();
                if (value.equalsIgnoreCase(StyleConstants.Italic.toString())) {
                    styleNo += 2;
                }
            }
            setSelection(styleNo);
            if (++setValCount < 2) {
                originalValue = styleNo;
            }
            ignoreTextChanges = false;
            return success;
        }

        public AttributeSet getValue() {
            final SimpleAttributeSet set = new SimpleAttributeSet();
            final int styleNo = getIndex();
            switch (styleNo) {
                case 0:
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT, Util.CSS_ATTRIBUTE_NORMAL);
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE, Util.CSS_ATTRIBUTE_NORMAL);
                    break;
                case 1:
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT, StyleConstants.Bold.toString());
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE, Util.CSS_ATTRIBUTE_NORMAL);
                    break;
                case 2:
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT, Util.CSS_ATTRIBUTE_NORMAL);
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE, StyleConstants.Italic.toString());
                    break;
                case 3:
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT, StyleConstants.Bold.toString());
                    Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE, StyleConstants.Italic.toString());
                    break;
            }
            return set;
        }

        public AttributeSet getValue(final boolean includeUnchanged) {
            if (includeUnchanged) {
                final String value = Util.CSS_ATTRIBUTE_NORMAL;
                final SimpleAttributeSet set = new SimpleAttributeSet();
                final int styleNo = getIndex();
                switch (styleNo) {
                    case 0:
                        Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE, value);
                        break;
                    case 1:
                        Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT,
                            StyleConstants.Bold.toString());
                        break;
                    case 2:
                        Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE,
                            StyleConstants.Italic.toString());
                        break;
                    case 3:
                        Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_WEIGHT,
                            StyleConstants.Bold.toString());
                        Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_STYLE,
                            StyleConstants.Italic.toString());
                        break;
                }
                return set;
            }
            else {
                return getValue();
            }
        }

        public int getFontStyle() {
            int fontStyle = 0;
            switch (getIndex()) {
                case 0:
                    fontStyle = Font.PLAIN;
                    break;
                case 1:
                    fontStyle = Font.BOLD;
                    break;
                case 2:
                    fontStyle = Font.ITALIC;
                    break;
                case 3:
                    fontStyle = Font.BOLD | Font.ITALIC;
                    break;
            }
            return fontStyle;
        }

        public void reset() {
            setValCount = 0;
            originalValue = -2;
        }
    }
}
