/*******************************************************************************
 * Copyright (c) 2012, 2018 Red Hat, Inc.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Red Hat initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.profiling.launch.provider.launch;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;

import org.eclipse.cdt.launch.AbstractCLaunchDelegate;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.linuxtools.internal.profiling.launch.ProfileLaunchPlugin;
import org.eclipse.linuxtools.internal.profiling.launch.provider.ProviderProfileConstants;
import org.eclipse.linuxtools.profiling.launch.ProfileLaunchConfigurationTabGroup;
import org.eclipse.linuxtools.profiling.launch.ProfileLaunchShortcut;
import org.eclipse.ui.preferences.ScopedPreferenceStore;

/**
 * This class has various methods to access relevant information from
 * the extension point defined by the schema :
 *
 * org.eclipse.linuxtools.profiling.launch.launchProvider
 *
 */
public class ProviderFramework {

    /**
     * Get a profiling launch shortcut that provides the specified type of profiling. This
     * looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code> that have a
     * specific type attribute.
     *
     * @param type A profiling type (eg. memory, snapshot, timing, etc.)
     * @return a profiling launch shortcut that implements <code>ProfileLaunchShortcut</code>
     * and provides the necessary profiling type, or <code>null</code> if none could be found.
     * @since 1.2
     */
    public static ProfileLaunchShortcut getProfilingProvider(String type) {
        ArrayList<IConfigurationElement> configList = getOrderedConfigElements(type);

        for (IConfigurationElement config : configList) {
            try {
                Object obj = config.createExecutableExtension("shortcut"); //$NON-NLS-1$
                if (obj instanceof ProfileLaunchShortcut) {
                    return (ProfileLaunchShortcut) obj;
                }
            } catch (CoreException e) {
                // continue, other configuration may succeed
            }
        }
        return null;
    }

    /**
     * Get a profiling launch shortcut that is associated with the specified id.
     * This looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * that have a specific id.
     *
     * @param id A unique identifier
     * @return a profiling launch shortcut that implements <code>ProfileLaunchShortcut</code>
     * and provides the necessary profiling type, or <code>null</code> if none could be found.
     * @since 1.2
     */
    public static ProfileLaunchShortcut getLaunchShortcutProviderFromId(
            String id) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String shortcut = config.getAttribute("shortcut"); //$NON-NLS-1$
                if (currentId != null && shortcut != null
                        && currentId.equals(id)) {
                    try {
                        Object obj = config
                                .createExecutableExtension("shortcut"); //$NON-NLS-1$
                        if (obj instanceof ProfileLaunchShortcut) {
                            return (ProfileLaunchShortcut) obj;
                        }
                    } catch (CoreException e) {
                        // continue, perhaps another configuration will succeed
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get a launch configuration delegate that is associated with the specified id.
     * This looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code> that
     * have a specific delegate attribute.
     *
     * @param id a unique identifier
     * @return a launch configuration delegate that implements
     * <code>AbstractCLaunchDelegate</code> , or <code>null</code> if
     * none could be found.
     * @since 1.2
     */
    public static AbstractCLaunchDelegate getConfigurationDelegateFromId(
            String id) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String tabgroup = config.getAttribute("delegate"); //$NON-NLS-1$
                if (currentId != null && tabgroup != null
                        && currentId.equals(id)) {
                    try {
                        Object obj = config
                                .createExecutableExtension("delegate"); //$NON-NLS-1$
                        if (obj instanceof AbstractCLaunchDelegate) {
                            return (AbstractCLaunchDelegate) obj;
                        }
                    } catch (CoreException e) {
                        // continue, perhaps another configuration will succeed
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get id of highest priority profiling tabgroup launch configuration that
     * provides the type of profiling. This looks through extensions of the
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * extension point that have a specific type attribute.
     *
     * @param type A profiling type (eg. memory, snapshot, timing, etc.)
     * @return an id of the profiling launch shortcut that implements
     * <code>ProfileLaunchShortcut</code> and provides the necessary
     * profiling type, or <code>null</code> if none could be found.
     * @since 1.2
     */
    public static String getHighestProviderId(String type) {
        ArrayList<IConfigurationElement> list = getOrderedConfigElements(type);

        if (list.size() > 0) {
            return list.get(0).getAttribute("id"); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Get map of all pairs of names and IDs of the specific provider type. This
     * looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * that have a specific type.
     *
     * @param type A profiling type (eg. memory, snapshot, timing, etc.)
     * @return A <code>HashMap<String, String></code> of all pairs of names and IDs
     * of the specific type.
     * @since 1.2
     */
    public static HashMap<String, String> getProviderNamesForType(String type) {
        HashMap<String, String> ret = new HashMap<>();
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String currentName = config.getAttribute("name"); //$NON-NLS-1$
                String currentType = config.getAttribute("type"); //$NON-NLS-1$
                if (currentType != null && type != null
                        && currentType.equals(type) && currentName != null) {
                    ret.put(currentName, currentId);
                }
            }
        }
        return ret;
    }

    /**
     * Get map of all pairs of names and IDs of profiling providers. This
     * looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>.
     *
     * @return A <code>SortedMap<String, String></code> of all pairs of names and IDs
     * of profiling providers.
     * @since 2.0
     */
    public static SortedMap<String, String> getAllProviderNames() {
        SortedMap<String, String> ret = new TreeMap<>();
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String currentName = config.getAttribute("name"); //$NON-NLS-1$
                if (currentName != null && currentId != null) {
                    ret.put(currentName, currentId);
                }
            }
        }
        return ret;
    }

    /**
     * Get launch providers for a given type and order them with regards to highest priority first.
     *
     * @param type Type of launch providers requested.
     * @return Array of launch provider configuration elements in prioritized order
     * @since 1.2
     */
    public static ArrayList<IConfigurationElement> getOrderedConfigElements(String type) {
        IConfigurationElement[] configs = getConfigurationElements();
        ArrayList<IConfigurationElement> configList = new ArrayList<>();

        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentType = config.getAttribute("type"); //$NON-NLS-1$
                if (currentType != null && currentType.equals(type)) {

                    String priority = config.getAttribute("priority"); //$NON-NLS-1$
                    if (priority != null) {
                        try {
                            Integer.parseInt(priority);
                            configList.add(config);
                        } catch (NumberFormatException e) {
                            // continue
                        }
                    }
                }
            }
        }

		Collections.sort(configList, (c1, c2) -> {
			int p1, p2;
			// If priority is not an int or is < 0, corresponding config has
			// lowest priority.
			try {
				p1 = Integer.parseInt(c1.getAttribute("priority")); //$NON-NLS-1$
				if (p1 <= 0) {
					return 1;
				}
			} catch (NumberFormatException e1) {
				return 1;
			}
			try {
				p2 = Integer.parseInt(c2.getAttribute("priority")); //$NON-NLS-1$
				if (p2 <= 0) {
					return -1;
				}
			} catch (NumberFormatException e2) {
				return -1;
			}
			return p1 < p2 ? -1 : 1;
		});
        return configList;
    }

    /**
     * Helper method to return the list of extensions that contribute the the
     * provider framework.
     * @return All extensions that contribute to the provider framework.
     */
    private static IConfigurationElement [] getConfigurationElements () {
        IExtensionPoint extPoint = Platform.getExtensionRegistry()
                .getExtensionPoint(ProfileLaunchPlugin.PLUGIN_ID, "launchProvider"); //$NON-NLS-1$
        return extPoint.getConfigurationElements();
    }

    /**
     * Get name of tool with plug-in id <code>id</code>. This looks through
     * extensions of the
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * extensions point.
     * @param id The id attribute requested.
     * @return The name of the tool if found.
     *
     * @since 2.0
     */
    public static String getProviderToolNameFromId(String id) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                if (currentId != null && currentId.equals(id)) {
                    return config.getAttribute("name"); //$NON-NLS-1$
                }
            }
        }
        return null;
    }

    /**
     * Get content of attribute <code>attribute</code> from the launch provider
     * with id <code>toolId</code>.
     *
     * @param toolId String unique id of the tool.
     * @param attribute The attribute requested.
     * @return String description of tool.
     * @since 2.0
     */
    public static String getToolInformationFromId(String toolId,
            String attribute) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String currentToolDescription = config.getAttribute(attribute);
                if (currentId != null && currentToolDescription != null
                        && currentId.equals(toolId)) {
                    return currentToolDescription;
                }
            }
        }
        return null;
    }

    /**
     * Get a profiling tab that provides the specified type of profiling. This
     * looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code> that have a
     * specific type attribute.
     *
     * @param type A profiling type (eg. memory, snapshot, timing, etc.)
     * @return a tab that implements <code>ProfileLaunchConfigurationTabGroup</code>
     * and provides the necessary profiling type, or <code>null</code> if none could be found.
     * @since 2.0
     */
    public static ProfileLaunchConfigurationTabGroup getTabGroupProvider(String type) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentType = config.getAttribute("type"); //$NON-NLS-1$
                String shortcut = config.getAttribute("tabgroup"); //$NON-NLS-1$
                if (currentType != null && shortcut != null
                        && currentType.equals(type)) {
                    try {
                        Object obj = config
                                .createExecutableExtension("tabgroup"); //$NON-NLS-1$
                        if (obj instanceof ProfileLaunchConfigurationTabGroup) {
                            return (ProfileLaunchConfigurationTabGroup) obj;
                        }
                    } catch (CoreException e) {
                        // continue, perhaps another configuration will succeed
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get a profiling tab that is associated with the specified id.
     * This looks through extensions of the extension point
     * <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code> that have a
     * specific id.
     *
     * @param id A unique identifier
     * @return a tab that implements <code>ProfileLaunchConfigurationTabGroup</code>
     * and provides the necessary profiling type, or <code>null</code> if none could be found.
     * @since 2.0
     */
    public static ProfileLaunchConfigurationTabGroup getTabGroupProviderFromId(
            String id) {
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String tabgroup = config.getAttribute("tabgroup"); //$NON-NLS-1$
                if (currentId != null && tabgroup != null
                        && currentId.equals(id)) {
                    try {
                        Object obj = config
                                .createExecutableExtension("tabgroup"); //$NON-NLS-1$
                        if (obj instanceof ProfileLaunchConfigurationTabGroup) {
                            return (ProfileLaunchConfigurationTabGroup) obj;
                        }
                    } catch (CoreException e) {
                        // continue, perhaps another configuration will succeed
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get all IDs of the specific type. This looks through extensions of
     * the extension point <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * that have a specific type.
     *
     * @param type A profiling type (eg. memory, snapshot, timing, etc.)
     * @return A <code>String []</code> of all IDs of the specific type.
     * @since 2.0
     */
    public static String[] getProviderIdsForType(String type) {
        ArrayList<String> ret = new ArrayList<> ();
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentId = config.getAttribute("id"); //$NON-NLS-1$
                String currentType = config.getAttribute("type"); //$NON-NLS-1$
                if (currentType != null && type != null
                        && currentType.equals(type)) {
                    ret.add(currentId);
                }
            }
        }
        return ret.toArray(new String [] {});
    }

    /**
     * Get all the profiling categories. This looks through extensions of
     * the extension point <code>org.eclipse.linuxtools.profiling.launch.launchProvider</code>
     * and stores the different categories found.
     *
     * @return A <code>String []</code> of all profiling categories.
     * @since 2.0
     */
    public static String[] getProviderCategories() {
        Set<String> ret = new TreeSet<> ();
        IConfigurationElement[] configs = getConfigurationElements();
        for (IConfigurationElement config : configs) {
            if (config.getName().equals("provider")) { //$NON-NLS-1$
                String currentType = config.getAttribute("type"); //$NON-NLS-1$
                if (currentType != null) {
                    ret.add(currentType);
                }
            }
        }
        return ret.toArray(new String [] {});
    }

    /**
     * Get a provider id to run for the given profiling type.
     *
     * This first checks for a provider in the project properties if the project
     * can be found and has indicated that project preferences are to override
     * the workspace preferences.  If no project is obtainable or the project
     * has not indicated override, then it looks at provider preferences.  If these
     * are not set or the specified preference points to a non-installed provider,
     * it will look for the provider with the highest priority for the specified type.
     * If this fails, it will look for the default provider.
     *
     * @param wc The launch configuration.
     * @param type A profiling type
     * @return A provider id that contributes to the specified type
     * @since 2.0
     */

    public static String getProviderIdToRun(ILaunchConfigurationWorkingCopy wc, String type) {
        String providerId = null;
        // Look for a project first
        if (wc != null) {
            try {
                IResource[] resources = wc.getMappedResources();
                if(resources != null){
                    for (int i = 0; i < resources.length; ++i) {
                        IResource resource = resources[i];
                        if (resource instanceof IProject) {
                            IProject project = (IProject)resource;
                            ScopedPreferenceStore store = new ScopedPreferenceStore(new ProjectScope(project),
                                    ProviderProfileConstants.PLUGIN_ID);
                            boolean use_project_settings = store.getBoolean(ProviderProfileConstants.USE_PROJECT_SETTINGS + type);
                            if (use_project_settings) {
                                String provider = store.getString(ProviderProfileConstants.PREFS_KEY + type);
                                if (!provider.isEmpty())
                                    providerId = provider;
                            }
                        }
                    }
                }
            } catch (CoreException e) {
                e.printStackTrace();
            }
        }
        // if no providerId specified for project, get one from the preferences
        if (providerId == null) {
            // Look in the preferences for a provider
            providerId = ConfigurationScope.INSTANCE.getNode(
                    ProviderProfileConstants.PLUGIN_ID).get(
                            ProviderProfileConstants.PREFS_KEY + type, ""); //$NON-NLS-1$
            if (providerId.isEmpty() || getConfigurationDelegateFromId(providerId) == null) {
                // Get highest priority provider
                providerId = getHighestProviderId(type);
            }
        }
        return providerId;
    }

}
