/*
 * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 * 
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.graalvm.visualvm.core.ui;

import org.graalvm.visualvm.core.datasource.DataSource;
import org.graalvm.visualvm.core.snapshot.Snapshot;
import org.graalvm.visualvm.core.ui.components.DataViewComponent;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Extensible DataSourceViewProvider with support for customizations via DataSourceViewPluginProvider.
 *
 * @author Jiri Sedlacek
 */
public abstract class PluggableDataSourceViewProvider<X extends DataSource> extends DataSourceViewProvider<X> {
    
    /**
     * Set defining all pluggable locations.
     */
    public static final Set<Integer> ALL_LOCATIONS;
    static {
        ALL_LOCATIONS = new HashSet();
        ALL_LOCATIONS.add(DataViewComponent.TOP_LEFT);
        ALL_LOCATIONS.add(DataViewComponent.TOP_RIGHT);
        ALL_LOCATIONS.add(DataViewComponent.BOTTOM_LEFT);
        ALL_LOCATIONS.add(DataViewComponent.BOTTOM_RIGHT);
    }
    
    private final Set<DataSourceViewPluginProvider<X>> pluginProviders =
            Collections.synchronizedSet(new HashSet());
    private final Map<X, Set<DataSourceViewPluginProvider<X>>> pluginProvidersCache =
            Collections.synchronizedMap(new HashMap());
    
    
    /**
     * Registers new DataSourceViewPluginProvider.
     * 
     * @param pluginProvider DataSourceViewPluginProvider to register.
     */
    public final void registerPluginProvider(DataSourceViewPluginProvider<X> pluginProvider) {
        pluginProviders.add(pluginProvider);
    }
    
    /**
     * Unregisters DataSourceViewPluginProvider.
     * 
     * @param pluginProvider DataSourceViewPluginProvider to unregister.
     */
    public final void unregisterPluginProvider(DataSourceViewPluginProvider<X> pluginProvider) {
        pluginProviders.remove(pluginProvider);
    }
    
    /**
     * Returns Set of all locations of the DataSourceView which can be customized by DataSourceViewPluginProviders.
     * 
     * @param view DataSourceView to be customized.
     * @return Set of all locations of the DataSourceView which can be customized by DataSourceViewPluginProviders.
     */
    public abstract Set<Integer> getPluggableLocations(DataSourceView view);
    
    
    /**
     * Saves the DataSourceView for the DataSource into the Snapshot.
     * 
     * @param dataSource DataSource for which to save the view.
     * @param snapshot Snapshot into which to save the view.
     */
    protected void saveView(X dataSource, Snapshot snapshot) {
    };
    
    
    void viewSaveView(X dataSource, Snapshot snapshot) {
        super.viewSaveView(dataSource, snapshot);
        Set<DataSourceViewPluginProvider<X>> providers = getProviders(dataSource, false);
        for (DataSourceViewPluginProvider<X> provider : providers)
            if (provider.supportsSavePluginFor(dataSource, snapshot.getClass()))
                provider.savePlugin(dataSource, snapshot);
    }
    
    void processCreatedComponent(DataSourceView view, DataViewComponent component) {
        X dataSource = (X)view.getDataSource();
        Set<DataSourceViewPluginProvider<X>> providers = getProviders(dataSource, true);
        for (DataSourceViewPluginProvider<X> provider : providers) {
            Set<Integer> pluggableLocations = getPluggableLocations(view);
            DataSourceViewPlugin plugin = provider.getPlugin(dataSource);
            for (int pluggableLocation : pluggableLocations) {
                DataViewComponent.DetailsView pluginView = plugin.createView(pluggableLocation);
                if (pluginView != null) component.addDetailsView(pluginView, pluggableLocation);
            }
        }
    }
    
    void viewWillBeAdded(DataSourceView view) {
        X dataSource = (X)view.getDataSource();
        Set<DataSourceViewPluginProvider<X>> providers = getProviders(dataSource, true);
        for (DataSourceViewPluginProvider<X> provider : providers)
            provider.getPlugin(dataSource).pluginWillBeAdded();
    }
    
    void viewAdded(DataSourceView view) {
        X dataSource = (X)view.getDataSource();
        Set<DataSourceViewPluginProvider<X>> providers = getProviders(dataSource, true);
        for (DataSourceViewPluginProvider<X> provider : providers)
            provider.getPlugin(dataSource).pluginAdded();
    }
    
    void viewRemoved(DataSourceView view) {
        X dataSource = (X)view.getDataSource();
        Set<DataSourceViewPluginProvider<X>> providers = getProviders(dataSource, true);
        for (DataSourceViewPluginProvider<X> provider : providers)
            provider.getPlugin(dataSource).pluginRemoved();
        pluginProvidersCache.remove(dataSource);
        super.viewRemoved(view);
    }
    
    
    private Set<DataSourceViewPluginProvider<X>> getProviders(X dataSource, boolean cache) {
        Set<DataSourceViewPluginProvider<X>> providers = pluginProvidersCache.get(dataSource);
        if (providers != null) return providers;
        providers = new HashSet(pluginProviders);
        Set<DataSourceViewPluginProvider<X>> compatibleProviders = new HashSet();
        for (DataSourceViewPluginProvider<X> provider : providers)
            if (provider.supportsPluginFor(dataSource))
                compatibleProviders.add(provider);
        if (cache) pluginProvidersCache.put(dataSource, compatibleProviders);
        return compatibleProviders;
    }

}
