/**********************************************************************
 * Copyright (c) 2005, 2014 IBM Corporation, Ericsson
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Bernd Hufmann - Updated for TMF
 **********************************************************************/

package org.eclipse.tracecompass.tmf.ui.views.uml2sd.load;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.tmf.ui.views.uml2sd.SDView;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

/**
 * Manager class for the UML2SD extension point.
 *
 * @version 1.0
 * @author sveyrier
 * @author Bernd Hufmann
 */
public class LoadersManager {

    // ------------------------------------------------------------------------
    // Constants
    // ------------------------------------------------------------------------

    /** Namespace of the extension point */
    private static final String EXT_NAMESPACE = "org.eclipse.linuxtools.tmf.ui"; //$NON-NLS-1$

    /** The loader tag for the extension point */
    private static final String LOADER_TAG = "uml2SDLoader"; //$NON-NLS-1$

    /** The loader prefix */
    private static final String LOADER_PREFIX = LOADER_TAG + "."; //$NON-NLS-1$

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    /**
     * The LoadersManager singleton instance.
     */
    private static LoadersManager fLoadersManager;

    /**
     * Map for caching information (view ID to loader class)
     */
    private Map<String, IUml2SDLoader> fViewLoaderMap = new HashMap<>();
    /**
     * Map for caching information (view ID to list of configuration elements)
     */
    private Map<String, ArrayList<IConfigurationElement>> fViewLoadersList = new HashMap<>();

    // ------------------------------------------------------------------------
    // Constructor
    // ------------------------------------------------------------------------
    /**
     * This should not be used by the clients
     */
    protected LoadersManager() {
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------
    /**
     * A static method to get the manager instance.
     *
     * @return the manager instance
     */
    public static synchronized LoadersManager getInstance() {
        if (fLoadersManager == null) {
            fLoadersManager = new LoadersManager();
        }
        return fLoadersManager;
    }

    /**
     * Creates a loader instance and associate it to the view. It requires
     * that the loader-view-association was created by an eclipse extension.
     *
     * @param className The name of the class to create an instance from
     * @param view The UML2 Sequence Diagram view instance
     * @return The created loader
     */
    public IUml2SDLoader createLoader(String className, SDView view) {

        // Safety check
        if (view == null) {
            return null;
        }

        String viewId = view.getViewSite().getId();

        // Get loaders from all extensions for given view
        List<IConfigurationElement> loaderElements = getLoaderConfigurationElements(viewId);
        IConfigurationElement ce = getLoaderConfigurationElement(className, loaderElements);

        if (ce != null) {
            // Assign a loader instance to this view
            createLoaderForView(viewId, ce);
            IUml2SDLoader loader = fViewLoaderMap.get(viewId);
            if (loader != null) {
                loader.setViewer(view);
                return loader;
            }
        }
        return null;
    }

    /**
     * Sets the loader to null for this view, a kind of clean-up while disposing.
     *
     * @param viewId the id of the view
     */
    public void resetLoader(String viewId) {
        IUml2SDLoader loader = fViewLoaderMap.get(viewId);
        if (loader != null) {
            loader.dispose();
        }
        fViewLoaderMap.put(viewId, null);
    }

    /**
     * Returns the loader in use in given Sequence Diagram View
     *
     * @param viewId The Sequence Diagram viewId.
     * @return the current loader if any - null otherwise
     */
    public IUml2SDLoader getCurrentLoader(String viewId) {
        return getCurrentLoader(viewId, null);
    }

    /**
     * Returns the loader in use in this Sequence Diagram View
     *
     * @param viewId The Sequence Diagram viewId
     * @param view The Sequence Diagram view (if known). Use null to reference the primary SD View.
     * @return the current loader if any - null otherwise
     */
    public IUml2SDLoader getCurrentLoader(String viewId, SDView view) {
        if (viewId == null) {
            return null;
        }

        IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
        // During Eclipse shutdown the active workbench window is null
        if (window == null) {
            return null;
        }

        IWorkbenchPage persp = window.getActivePage();

        SDView sdView = view;

        try {
            // Search the view corresponding to the viewId
            if (sdView == null) {
                IViewReference viewref = persp.findViewReference(viewId);
                if (viewref != null) {
                    sdView = (SDView) viewref.getView(false);
                }

                if (sdView == null) {
                    // no corresponding view exists -> return null for the loader
                    return null;
                }
            }

            // Return the loader corresponding to that view (if any)
            IUml2SDLoader loader = fViewLoaderMap.get(viewId);
            if (loader == null) {
                createLastLoaderIfAny(viewId);
                loader = fViewLoaderMap.get(viewId);
            }

            return loader;
        } catch (Exception e) {
            Activator.getDefault().logError("Error getting loader class", e); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Returns the loader class name that have been saved last time.
     *
     * @param viewId The view this loader belongs to
     * @return the class name of the saved loader
     */
    public String getSavedLoader(String viewId) {
        IPreferenceStore p = Activator.getDefault().getPreferenceStore();
        return p.getString(LOADER_PREFIX + viewId);
    }

    /**
     * Saves the last loader in order to reload it on next session.
     *
     * @param id
     *            Standalone ID of the loader
     * @param id2
     *            Suffix ID of the loader
     */
    public void saveLastLoader(String id, String id2) {
        IPreferenceStore p = Activator.getDefault().getPreferenceStore();
        p.setValue(LOADER_PREFIX + id2, id);
    }

    /**
     * Changes the current unique loader to the given secondary viewId.
     *
     * @param loader The current loader
     * @param id the view secondary id or null
     */
    private void setCurrentLoader(IUml2SDLoader loader, String id) {
        if (id == null) {
            return;
        }

        // Get the loader in use
        IUml2SDLoader currentLoader = fViewLoaderMap.get(id);

        if ((currentLoader != null) && (currentLoader != loader)) {
            if (loader != null) {
                IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                // During Eclipse shutdown the active workbench window is null
                if (window == null) {
                    return;
                }
                IWorkbenchPage persp = window.getActivePage();
                try {
                    // Search view corresponding to the viewId
                    SDView sdview = null;
                    IViewReference viewref = persp.findViewReference(id);
                    if (viewref != null) {
                        sdview = (SDView) viewref.getView(false);
                    }

                    // Make everything clean for the new loader
                    if (sdview != null) {
                        sdview.resetProviders();
                    }

                } catch (Exception e) {
                    Activator.getDefault().logError("Error setting current loader class", e); //$NON-NLS-1$
                }
            }
            // The old loader is going to be kicked
            currentLoader.dispose();
        }

        // Replace the current loader by the new one in the map
        fViewLoaderMap.put(id, loader);

        // Store this loader in the preferences to be able to restore it when the workbench will be re-launched
        if (loader != null) {
            saveLastLoader(loader.getClass().getName(), id);
        }
    }

    /**
     * Creates the last loader and saves it. If not last is not available, it creates
     * and saves the default loader, else no loader is created.
     *
     * @param viewId The view ID.
     */
    private void createLastLoaderIfAny(String viewId) {
        // Get saved loader from preferences
        String loaderName = getSavedLoader(viewId);

        // Get loaders from all extensions for given view
        List<IConfigurationElement> loaderElements = getLoaderConfigurationElements(viewId);
        IConfigurationElement ce = getLoaderConfigurationElement(loaderName, loaderElements);

        if (ce == null) {
            ce = getDefaultLoader(loaderElements);
        }

        if (ce != null) {
            createLoaderForView(viewId, ce);
        }
    }

    /**
     * Gets a list of loader configuration elements from the extension point registry for a given view.
     * @param viewId The view ID
     * @return List of extension point configuration elements.
     */
    private List<IConfigurationElement> getLoaderConfigurationElements(String viewId) {
        List<IConfigurationElement> list = fViewLoadersList.get(viewId);
        if (list != null) {
            return list;
        }

        ArrayList<IConfigurationElement> ret = new ArrayList<>();
        IExtensionPoint iep = Platform.getExtensionRegistry().getExtensionPoint(EXT_NAMESPACE, LOADER_TAG);
        if (iep == null) {
            return ret;
        }

        IExtension[] ie = iep.getExtensions();
        if (ie == null) {
            return ret;
        }

        for (int i = 0; i < ie.length; i++) {
            IConfigurationElement c[] = ie[i].getConfigurationElements();
            for (int j = 0; j < c.length; j++) {
                if (viewId.equals(c[j].getAttribute("view"))) { //$NON-NLS-1$
                    ret.add(c[j]);
                }
            }
        }
        fViewLoadersList.put(viewId, ret);
        return ret;
    }

    /**
     * Returns the loader configuration element for given loader class name and for the given
     * list of configuration elements, if available else null.
     *
     * @param loaderClassName The loader class name.
     * @param loaderElements  The list of loader configuration elements
     * @return Extension point configuration element
     */
    private static IConfigurationElement getLoaderConfigurationElement(
            String loaderClassName, List<IConfigurationElement> loaderElements) {
        if (loaderClassName != null && loaderClassName.length() > 0) {
            // Find configuration element corresponding to the saved loader
            for (Iterator<IConfigurationElement> i = loaderElements.iterator(); i.hasNext();) {
                IConfigurationElement ce = i.next();
                if (ce.getAttribute("class").equals(loaderClassName)) { //$NON-NLS-1$
                    return ce;
                }
            }
        }
        return null;
    }

    /**
     * Returns the loader configuration element for the given list of configuration elements, if available else null.
     * Note that if multiple default loaders are defined it selects the first one

     * @param loaderElements The list of loader configuration elements
     * @return The default extension point configuration element.
     */
    private static IConfigurationElement getDefaultLoader(
            List<IConfigurationElement> loaderElements) {
        // Look for a default loader
        for (Iterator<IConfigurationElement> i = loaderElements.iterator(); i.hasNext();) {
            IConfigurationElement ce = i.next();
            if (Boolean.valueOf(ce.getAttribute("default")).booleanValue()) { //$NON-NLS-1$
                return ce;
            }
        }
        return null;
    }

    /**
     * Creates an instance of the loader class for a given extension point configuration element and
     * also sets it as current loader for the given view.
     *
     * @param viewId The view ID.
     * @param ce The extension point configuration element
     */
    private void createLoaderForView(String viewId, IConfigurationElement ce) {
        try {
            Object obj = ce.createExecutableExtension("class"); //$NON-NLS-1$
            IUml2SDLoader l = (IUml2SDLoader) obj;
            if (viewId != null) {
                setCurrentLoader(l, viewId);
            }
        } catch (CoreException e4) {
            Activator.getDefault().logError("Error 'uml2SDLoader' Extension point", e4); //$NON-NLS-1$
        } catch (Exception e5) {
            Activator.getDefault().logError("Error 'uml2SDLoader' Extension point", e5); //$NON-NLS-1$
        }
    }
}
