/*
 * BasicControlPalette.java
 *
 * Copyright (c) 2009 JAM Development Team
 *
 * This package is distributed under the Lesser Gnu Public Licence (LGPL)
 *
 */

package jam.controlpalettes;

import jam.disclosure.DisclosureListener;
import jam.disclosure.DisclosurePanel;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Andrew Rambaut
 * @version $Id: BasicControlPalette.java 948 2008-10-02 00:19:47Z rambaut $
 */
public class BasicControlPalette extends JPanel implements ControlPalette {

    public final static int DEFAULT_OPENING_SPEED = 100;

    public enum DisplayMode {
        DEFAULT_OPEN,
        INITIALLY_OPEN,
        INITIALLY_CLOSED,
        ONLY_ONE_OPEN
    }

    public BasicControlPalette(int preferredWidth) {
        this(preferredWidth, DisplayMode.ONLY_ONE_OPEN, DEFAULT_OPENING_SPEED);
    }

    public BasicControlPalette(int preferredWidth, DisplayMode displayMode) {
        this(preferredWidth, displayMode, DEFAULT_OPENING_SPEED);
    }

    public BasicControlPalette(int preferredWidth, DisplayMode displayMode, int openingSpeed) {
        this.preferredWidth = preferredWidth;
        this.displayMode = displayMode;
        this.openingSpeed = openingSpeed;
//        BoxLayout layout = new BoxLayout(this, BoxLayout.PAGE_AXIS);
        setLayout(new GridBagLayout());
        setOpaque(true);
    }


    public Dimension getPreferredSize() {
        return new Dimension(preferredWidth, super.getPreferredSize().height);
    }

    public Dimension getMaximumSize() {
        return new Dimension(preferredWidth, super.getMaximumSize().height);
    }

    public Dimension getMinimumSize() {
        return new Dimension(preferredWidth, super.getMaximumSize().height);
    }

    public JPanel getPanel() {
        return this;
    }

    private ControllerListener controllerListener = new ControllerListener() {
        public void controlsChanged() {
            layoutControls();
        }
    };

    public void addController(Controller controller) {
        controllers.add(controller);
        controller.addControllerListener(controllerListener);
        setupControls();
    }

    public void addController(int position, Controller controller) {
        controllers.add(position, controller);
        controller.addControllerListener(controllerListener);
        setupControls();
    }

    public void removeController(Controller controller) {
        controller.removeControllerListener(controllerListener);
        controllers.remove(controller);
        setupControls();
    }

    public int getControllerCount() {
        return controllers.size();
    }

    public void fireControlsChanged() {
        for (ControlPaletteListener listener : listeners) {
            listener.controlsChanged();
        }
    }

    public void addControlPaletteListener(ControlPaletteListener listener) {
        listeners.add(listener);
    }

    public void removeControlPaletteListener(ControlPaletteListener listener) {
        listeners.remove(listener);
    }

    private final List<ControlPaletteListener> listeners = new ArrayList<ControlPaletteListener>();

    private void setupControls() {
        removeAll();
        disclosurePanels.clear();
        controlsStates.clear();

        int i = 0;
        for (Controller controller : controllers) {
            setupController(i, controller);
            i++;
        }

        GridBagConstraints gc = new GridBagConstraints();
        gc.weightx = 1.0;
        gc.weighty = 1.0;
        gc.gridx = 0;
        gc.gridy = i;
        gc.fill = GridBagConstraints.HORIZONTAL;
        gc.anchor = GridBagConstraints.FIRST_LINE_START;
        add(new JSeparator(), gc);
    }

    public void layoutControls() {
        for (DisclosurePanel panel : disclosurePanels) {
            panel.invalidate();
        }
        validate();
        revalidate();
    }

    public void initialize() {
        for (Controller controller : controllers) {
            controller.initialize();
        }
    }

    public void getSettings(Map<String,Object> settings) {
        for (Controller controller : controllers) {
            controller.getSettings(settings);
        }
    }

    public void setSettings(Map<String,Object> settings) {
        for (Controller controller : controllers) {
            controller.setSettings(settings);
        }
    }

    @Override
    public void setPreferredWidth(int preferredWidth) {
        this.preferredWidth = preferredWidth;
        invalidate();
    }

    private void setupController(int index, Controller controller) {

        // if there is no title component then this is an invisible controller
        if (controller.getTitleComponent() != null) {
            JPanel titlePanel = new JPanel(new BorderLayout(6, 0));
            titlePanel.setOpaque(false);
            JComponent comp = controller.getTitleComponent();
            comp.setFocusable(false);
            titlePanel.add(comp, BorderLayout.CENTER);

            JPanel controllerPanel = controller.getPanel();
            controllerPanel.setOpaque(false);

            // This tells Mac L&Fs to use a small components (ignored otherwise)
            controller.getTitleComponent().setFont(UIManager.getFont("SmallSystemFont"));
            controller.getTitleComponent().setOpaque(false);

            PinnedButton pinnedButton = new PinnedButton();

            pinnedButton.setSelected(controller.isInitiallyVisible());
            titlePanel.add(pinnedButton, BorderLayout.EAST);

            final DisclosurePanel panel = new DisclosurePanel(
                    titlePanel, preferredTitleHeight, controllerPanel, controller.isInitiallyVisible(), openingSpeed);

            if (displayMode == DisplayMode.ONLY_ONE_OPEN) {
                panel.addDisclosureListener(new DisclosureListener() {
                    public void opening(Component component) {
                    }

                    public void opened(Component component) {
                        int newlyOpened = disclosurePanels.indexOf(component);
                        ControlsState controlsState = controlsStates.get(newlyOpened);

                        if (currentlyOpen >= 0) {
                            DisclosurePanel currentPanel = disclosurePanels.get(currentlyOpen);

                            ControlsState currentControls = controlsStates.get(currentlyOpen);
                            if (!currentControls.isPinned()) {
                                currentPanel.setOpen(false);
                                currentControls.setVisible(false);
                            }
                        }
                        currentlyOpen = newlyOpened;
                        controlsState.setVisible(true);
                        BasicControlPalette.this.invalidate();
                        BasicControlPalette.this.revalidate();
                        invalidate();
                    }

                    public void closing(Component component) {
                    }

                    public void closed(Component component) {
                        int newlyClosed = disclosurePanels.indexOf(component);
                        ControlsState controlsState = controlsStates.get(newlyClosed);
                        controlsState.setVisible(false);

                        if (newlyClosed == currentlyOpen) {
                            currentlyOpen = -1;
                        }
                        BasicControlPalette.this.invalidate();
                        BasicControlPalette.this.revalidate();
                        invalidate();
                    }
                });
            }

            final ControlsState controlsState = new ControlsState(
                    controller.isInitiallyVisible(),
                    pinnedButton.isSelected());

            pinnedButton.addItemListener(new ItemListener() {
                public void itemStateChanged(ItemEvent itemEvent) {
                    controlsState.setPinned(itemEvent.getStateChange() == ItemEvent.SELECTED);
                }
            });
            disclosurePanels.add(panel);
            controlsStates.add(controlsState);

//            panel.setAlignmentX(Component.LEFT_ALIGNMENT);
//            add(panel);
            GridBagConstraints gc = new GridBagConstraints();
            gc.weightx = 1.0;
            gc.weighty = 0.0;
            gc.gridx = 0;
            gc.gridy = index;
            gc.fill = GridBagConstraints.HORIZONTAL;
            gc.anchor = GridBagConstraints.FIRST_LINE_START;
            add(panel, gc);
        }
    }

    private int preferredWidth;
    private int preferredTitleHeight = 22;

    private DisplayMode displayMode;
    private final int openingSpeed;
    private int currentlyOpen = 0;
    private List<Controller> controllers = new ArrayList<Controller>();
    private List<DisclosurePanel> disclosurePanels = new ArrayList<DisclosurePanel>();
    private List<ControlsState> controlsStates = new ArrayList<ControlsState>();

    private class ControlsState {
        ControlsState(boolean visible, boolean pinned) {
            isVisible = visible;
            isPinned = pinned;
        }

        boolean isVisible() {
            return isVisible;
        }

        void setVisible(boolean visible) {
            isVisible = visible;
        }

        boolean isPinned() {
            return isPinned;
        }

        void setPinned(boolean pinned) {
            isPinned = pinned;
        }

        private boolean isVisible;
        private boolean isPinned;
    }
}