/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.gui.panel;

import java.awt.Component;

import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractListModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

import net.sf.jaxodraw.util.JaxoLog;
import net.sf.jaxodraw.util.JaxoLooknFeel;


/** Wrapper for a panel that displays the currently installed LookAndFeels
 * and allows to choose supported ones.
 * @since 2.0
 */

// NOTES:
// 1. If the current LookAndFeel 'l' is not installed, this will add it to the
// list of choosable LookAndFeels anyway. However, if the LookAndFeel is switched
// to another one, 'l' will disappear from the lists of JaxoLookAndFeelPanels
// created afterwards.
// 2. The list of installed LookAndFeels is read on creation time. The choosable
// LookAndFeels are the installed LookAndFeels and also any LookAndFeels that were
// current on initialization or when setValue/setToUIManagerValue have been called.
public class JaxoLookAndFeelPanel {
    private final JScrollPane root;
    private final JList list;
    private final LookAndFeelListModel lookAndFeels;

    /** Constructor. */
    public JaxoLookAndFeelPanel() {
        lookAndFeels = new LookAndFeelListModel();

        final LookAndFeel currentLookAndFeel = UIManager.getLookAndFeel();

        final LookAndFeelInfo[] ll = UIManager.getInstalledLookAndFeels();

        for (int i = 0; i < ll.length; i++) {
            boolean supported = true;

            try { // check whether this LaF is supported - alas
                JaxoLooknFeel.setLookAndFeel(ll[i].getClassName());
            } catch (Exception x) {
                JaxoLog.debug(x);
                supported = false;
            }

            lookAndFeels.add(ll[i], supported);
        }

        try {
            JaxoLooknFeel.setLookAndFeel(currentLookAndFeel);
        } catch (Exception x) {
            // this should work in any case
            JaxoLog.debug(x);
        }

        list = new JList(lookAndFeels);
        list.setVisibleRowCount(5);
        list.setPrototypeCellValue(new LookAndFeelInfo(
                "LookAndFeel with a very long name", null));
        root = new JScrollPane(list);

        // Some hacks to have only supported items selectable while allowing
        // changes to lead/anchor; also do not de-selecting.
        list.setSelectionModel(new DefaultListSelectionModel() {
                private static final long serialVersionUID = 7526475622776147L;
                @Override
                public void addSelectionInterval(final int index0, final int index1) {
                    setSelectionInterval(index0, index1);
                }

                @Override
                public void setSelectionInterval(final int index, final int index1) {
                    int index0 = index;

                    if (index0 != index1) {
                        index0 = index1;
                    }

                    if (lookAndFeels.isSupported(index0)) {
                        super.setSelectionInterval(index0, index1);
                    } else {
                        setLeadAnchor(index0, index1);
                    }
                }

                @Override
                public void removeSelectionInterval(final int index0, final int index1) {
                    // nop
                }

                @Override
                public void clearSelection() {
                    // nop
                }

                private void setLeadAnchor(final int anchor, final int lead) {
                    if (isSelectedIndex(lead)) {
                        super.addSelectionInterval(lead, lead);
                    } else {
                        super.removeSelectionInterval(lead, lead);
                    }

                    setAnchorSelectionIndex(anchor);
                }
            });

        // Display human-readable name, and show non-supported items disabled.
        list.setCellRenderer(new DefaultListCellRenderer() {
                private static final long serialVersionUID = 7526471155622777L;
                {
                    JaxoLooknFeel.registerComponent(this);
                }

                @Override
                public Component getListCellRendererComponent(final JList jlist,
                    final Object value, final int index, final boolean selected, final boolean focused) {
                    final LookAndFeelInfo l = (LookAndFeelInfo) value;
                    super.getListCellRendererComponent(jlist, l.getName(),
                        index, selected, focused);
                    setEnabled(jlist.isEnabled() && (index != -1)
                        && ((LookAndFeelListModel) jlist.getModel())
                        .isSupported(index));

                    return this;
                }
            });

        setUpUIManagerValue();
    }

    /**
     * Root component (constant).
     * @return The root component.
     */
    public final JComponent getRoot() {
        return root;
    }

    /**
     * Chosen LookAndFeel class name.
     * @return The chosen LAF as a string.
     */
    public String getValue() {
        if (list.getSelectedIndex() == -1) { // paranoia?
            return UIManager.getLookAndFeel().getClass().getName();
        } else {
            return ((LookAndFeelInfo) list.getSelectedValue()).getClassName();
        }
    }

    /**
     * Set chosen LookAndFeel to the one with matching 'name'.
     * If not found, reset to the current LookAndFeel.
     * @param value The LAF to set.
     */
    public void setValue(final String value) {
        // Take first as default
        int selectedIndex = -1;

        for (int i = 0; i < lookAndFeels.getSize(); i++) {
            if (value.equals(lookAndFeels.getInfoAt(i).getClassName())) {
                selectedIndex = i;
                break;
            }
        }

        if (selectedIndex == -1) {
            setToUIManagerValue();
        } else {
            list.setSelectedIndex(selectedIndex);
        }
    }

    /**
     * Current LookAndFeel class name. This is the LookAndFeel name that
     * would be selected (possibly added to the list) if setToUIManagerValue()
     * or setValue(.) with unknown argument were called.
     * @return The current LAF class name.
     */
    public String getUIManagerValue() {
        return UIManager.getLookAndFeel().getClass().getName();
    }

    /** Set chosen LookAndFeel to the current UIManager one. */
    public void setToUIManagerValue() {
        setUpUIManagerValue();
    }

    private void setUpUIManagerValue() {
        final String currentClassName =
            UIManager.getLookAndFeel().getClass().getName();

        int selectedIndex = -1;

        for (int i = 0; i < lookAndFeels.getSize(); i++) {
            final LookAndFeelInfo l = lookAndFeels.getInfoAt(i);

            if (currentClassName.equals(l.getClassName())) {
                selectedIndex = i;
                break;
            }
        }

        // If not there, add current LookAndFeel to the list
        if (selectedIndex == -1) {
            selectedIndex = lookAndFeels.getSize();
            lookAndFeels.add(new LookAndFeelInfo(UIManager.getLookAndFeel()
                                                          .getName(),
                    currentClassName), true);
        }

        list.setSelectedIndex(selectedIndex);
    }

    // ListModel containing LookAndFeelInfos, knowing which are supported.
    private static class LookAndFeelListModel extends AbstractListModel {
        private static final long serialVersionUID = 7526471155622776147L;
        private final List<Object> data;

        LookAndFeelListModel() {
            super();
            data = new ArrayList<Object>(16);
        }

        public int getSize() {
            return data.size() / 2;
        }

        public Object getElementAt(final int index) {
            return data.get(index * 2);
        }

        public LookAndFeelInfo getInfoAt(final int index) {
            return (LookAndFeelInfo) getElementAt(index);
        }

        public boolean isSupported(final int index) {
            return data.get((index * 2) + 1).equals(Boolean.TRUE);
        }

        public void add(final LookAndFeelInfo l, final boolean supported) {
            final int index = getSize();

            data.add(l);
            data.add(Boolean.valueOf(supported));

            fireIntervalAdded(this, index, index);
        }
    }
}
