/*******************************************************************************
 * Copyright (c) 2007, 2016 Intel Corporation and others.
 *
 * 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:
 * Intel Corporation - Initial API and implementation
 * Serge Beauchamp (Freescale Semiconductor) - Bug 406545
 *******************************************************************************/
package org.eclipse.cdt.managedbuilder.ui.properties;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.managedbuilder.core.BuildListComparator;
import org.eclipse.cdt.managedbuilder.core.IBuildObject;
import org.eclipse.cdt.managedbuilder.core.IBuilder;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IFileInfo;
import org.eclipse.cdt.managedbuilder.core.IFolderInfo;
import org.eclipse.cdt.managedbuilder.core.IResourceInfo;
import org.eclipse.cdt.managedbuilder.core.ITool;
import org.eclipse.cdt.managedbuilder.core.IToolChain;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.cdt.managedbuilder.internal.core.IRealBuildObjectAssociation;
import org.eclipse.cdt.managedbuilder.internal.core.ToolChain;
import org.eclipse.cdt.managedbuilder.internal.ui.Messages;
import org.eclipse.cdt.managedbuilder.tcmodification.CompatibilityStatus;
import org.eclipse.cdt.managedbuilder.tcmodification.IConfigurationModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IConflict;
import org.eclipse.cdt.managedbuilder.tcmodification.IFileInfoModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IFolderInfoModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IModificationOperation;
import org.eclipse.cdt.managedbuilder.tcmodification.IToolChainModificationManager;
import org.eclipse.cdt.managedbuilder.tcmodification.IToolListModification;
import org.eclipse.cdt.managedbuilder.tcmodification.IToolModification;
import org.eclipse.cdt.ui.CDTSharedImages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

/**
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class ToolChainEditTab extends AbstractCBuildPropertyTab {

	private static final IToolChainModificationManager tcmmgr = ManagedBuildManager.getToolChainModificationManager();
	private static final String NO_TC = Messages.StdProjectTypeHandler_0;
	private static final IToolChain[] r_tcs = ManagedBuildManager.getRealToolChains();
	private static final IBuilder[] r_bs = ManagedBuildManager.getRealBuilders();
	private static final ITool[] r_tools = ManagedBuildManager.getRealTools();
	private static final Color red = Display.getDefault().getSystemColor(SWT.COLOR_RED);
	private static final String SPACE = " "; //$NON-NLS-1$

	static private final Image IMG_WARNING = CDTSharedImages.getImage(CDTSharedImages.IMG_OBJS_REFACTORING_WARNING);
	static private final Image IMG_ERROR = CDTSharedImages.getImage(CDTSharedImages.IMG_OBJS_REFACTORING_ERROR);
	static private final Image IMG_INFO = CDTSharedImages.getImage(CDTSharedImages.IMG_OBJS_REFACTORING_INFO);

	private Text text;
	private Button b_dispCompatible;
	private Combo c_toolchain;
	private Combo c_builder;
	private Combo c_tool;
	private Button button_edit;
	private Group tools_group;
	private Group single_tool_group;
	private Text st_builder;
	private Text st_toolchain;
	private Label st_tool;

	private IBuilder[] v_bs;
	private IToolChain[] v_tcs;
	private ITool[] v_tools;

	private IConfiguration cfg;
	private IResourceInfo ri;
	private IToolListModification mod;

	@Override
	public void createControls(Composite parent) {
		super.createControls(parent);
		usercomp.setLayout(new GridLayout(2, false));
		b_dispCompatible = setupCheck(usercomp, Messages.ToolChainEditTab_0, 2, GridData.BEGINNING);
		b_dispCompatible.setSelection(true);

		setupLabel(usercomp, Messages.ToolChainEditTab_1, 1, GridData.BEGINNING);
		c_toolchain = new Combo(usercomp, SWT.READ_ONLY | SWT.DROP_DOWN | SWT.BORDER);
		GridData gd = new GridData(GridData.FILL_HORIZONTAL);
		gd.horizontalSpan = 1;
		c_toolchain.setLayoutData(gd);
		c_toolchain.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				modifyToolchain();
			}
		});
		c_toolchain.setEnabled(!page.isForFile());

		st_toolchain = new Text(usercomp, SWT.LEFT | SWT.READ_ONLY);
		setupControl(st_toolchain, 2, GridData.FILL_HORIZONTAL);
		st_toolchain.setForeground(red);

		setupLabel(usercomp, Messages.ToolChainEditTab_2, 1, GridData.BEGINNING);
		c_builder = new Combo(usercomp, SWT.READ_ONLY | SWT.DROP_DOWN | SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		gd.horizontalSpan = 1;
		c_builder.setLayoutData(gd);
		c_builder.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				modifyBuilder();
			}
		});
		c_builder.setEnabled(page.isForProject());

		st_builder = new Text(usercomp, SWT.LEFT | SWT.READ_ONLY);
		setupControl(st_builder, 2, GridData.FILL_HORIZONTAL);
		st_builder.setForeground(red);

		// make table for tools list
		if (page.isForFile()) {
			single_tool_group = setupGroup(usercomp, Messages.ToolChainEditTab_5, 2, GridData.FILL_BOTH);
			setupControl(single_tool_group, 2, GridData.FILL_BOTH);
			c_tool = new Combo(single_tool_group, SWT.READ_ONLY | SWT.DROP_DOWN | SWT.BORDER);
			gd = new GridData(GridData.FILL_HORIZONTAL);
			gd.horizontalSpan = 2;
			c_tool.setLayoutData(gd);
			c_tool.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					saveToolSelected();
				}
			});

			st_tool = setupLabel(single_tool_group, EMPTY_STR, 2, GridData.FILL_HORIZONTAL);
			st_tool.setForeground(red);
		} else { // Folder or Project
			tools_group = setupGroup(usercomp, Messages.ToolChainEditTab_3, 2, GridData.FILL_BOTH);
			setupControl(tools_group, 2, GridData.FILL_BOTH);

			text = new Text(tools_group,
					SWT.BORDER | SWT.WRAP | SWT.MULTI | SWT.READ_ONLY | SWT.V_SCROLL | SWT.H_SCROLL);
			gd = new GridData(GridData.FILL_BOTH);
			gd.grabExcessHorizontalSpace = true;
			text.setLayoutData(gd);

			button_edit = new Button(tools_group, SWT.PUSH);
			GridData gdb = new GridData(GridData.VERTICAL_ALIGN_CENTER);
			gdb.grabExcessHorizontalSpace = false;
			gdb.horizontalAlignment = SWT.FILL;
			gdb.widthHint = 80;
			button_edit.setLayoutData(gdb);
			button_edit.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent event) {
					modifyTools();
				}
			});

			button_edit.setLayoutData(new GridData(GridData.END));
			button_edit.setText(Messages.ToolChainEditTab_4);
		}
	}

	private IToolListModification getModification() {
		if (ri instanceof IFolderInfo)
			return tcmmgr.createModification((IFolderInfo) ri);
		else
			return tcmmgr.createModification((IFileInfo) ri);
	}

	@Override
	protected void updateData(ICResourceDescription rcfg) {
		if (page.isMultiCfg()) {
			setAllVisible(false, null);
			return;
		} else {
			setAllVisible(true, null);
		}
		cfg = getCfg(rcfg.getConfiguration());
		ri = cfg.getResourceInfo(rcfg.getPath(), false);
		mod = getModification();
		updateData();
	}

	/**
	 * @since 8.2
	 */
	@Override
	public boolean canSupportMultiCfg() {
		return false;
	}

	private void updateData() {
		showErrorMessage();
		fillToolChainCombo();
		fillBuilderCombo();
		fillToolsList();
	}

	private void showErrorMessage() {
		if (ri instanceof IFolderInfo) {
			IFolderInfoModification foim = (IFolderInfoModification) mod;
			IToolChain toolChain = ((IFolderInfoModification) mod).getToolChain();
			IToolChain realToolChain = ManagedBuildManager.getRealToolChain(toolChain);
			if (realToolChain == null) {
				// The toolchain is not derived from extension
				String errText = NLS.bind(Messages.ToolChainEditTab_OrphanedToolchain, toolChain.getId(),
						toolChain.getName());
				st_toolchain.setText(errText);
			} else if (foim.isToolChainCompatible()) {
				st_toolchain.setText(EMPTY_STR);
			} else {
				String s = foim.getToolChainCompatibilityStatus().getMessage();
				st_toolchain.setText(s);
			}
			st_builder.setText(EMPTY_STR);
			if (foim instanceof IConfigurationModification) {
				IConfigurationModification cm = (IConfigurationModification) foim;
				if (!cm.isBuilderCompatible()) {
					IBuilder builder = cfg.getBuilder();
					IBuilder realBuilder = ManagedBuildManager.getRealBuilder(builder);
					if (realBuilder == null) {
						// The builder is not derived from extension
						String errText = NLS.bind(Messages.ToolChainEditTab_OrphanedBuilder, builder.getId(),
								builder.getName());
						st_builder.setText(errText);
					} else {
						CompatibilityStatus cs = cm.getBuilderCompatibilityStatus();
						if (cs != null) {
							String s = cs.getMessage();
							st_builder.setText(s);
						}
					}
				}
			}
			//		} else { // FileInfo
			//			IFileInfoModification fim = (IFileInfoModification)mod;
			//			fim.getProjectToolModifications();
		}
		st_toolchain.setEnabled(!EMPTY_STR.equals(st_toolchain.getText()));
		st_builder.setEnabled(!EMPTY_STR.equals(st_builder.getText()));
	}

	private void fillToolChainCombo() {
		IToolChain realToolChain = null;
		if (ri instanceof IFolderInfo) {
			IToolChain toolChainInstance = ((IFolderInfoModification) mod).getToolChain();
			realToolChain = ManagedBuildManager.getRealToolChain(toolChainInstance);
			if (realToolChain == null)
				realToolChain = toolChainInstance;
		}

		c_toolchain.removeAll();
		boolean isMng = cfg.getBuilder().isManagedBuildOn();
		ArrayList<IToolChain> list = new ArrayList<>();

		IToolChain[] tcs = r_tcs;
		if (b_dispCompatible.getSelection() && (ri instanceof IFolderInfo)) {
			IFolderInfoModification fim = (IFolderInfoModification) mod;
			tcs = fim.getCompatibleToolChains();
			IToolChain[] tcs1 = new IToolChain[tcs.length + 1];
			System.arraycopy(tcs, 0, tcs1, 0, tcs.length);
			tcs1[tcs.length] = realToolChain; // add existing toolchain
			tcs = tcs1;
		}
		for (IToolChain tc : tcs) {
			if (tc.isSystemObject() && !(((ToolChain) tc).isPreferenceToolChain() && !isMng)) {
				// NO TOOLCHAIN
				continue;
			}
			list.add(tc);
		}
		Collections.sort(list, BuildListComparator.getInstance());

		int pos = -1;
		v_tcs = list.toArray(new IToolChain[list.size()]);
		for (int i = 0; i < v_tcs.length; i++) {
			c_toolchain.add(v_tcs[i].getUniqueRealName());
			if (v_tcs[i].matches(realToolChain))
				pos = i;
		}

		if (pos != -1) {
			c_toolchain.select(pos);
			c_builder.setEnabled(page.isForProject());
		} else {
			c_toolchain.setText(EMPTY_STR); // unprobable case
			c_builder.setEnabled(false);
		}
	}

	private void fillBuilderCombo() {
		IBuilder realBuilder = ManagedBuildManager.getRealBuilder(cfg.getBuilder());
		if (realBuilder == null)
			realBuilder = cfg.getBuilder();
		int pos = -1;
		c_builder.removeAll();
		ArrayList<IBuilder> list = new ArrayList<>();

		IBuilder[] bs = r_bs;

		if (b_dispCompatible.getSelection() && (ri instanceof IFolderInfo)) {
			IFolderInfoModification fim = (IFolderInfoModification) mod;
			if (fim instanceof IConfigurationModification) {
				bs = ((IConfigurationModification) fim).getCompatibleBuilders();
				IBuilder[] bs1 = new IBuilder[bs.length + 1];
				System.arraycopy(bs, 0, bs1, 0, bs.length);
				bs1[bs.length] = realBuilder;
				bs = bs1;
			}
		}
		for (int i = 0; i < bs.length; i++) {
			if (bs[i].isSystemObject() && !bs[i].equals(realBuilder))
				continue;
			list.add(bs[i]);
		}
		bs = null;
		Collections.sort(list, BuildListComparator.getInstance());
		v_bs = list.toArray(new IBuilder[list.size()]);
		for (int i = 0; i < v_bs.length; i++) {
			c_builder.add(v_bs[i].getUniqueRealName());
			if (v_bs[i].matches(realBuilder))
				pos = i;
		}
		if (pos != -1)
			c_builder.select(pos);
		else
			c_builder.setText(EMPTY_STR);

	}

	private ITool getToolForFile() {
		ITool[] tools = ((IFileInfoModification) mod).getProjectTools();
		if (tools != null && tools.length > 0) {
			for (int i = 0; i < tools.length; i++) {
				if (tools[i] != null && !tools[i].getCustomBuildStep()) {
					return tools[i];
				}
			}
		}
		return null;
	}

	private void fillToolCombo(boolean add, ITool curr) {
		c_tool.removeAll();
		int pos = (curr == null) ? v_tools.length : -1;
		for (int i = 0; i < v_tools.length; i++) {
			if (pos == -1 && curr.matches(v_tools[i])) {
				pos = i;
				c_tool.add(curr.getUniqueRealName());
			} else {
				c_tool.add(v_tools[i].getUniqueRealName());
			}
		}
		// Add NO_TOOL
		if (add) {
			c_tool.add(Messages.ToolChainEditTab_6);
		}
		c_tool.select(pos);
	}

	private void fillToolsList() {
		updateAllTools(); // modifies v_tools inside !!!

		ToolChain tc = null;
		if (ri instanceof IFolderInfo)
			tc = (ToolChain) ManagedBuildManager.getRealToolChain(((IFolderInfo) ri).getToolChain());

		if (page.isForFile()) { // Edit tool in combo for File
			ITool curr = getToolForFile();
			boolean canAddNO_TOOL = true;
			if (ri instanceof IFileInfo && b_dispCompatible.getSelection())
				canAddNO_TOOL = updateCompatibleTools(curr); // modifies v_tools inside !!!
			fillToolCombo(canAddNO_TOOL, curr);
			showToolStatus(curr);
		} else if (tc != null && tc.isPreferenceToolChain()) { // display tools list for Folder and Project
			tools_group.setVisible(false);
		} else {
			tools_group.setVisible(true);
			String s = EMPTY_STR;
			ITool[] tools = ri.getTools();
			for (ITool tool : tools) {
				s = s + tool.getUniqueRealName() + "\n"; //$NON-NLS-1$
			}
			text.setText(s);
		}
	}

	private void updateAllTools() {
		int cnt = 0;
		v_tools = new ITool[r_tools.length];
		for (ITool r_tool : r_tools) {
			if (r_tool.isSystemObject())
				continue;
			if (r_tool.isAbstract())
				continue;
			v_tools[cnt++] = r_tool;
		}
		ITool[] tmp = new ITool[cnt];
		System.arraycopy(v_tools, 0, tmp, 0, cnt);
		Arrays.sort(tmp, BuildListComparator.getInstance());
		v_tools = tmp;
	}

	private void showToolStatus(ITool tool) {
		st_tool.setText(EMPTY_STR);
		st_tool.setImage(null);
		if (tool == null)
			return;
		IFileInfoModification fim = (IFileInfoModification) mod;
		IToolModification tm = fim.getToolModification(tool);
		if (tm != null && !tm.isCompatible()) {
			CompatibilityStatus cs = tm.getCompatibilityStatus();
			if (cs != null) {
				st_tool.setText(cs.getMessage());
				st_tool.setImage(getErrorIcon(cs));
			}
		}
	}

	private boolean updateCompatibleTools(ITool real) {
		boolean result = false;
		ArrayList<ITool> list = new ArrayList<>();
		IFileInfoModification fim = (IFileInfoModification) mod;

		if (real != null) { // Current tool exists
			real = ManagedBuildManager.getRealTool(real);
			list.add(real);
			IToolModification tm = fim.getToolModification(real);
			IModificationOperation[] mos = tm.getSupportedOperations();
			for (IModificationOperation mo : mos) {
				ITool t = mo.getReplacementTool();
				if (t == null)
					result = true;
				else {
					if (!t.isSystemObject() && !t.isAbstract())
						list.add(t);
				}
			}
		} else { // Current tool is NO_TOOL
			result = true;
			IToolModification[] tms = fim.getSystemToolModifications();
			for (IToolModification tm : tms) {
				IModificationOperation[] mos = tm.getSupportedOperations();
				for (IModificationOperation mo : mos) {
					if (mo.getReplacementTool() == null) {
						ITool t = tm.getTool();
						if (!t.isSystemObject() && !t.isAbstract())
							list.add(t);
						break;
					}
				}
			}
		}
		Collections.sort(list, BuildListComparator.getInstance());
		v_tools = list.toArray(new ITool[list.size()]);
		return result;
	}

	@Override
	protected void checkPressed(SelectionEvent e) {
		updateData();
	}

	@Override
	protected void performApply(ICResourceDescription src, ICResourceDescription dst) {
		if (mod == null)
			return;
		IConfiguration cfg = getCfg(dst.getConfiguration());
		try {
			IToolListModification tlm = (ri instanceof IFolderInfo)
					? (IToolListModification) tcmmgr.createModification(cfg, (IFolderInfoModification) mod)
					: (IToolListModification) tcmmgr.createModification(cfg, (IFileInfoModification) mod);
			tlm.apply();
		} catch (CoreException e) {
			ManagedBuilderUIPlugin.log(e);
		}
	}

	@Override
	protected void performDefaults() {
		if (mod != null) {
			mod.restoreDefaults();
			apply();
			updateData();
		}
	}

	@Override
	protected void updateButtons() {
	} // Do nothing. No buttons to update.

	private void saveToolSelected() {

		IFileInfoModification fim = (IFileInfoModification) mod;

		// Find old tool
		ITool oldTool = null;
		ITool[] ots = fim.getProjectTools();
		for (int i = 0; i < ots.length; i++) {
			if (!ots[i].getCustomBuildStep()) {
				oldTool = ots[i];
				break;
			}
		}
		// Find new Tool
		ITool newTool = null;
		int pos = c_tool.getSelectionIndex();
		if (pos < (c_tool.getItemCount() - 1))
			newTool = v_tools[pos];

		// May be we've seleceted the same tool
		if (newTool == null && oldTool == null)
			return; // nothing to change
		if (newTool != null && newTool.matches(oldTool))
			return; // nothing to change

		// Apply changes
		fim.changeProjectTools(oldTool, newTool);
		apply();
		updateData();
	}

	/**
	 * Opens Tools selection dialog.
	 * Depending of result (OK/Cancel)
	 * either applies tools changes,
	 * or re-creates modification to abandon changes
	 */
	private void modifyTools() {

		ToolSelectionDialog d = new ToolSelectionDialog(usercomp.getShell(), ri);
		d.all = v_tools;
		d.fi = (IFolderInfo) ri;
		d.mod = mod;
		int result = d.open();
		if (d.removed.size() > 0 || d.added.size() > 0) {
			if (result == 0) {
				apply();
				updateData();
			} else {
				mod = getModification(); // re-read data
			}
		}
	}

	/**
	 * Applies changes to Modification
	 */
	private void apply() {
		try {
			mod.apply();
		} catch (CoreException e) {
			ManagedBuilderUIPlugin.log(e);
		}
	}

	private void modifyToolchain() {
		IFolderInfoModification foim = (IFolderInfoModification) mod;
		int x = c_toolchain.getSelectionIndex();
		if (NO_TC.equals(c_toolchain.getItem(x))) {
			foim.setToolChain(null);
		} else {
			IToolChain tc = v_tcs[x];
			if (tc == null)
				return;
			foim.setToolChain(tc);
		}
		apply();
		updateData();
	}

	public static Image getErrorIcon(IStatus st) {
		if (st.isOK())
			return null;
		int sev = st.getSeverity();
		if (sev == IStatus.ERROR)
			return IMG_ERROR;
		else if (sev == IStatus.WARNING)
			return IMG_WARNING;
		else
			return IMG_INFO;
	}

	private void modifyBuilder() {
		IConfigurationModification cm = (IConfigurationModification) mod;
		cm.setBuilder(v_bs[c_builder.getSelectionIndex()]);
		apply();
		updateData();
	}

	/**
	 * Forms a message containing
	 * @param cs
	 * @return
	 */
	public static String getCompatibilityMessage(CompatibilityStatus cs) {
		IConflict[] cons = cs.getConflicts();
		StringBuilder result = new StringBuilder();
		for (int i = 0; i < cons.length; i++) {
			IBuildObject bo = cons[i].getBuildObject();
			String n = (bo == null) ? "NULL" : //$NON-NLS-1$
					(bo instanceof ITool) ? ((ITool) bo).getUniqueRealName() : bo.getName();
			String t = EMPTY_STR;
			switch (cons[i].getConflictType()) {
			case IConflict.INCOMPATIBLE:
				t = Messages.ToolChainEditTab_7;
				break;
			case IConflict.SOURCE_EXT_CONFLICT:
				t = Messages.ToolChainEditTab_8;
				break;
			}

			String o = EMPTY_STR;
			switch (cons[i].getObjectType()) {
			case IRealBuildObjectAssociation.OBJECT_TOOLCHAIN:
				o = Messages.ToolChainEditTab_9;
				break;
			case IRealBuildObjectAssociation.OBJECT_BUILDER:
				o = Messages.ToolChainEditTab_10;
				break;
			case IRealBuildObjectAssociation.OBJECT_CONFIGURATION:
				o = Messages.ToolChainEditTab_11;
				break;
			case IRealBuildObjectAssociation.OBJECT_FILE_INFO:
				o = Messages.ToolChainEditTab_12;
				break;
			case IRealBuildObjectAssociation.OBJECT_FOLDER_INFO:
				o = Messages.ToolChainEditTab_13;
				break;
			case IRealBuildObjectAssociation.OBJECT_TOOL:
				o = Messages.ToolChainEditTab_14;
				break;
			}

			result.append(Messages.ToolChainEditTab_15).append(i + 1).append(Messages.ToolChainEditTab_16).append(SPACE)
					.append(t).append(SPACE).append(o).append(SPACE).append(n).append(Messages.ToolChainEditTab_17);
		}
		String s = result.toString();
		if (s.trim().length() == 0)
			s = cs.getMessage();
		if (s == null)
			s = EMPTY_STR;
		return s;
	}
}
