/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.css.visual;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.UIManager;
import javax.swing.border.Border;
import org.netbeans.modules.css.visual.api.RuleEditorController;
import org.netbeans.modules.css.visual.spi.CssStylesPanelProvider;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.ProxyLookup;

/**
 *
 * @author marekfukala
 */
public class CssStylesPanel extends javax.swing.JPanel {

    private static final RequestProcessor RP = new RequestProcessor(CssStylesPanel.class);

    static final boolean AQUA = "Aqua".equals(UIManager.getLookAndFeel().getID()); //NOI18N 

    private final RuleEditorController controller;
    private final Collection<CssStylesPanelProvider> providers;
    private final ActionListener toolbarListener;

    /* Lookup for CssStylesPanelProviders. The content mutates based on changed file context.*/
    private final ModifiableLookup providersLookup;

    /* Lookup for the CssStylesTC. Content got from the CssStylesPanelProvider's lookup */
    private final ModifiableLookup tcLookup;

    private final JToolBar toolBar;

    private CssStylesPanelProvider active;
    private JComponent activePanel;

    /**
     * Remember last selected tab per mimetype.
     *
     * Note: this is not exactly correct as the panel's activity is no more
     * driven purely by the file context mimetype. See {@link CssStylesPanelProvider#providesContentFor(org.openide.filesystems.FileObject)
     * }.
     */
    private Map<String, CssStylesPanelProvider> selectedTabs = new HashMap<String, CssStylesPanelProvider>();

    /**
     * Creates new form CssStylesPanel
     */
    public CssStylesPanel() {
        initComponents();

        tcLookup = new ModifiableLookup();
        providersLookup = new ModifiableLookup();
        //assumption: should not change in time, otherwise we need to listen
        providers = new ArrayList<CssStylesPanelProvider>();
        for (CssStylesPanelProvider provider : Lookup.getDefault().lookupAll(CssStylesPanelProvider.class)) {
            providers.add(new ProxyCssStylesPanelProvider(provider));
        }

        //the bottom component
        controller = RuleEditorController.createInstance();

        //put of the rule editor component initialization so the whole css styles panel
        //initialization is not done in one chunk
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                splitPane.setBottomComponent(controller.getRuleEditorComponent());
            }
        });

        //toolbar
        toolbarListener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent ae) {
                String command = ae.getActionCommand();
                //linear search, but should be at most 2 or 3 items
                for (CssStylesPanelProvider provider : providers) {
                    if (provider.getPanelID().equals(command)) {
                        FileObject file = providersLookup.lookup(FileObject.class);
                        selectedTabs.put(file.getMIMEType(), provider);
                        setActiveProvider(provider);
                    }
                }
            }
        };

        toolBar = new JToolBar();
        toolBar.setFloatable(false);

        //copied from org.netbeans.core.multiview.TabsComponent to make the look 
        //similar to the editor tabs
        Border b = (Border) UIManager.get("Nb.Editor.Toolbar.border"); //NOI18N
        toolBar.setBorder(b);
        toolBar.setFocusable(true);
        if ("Windows".equals(UIManager.getLookAndFeel().getID())
                && !isXPTheme()) {
            toolBar.setRollover(true);
        } else if (AQUA) {
            toolBar.setBackground(UIManager.getColor("NbExplorerView.background"));
        }

        splitPane.setResizeWeight(0.66);
    }

    private Border buttonBorder = null;

    private Border getButtonBorder() {
        if (buttonBorder == null) {
            //For some lf's, core will supply one
            buttonBorder = UIManager.getBorder("nb.tabbutton.border"); //NOI18N
        }

        return buttonBorder;
    }

    private static boolean isXPTheme() {
        Boolean isXP = (Boolean) Toolkit.getDefaultToolkit().
                getDesktopProperty("win.xpstyle.themeActive"); //NOI18N
        return isXP == null ? false : isXP.booleanValue();
    }

    private JSplitPane createSplitPane() {
        return new JSplitPane() {

            @Override
            public String getUIClassID() {
                if (AQUA && UIManager.get("Nb.SplitPaneUI.clean") != null) //NOI18N
                {
                    return "Nb.SplitPaneUI.clean"; //NOI18N
                }
                return super.getUIClassID();
            }
        };
    }

    /**
     * Returns lookup which content changes based on the lookups of the active
     * CssStylesPanelProvider.
     */
    public Lookup getLookup() {
        return tcLookup;
    }

    private Collection<CssStylesPanelProvider> getActiveProviders(FileObject file) {
        Collection<CssStylesPanelProvider> active = new ArrayList<CssStylesPanelProvider>();
        for (CssStylesPanelProvider provider : providers) {
            if (provider.providesContentFor(file)) {
                active.add(provider);
            }
        }
        return active;
    }

    private void addToolbar() {
        if (toolBar.getParent() == null) {
            //not added in the hierarchy, add it
            topPanel.add(toolBar, BorderLayout.PAGE_START);
        }
    }

    private void removeToolbar() {
        if (toolBar.getParent() != null) {
            //preset in the hierarchy, remove it
            topPanel.remove(toolBar);
        }
    }

    private void updateToolbar(final FileObject file) {
        RP.post(new Runnable() {

            @Override
            public void run() {
                //getActiveProviders() must not be called in EDT as it might do some I/Os
                final Collection<CssStylesPanelProvider> activeProviders = getActiveProviders(file);
                EventQueue.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        updateToolbar(file, activeProviders);
                    }

                });
            }

        });
    }

    private void updateToolbar(FileObject file, Collection<CssStylesPanelProvider> activeProviders) {

        toolBar.removeAll();
        if (activeProviders.size() <= 1) {
            //remove the whole toolbar, if there's one or zero providers
            removeToolbar();
        } else {
            addToolbar();
        }

        // Button group for document and source buttons
        ButtonGroup buttonGroup = new ButtonGroup();

        boolean first = true;

        CssStylesPanelProvider selected = (file == null) ? null : selectedTabs.get(file.getMIMEType());

        //do the active providers contain the pre-selected provider for this mimetype?
        boolean containsPreselected = selected == null ? false : activeProviders.contains(selected);

        for (CssStylesPanelProvider provider : activeProviders) {
            JToggleButton button = new JToggleButton();
            button.setText(provider.getPanelDisplayName());
            button.setActionCommand(provider.getPanelID());
            button.addActionListener(toolbarListener);

            button.setFocusable(true);
            button.setFocusPainted(false);
            button.setRolloverEnabled(true);

            //copied from org.netbeans.core.multiview.TabsComponent.createButton to make the look 
            //similar to the editor tabs
            Border b = (getButtonBorder());
            if (b != null) {
                button.setBorder(b);
            }
            if (AQUA) {
                button.putClientProperty("JButton.buttonType", "square"); //NOI18N
                button.putClientProperty("JComponent.sizeVariant", "small"); //NOI18N
            }

            buttonGroup.add(button);
            toolBar.add(button);

            if (containsPreselected) {
                //one of the active providers is already pre-selected by user
                if (provider == selected) {
                    //the selected one - activate it
                    button.setSelected(true);
                    setActiveProvider(provider);
                } else {
                    button.setSelected(false);
                }
            } else {
                //no provider has been explicitly selected by the user yet
                button.setSelected(first);
                if (first) {
                    setActiveProvider(provider);
                    first = false;
                }
            }
        }

        revalidate();
        repaint();
    }

    public void setContext(FileObject file) {
        InstanceContent ic = new InstanceContent();
        if (file != null) {
            ic.add(file);
        }
        ic.add(getRuleEditorController());
        providersLookup.updateLookup(new AbstractLookup(ic));

        updateToolbar(file);
    }

    private void setActiveProvider(CssStylesPanelProvider provider) {
        if (active == provider) {
            return; //no change
        }

        if (active != null) {
            topPanel.remove(activePanel);
            active.deactivated();
        }

        active = provider;
        activePanel = provider.getContent(providersLookup);

        topPanel.add(activePanel, BorderLayout.CENTER);
        active.activated();

        //propagate the provider's lookup to the lookup of the CssStylesTC.
        tcLookup.updateLookup(active.getLookup());

        revalidate();
        repaint();
    }

    /**
     * Returns the default {@link RuleEditorController} associated with this
     * rule editor top component.
     */
    public RuleEditorController getRuleEditorController() {
        return controller;
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        splitPane = createSplitPane();
        topPanel = new javax.swing.JPanel();

        setLayout(new java.awt.BorderLayout());

        splitPane.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0));
        splitPane.setDividerSize(4);
        splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);

        topPanel.setLayout(new java.awt.BorderLayout());
        splitPane.setTopComponent(topPanel);

        add(splitPane, java.awt.BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JSplitPane splitPane;
    private javax.swing.JPanel topPanel;
    // End of variables declaration//GEN-END:variables

    private static class ModifiableLookup extends ProxyLookup {

        protected final void updateLookup(Lookup lookup) {
            if (lookup == null) {
                setLookups();
            } else {
                setLookups(lookup);
            }
        }
    }

    /**
     * Caches the content panel so the real provider is asked for it just once.
     */
    private static class ProxyCssStylesPanelProvider implements CssStylesPanelProvider {

        private final CssStylesPanelProvider delegate;
        private JComponent content;

        public ProxyCssStylesPanelProvider(CssStylesPanelProvider delegate) {
            this.delegate = delegate;
        }

        @Override
        public String getPanelID() {
            return delegate.getPanelID();
        }

        @Override
        public String getPanelDisplayName() {
            return delegate.getPanelDisplayName();
        }

        @Override
        public JComponent getContent(Lookup lookup) {
            if (content == null) {
                content = delegate.getContent(lookup);
            }
            return content;
        }

        @Override
        public Lookup getLookup() {
            return delegate.getLookup();
        }

        @Override
        public void activated() {
            delegate.activated();
        }

        @Override
        public void deactivated() {
            delegate.deactivated();
        }

        @Override
        public boolean providesContentFor(FileObject file) {
            return delegate.providesContentFor(file);
        }
    }
}
