/*******************************************************************************
 * Copyright (c) 2013, 2018 Red Hat, Inc and others.
 * 
 * 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;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import org.eclipse.linuxtools.internal.profiling.launch.provider.ProviderProfileConstants;
import org.eclipse.linuxtools.internal.profiling.launch.provider.launch.ProviderFramework;
import org.eclipse.linuxtools.internal.profiling.launch.provider.launch.ProviderLaunchShortcut;
import org.eclipse.linuxtools.profiling.launch.ProfileLaunchConfigurationTabGroup;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

/**
 * Shared class for displaying profiling options in a single tab.
 *
 * @since 2.0
 */
public abstract class AbstractProfilingOptionsTab extends AbstractLaunchConfigurationTab {

    private String type;
    private String name;
    private String id;
    private Composite top;
    private Combo providerCombo;
    private AbstractLaunchConfigurationTab[] tabs;
    private ILaunchConfiguration initial;
    private Map<String, String> comboItems;
    private CTabFolder tabgroup;
    protected Image img;

    // if tabs are being initialized do not call performApply()
    private Map<String, Boolean> initialized = new HashMap<> ();

    /**
     * Get list of profiling providers for the user to choose from.
     *
     * @return Map of provider ids and provider tool names
     */
    protected abstract Map<String, String> getProviders();

    @Override
    public void createControl(Composite parent) {
        top = new Composite(parent, SWT.NONE);
        setControl(top);
        top.setLayout(new GridLayout(1, true));

        comboItems = getProviders();
        Set<String> providerNames = comboItems.keySet();

        providerCombo = new Combo(top, SWT.READ_ONLY);
        providerCombo.setItems(providerNames.toArray(new String[0]));
        if (providerNames.size() == 0) {
            providerCombo.setVisible(false);
            providerCombo.setEnabled(false);
            Label label = new Label(top, SWT.NULL);
            label.setText(Messages.ProfilingTab_no_category_profilers_installed);
        }

        tabgroup = new CTabFolder(top, SWT.NONE);
        tabgroup.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true,
                true));

		providerCombo.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
			String curProviderId = comboItems.get(providerCombo.getText());
			loadTabGroupItems(tabgroup, curProviderId);
			initializeFrom(initial);
			// Since we are calling initializeFrom manually, we have to
			// update the launch configuration dialog manually to ensure
			// initial validation on the configuration.
			updateLaunchConfigurationDialog();
			top.layout();
		}));
    }

    /**
     * Get the default provider id to use if one is not set for the configuration.
     *
     * @return default provider id
     */
    protected abstract String getDefaultProviderId();

    private void loadTabGroupItems(CTabFolder tabgroup, String curProviderId) {
        // dispose of old tabs and their state
        for (CTabItem item : tabgroup.getItems()) {
            item.dispose();
        }
        setErrorMessage(null);
        initialized.clear();

        ProfileLaunchConfigurationTabGroup tabGroupConfig;

        if (curProviderId == null || curProviderId.isEmpty()) {
            curProviderId = getDefaultProviderId();
        }

        // starting initialization of this tab's controls
        initialized.put(curProviderId, false);

        tabGroupConfig = ProviderFramework.getTabGroupProviderFromId(curProviderId);
        if (tabGroupConfig == null) {
            String profilingToolName = null;
            try {
                profilingToolName = initial.getAttribute(ProviderProfileConstants.PROVIDER_CONFIG_TOOLNAME_ATT, (String)null);
            } catch (CoreException e) {
                // do nothing
            }
            if (profilingToolName == null) {
                setErrorMessage(NLS.bind(Messages.ProfilingTab_specified_providerid_not_installed, curProviderId));
            } else {
                setErrorMessage(NLS.bind(Messages.ProfilingTab_specified_profiler_not_installed, profilingToolName));
            }
            return;
        }
        tabs = tabGroupConfig.getProfileTabs();
        setProvider(curProviderId);

        // Show provider name in combo.
        int itemIndex = getComboItemIndexFromId(curProviderId);
        providerCombo.select(itemIndex);

        // Set name of configuration.
        setConfigurationName(providerCombo.getText());

        // create the tab item, and load the specified tab inside
        for (ILaunchConfigurationTab tab : tabs) {
            tab.setLaunchConfigurationDialog(getLaunchConfigurationDialog());
            CTabItem item = new CTabItem(tabgroup, SWT.NONE);
            item.setText(tab.getName());
            item.setImage(tab.getImage());

            tab.createControl(tabgroup);
            item.setControl(tab.getControl());
            tabgroup.setSelection(0);
        }
    }

    @Override
    public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
        if (providerCombo != null && !providerCombo.getText().isEmpty()) {
            for (AbstractLaunchConfigurationTab tab : tabs) {
                tab.setDefaults(configuration);
            }
        }
    }

    @Override
    public void initializeFrom(ILaunchConfiguration configuration) {
        /**
         * First time the configuration is selected.
         *
         * This is a cheap way to get access to the launch configuration. Our
         * tabs are loaded dynamically, so the tab group doesn't "know" about
         * them. We get access to this launch configuration to ensure that we
         * can properly load the widgets the first time.
         */

        // update current configuration (initial) with configuration being
        // passed in
        initial = configuration;


        // check if there exists a launch provider id in the configuration
        if (initial != null) {
            try {
                String providerId = initial.getAttribute(
                        ProviderProfileConstants.PROVIDER_CONFIG_ATT, (String)null);
                // load provider corresponding to specified ids
                loadTabGroupItems(tabgroup, providerId);
            } catch (CoreException e) {
                // continue, initialize tabs
            }
        }
        if (tabs != null) {
            for (AbstractLaunchConfigurationTab tab : tabs) {
                tab.initializeFrom(configuration);
            }
        }

        // finished initialization
        initialized.put(getProviderId(), true);
    }

    @Override
    public void performApply(ILaunchConfigurationWorkingCopy configuration) {
        // make sure tabs are not null, and the tab's controls have been
        // initialized.

        Boolean isInitialized = initialized.get(getProviderId());
        isInitialized = (isInitialized != null) ? isInitialized : false;

        if (tabs != null && isInitialized) {
            for (AbstractLaunchConfigurationTab tab : tabs) {
                tab.performApply(configuration);
            }
        }
    }

    /**
     * Set the provider attribute in the specified configuration.
     * @param providerId The new provider id.
     */
    private void setProvider(String providerId) {
        try {
            ILaunchConfigurationWorkingCopy wc = initial.getWorkingCopy();
            wc.setAttribute(ProviderProfileConstants.PROVIDER_CONFIG_ATT,
                    providerId);
            initial = wc.doSave();
        } catch (CoreException e1) {
            e1.printStackTrace();
        }
    }

    /**
     * Set name of the launch configuration.
     *
     * @param newToolName String tool name to be appended to configuration name,
     */
    protected void setConfigurationName(String newToolName) {
        try {
            String currentToolName = initial.getAttribute(
                    ProviderProfileConstants.PROVIDER_CONFIG_TOOLNAME_ATT, ""); //$NON-NLS-1$

            // Append the new tool name as long as the current and new tool
            // names are different.
            if (newToolName != null && !newToolName.isEmpty()
                    && !currentToolName.equals(newToolName)) {

                String projectName = initial.getAttribute(
                        ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); //$NON-NLS-1$

                // String of the form <project name> [<tool name>].
                String newConfigurationName = ProviderLaunchShortcut
                        .generateProviderConfigurationName(projectName,
                                newToolName);

                // Unique name of the form <project name> [<tool name>]{(<number>)}.
                String newUniqueToolName = getLaunchManager()
                        .generateLaunchConfigurationName(newConfigurationName);

                // Save changes in current configuration.
                ILaunchConfigurationWorkingCopy wc = initial.getWorkingCopy();
                wc.rename(newUniqueToolName);
                wc.setAttribute(
                        ProviderProfileConstants.PROVIDER_CONFIG_TOOLNAME_ATT,
                        newToolName);
                initial = wc.doSave();

                // Set name field in launch configuration dialog to avoid the
                // new configuration name from being overwritten.
                getLaunchConfigurationDialog().setName(newUniqueToolName);
            }
        } catch (CoreException e) {
            // If unable to set the name, leave the original name as is.
        }
    }

    /**
     * Get the provider ID for the provider of the currently loaded
     * configuration.
     *
     * @return the provider ID or an empty string if the configuration
     * has no provider ID defined.
     */
    private String getProviderId() {
        try {
            return initial.getAttribute(
                    ProviderProfileConstants.PROVIDER_CONFIG_ATT, ""); //$NON-NLS-1$
        } catch (CoreException e) {
            return ""; //$NON-NLS-1$
        }
    }

    /**
     * Get Combo item name from specified id
     *
     * @param id provider id
     * @return name of item, <code>null</code> if no entry found with given id.
     */
    private String getComboItemNameFromId(String id) {
        for (Entry<String, String> entry : comboItems.entrySet()) {
            if (id.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        return null;
    }

    /**
     * Get index of specific name in the combo items list.
     *
     * @param name Name of item
     * @return Index of given name, -1 if it not found.
     */
    private int getItemIndex(String name) {
        int itemCount = providerCombo.getItemCount();
        for (int i = 0; i < itemCount; i++) {
            if (providerCombo.getItem(i).equals(name)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Get index of specific id in the provider combo items list
     *
     * @param id Combo item id.
     * @return index of given id in provider combo items list, -1 if it not found.
     */
    private int getComboItemIndexFromId(String id) {
        String providerName = getComboItemNameFromId(id);
        return getItemIndex(providerName);
    }

    @Override
    public boolean isValid(ILaunchConfiguration config) {
        String provider;
        try {
            provider = config.getAttribute(
                    ProviderProfileConstants.PROVIDER_CONFIG_ATT, ""); //$NON-NLS-1$
        } catch (CoreException e) {
            setErrorMessage(e.getMessage());
            return false;
        }
        if (provider.isEmpty()) {
            setErrorMessage(Messages.ProfilingTab_providerid_not_found);
            return false;
        }

        Boolean isInitialized = initialized.get(getProviderId());

        if (isInitialized) {
            // Tabs should not be null after initialization.
            if (tabs == null) {
                return false;
            }

            // Validate tab configurations of underlying tool.
            for (AbstractLaunchConfigurationTab tab : tabs) {
                if (!tab.isValid(config)) {
                    setErrorMessage(tab.getErrorMessage());
                    return false;
                }
            }
        } else {
            if (tabs != null) {
                // even though not initialized, tabs should exist
                // use their state to determine whether config is valid
                for (AbstractLaunchConfigurationTab tab : tabs) {
                    if (!tab.isValid(config)) {
                        return false;
                    }
                }
            }
        }

        setErrorMessage(null);
        return true;
    }

    /**
     * Get name of profiling type that used for this tab.
     *
     * @return String profiling name.
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Set the name for this tab.
     *
     * @param name New tab name.
     */
    protected void setName(String name) {
        this.name = name;
    }

    @Override
    public String getId() {
        return id;
    }

    /**
     * Set the id for this tab.
     *
     * @param id New id of the tab.
     */
    protected void setId(String id) {
        this.id = id;
    }

    /**
     * Get profiling type of the configuration.
     *
     * @return String profiling type this plug-in supports.
     */
    protected String getProfilingType() {
        return type;
    }

    /**
     * Set profiling type of configuration.
     *
     * @param type New profiling type.
     */
    protected void setProfilingType(String type) {
        this.type = type;
    }

    @Override
    public Image getImage() {
        return img;
    }

    /**
     * Set the image for this tab.
     *
     * @param img New image.
     */
    public void setImage(Image img) {
        this.img = img;
    }

    @Override
    public void dispose() {
        if (img != null) {
            img.dispose();
        }
        super.dispose();
    }
}

