/*******************************************************************************
 * Copyright (c) 2015 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bruno Medeiros - initial API and implementation
 *******************************************************************************/
package melnorme.lang.ide.ui.navigator;

import static java.text.MessageFormat.format;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.CoreUtil.list;
import static melnorme.utilbox.core.fntypes.CommonGetter.getOrNull;

import java.text.MessageFormat;

import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.navigator.ICommonActionConstants;

import melnorme.lang.ide.core.launch.BuildTargetLaunchCreator;
import melnorme.lang.ide.core.operations.build.BuildManager;
import melnorme.lang.ide.core.operations.build.BuildManagerMessages;
import melnorme.lang.ide.core.operations.build.BuildTarget;
import melnorme.lang.ide.core.operations.build.BuildTargetData;
import melnorme.lang.ide.core.operations.build.ProjectBuildInfo;
import melnorme.lang.ide.ui.LangUIPlugin_Actual;
import melnorme.lang.ide.ui.launch.LangLaunchShortcut;
import melnorme.lang.ide.ui.launch.LangLaunchShortcut.BuildTargetLaunchable;
import melnorme.lang.ide.ui.navigator.LangNavigatorActionProvider.ViewPartActionGroup;
import melnorme.lang.ide.ui.utils.BuildUtilities;
import melnorme.lang.ide.ui.utils.UIOperationsStatusHandler;
import melnorme.lang.ide.ui.utils.operations.JobUIOperation;
import melnorme.lang.tooling.bundle.LaunchArtifact;
import melnorme.lang.tooling.common.ops.IOperationMonitor;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.collections.Collection2;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.concurrency.OperationCancellation;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.ownership.Disposable;
import melnorme.utilbox.status.Severity;
import melnorme.utilbox.status.StatusException;

public abstract class BuildTargetsActionGroup extends ViewPartActionGroup {
	
	public BuildTargetsActionGroup(IViewPart viewPart) {
		super(viewPart);
	}
	
	@Override
	public void fillContextMenu(IMenuManager menu) {
		Object firstSel = getSelectionFirstElement();
		
		if(firstSel instanceof BuildTargetsContainer) {
			BuildTargetsContainer buildTargetsContainer = (BuildTargetsContainer) firstSel;
			
			menu.add(new BuildAllTargetsAction(buildTargetsContainer));
			menu.add(new BuildEnabledTargetsAction(buildTargetsContainer));
			menu.add(new Separator("additions"));
			menu.add(new ConfigureBuildTargetAction(buildTargetsContainer));
		}
		
		if(firstSel instanceof BuildTargetElement) {
			BuildTargetElement buildTargetElement = (BuildTargetElement) firstSel;
			
			menu.add(new BuildSingleTargetAction(buildTargetElement));
			
			addLaunchActions(menu, buildTargetElement);
			
			menu.add(new Separator("configure_BuildTarget"));
			menu.add(new ToggleEnabledAction(buildTargetElement));
			menu.add(new ToggleAutoBuildEnabledAction(buildTargetElement));
			menu.add(new ConfigureBuildTargetAction(buildTargetElement));
		}
		
	}
	
	protected void addLaunchActions(IMenuManager menu, BuildTargetElement buildTargetElement) {
		
		BuildTarget buildTarget = buildTargetElement.getBuildTarget();
		
		LaunchArtifact mainLaunchArtifact = getOrNull(buildTarget::getMainLaunchArtifact);
		Indexable<LaunchArtifact> launchSubArtifacts = getOrNull(buildTarget::getSubLaunchArtifacts);
		
		String suggestedLaunchSuffix = buildTargetElement.getTargetDisplayName();
		if(buildTarget.isDefaultBuildType()) {
			suggestedLaunchSuffix = null;
		}
		
		LaunchBuildTargetAction runTargetAction = new LaunchBuildTargetAction(buildTargetElement, true, null, 
			suggestedLaunchSuffix, 
			MessageFormat.format("Run {0}", buildTargetElement.getTargetDisplayName()));
		LaunchBuildTargetAction debugTargetAction = new LaunchBuildTargetAction(buildTargetElement, false, null, 
			suggestedLaunchSuffix, 
			MessageFormat.format("Debug {0}", buildTargetElement.getTargetDisplayName()));
		
		
		if(launchSubArtifacts == null) {
			if(mainLaunchArtifact != null) {
				menu.add(runTargetAction);
				menu.add(debugTargetAction);
			}
			
			return;
		}
		
		MenuManager runSubTargetsMenu = new MenuManager("Run ");
		MenuManager debugSubTargetsMenu = new MenuManager("Debug ");
		
		if(mainLaunchArtifact != null) {
			addMainArtifactAction(buildTarget, 
				runTargetAction, debugTargetAction, 
				runSubTargetsMenu, debugSubTargetsMenu);
		}
		
		for(LaunchArtifact launchArtifact : launchSubArtifacts) {
			
			String launchName = createLaunchShortcut().getLaunchNameForSubTarget(launchArtifact.getName());
			String artifactPath = launchArtifact.getArtifactPath();
			
			runSubTargetsMenu.add(new LaunchBuildTargetAction(buildTargetElement, true, artifactPath, launchName, 
				"Run " + launchName + " - " + artifactPath + "")); 
			debugSubTargetsMenu.add(new LaunchBuildTargetAction(buildTargetElement, false, artifactPath, launchName, 
				"Debug " + launchName + " - " + artifactPath + ""));
		}
			
		menu.add(runSubTargetsMenu);
		menu.add(debugSubTargetsMenu);
	}
	
	protected void addMainArtifactAction(
			BuildTarget buildTarget, 
			LaunchBuildTargetAction runTargetAction,
			LaunchBuildTargetAction debugTargetAction, 
			MenuManager runSubTargetsMenu, 
			MenuManager debugSubTargetsMenu) 
	{
		if(buildTarget.isDefaultBuildType()) {
			runTargetAction.setText("Run default executable");
			debugTargetAction.setText("Debug default executable");
		}
		
		runSubTargetsMenu.add(runTargetAction);
		runSubTargetsMenu.add(new Separator());
		debugSubTargetsMenu.add(debugTargetAction);
		debugSubTargetsMenu.add(new Separator());
	}
	
	@Override
	public void fillActionBars(IActionBars actionBars) {
		Object firstSel = getSelectionFirstElement();
		
		if(firstSel instanceof BuildTargetElement) {
			BuildTargetElement buildTargetElement = (BuildTargetElement) firstSel;
			
			fillActionBars(actionBars, buildTargetElement);
		}
	}
	
	protected void fillActionBars(IActionBars actionBars, BuildTargetElement buildTargetElement) {
		actionBars.setGlobalActionHandler(ICommonActionConstants.OPEN, new BuildSingleTargetAction(buildTargetElement));
	}
	
	public static abstract class AbstractBuildElementAction extends Action {
		
		protected final BuildManager buildManager;
		protected final IProject project;
		protected final String opName;
		
		public AbstractBuildElementAction(BuildTargetsContainer buildTargetContainer, String opName) {
			this(buildTargetContainer, opName, Action.AS_UNSPECIFIED);
		}
		
		public AbstractBuildElementAction(BuildTargetsContainer buildTargetContainer, String opName, int style) {
			this(buildTargetContainer.getBuildManager(), buildTargetContainer.getProject(), opName, style);
		}
		
		public AbstractBuildElementAction(BuildManager buildManager, IProject project, String opName, int style) {
			super(opName, style);
			this.buildManager = assertNotNull(buildManager);
			this.project = assertNotNull(project);
			this.opName = opName;
		}
		
		public IProject getProject() {
			return project;
		}
		
		public BuildManager getBuildManager() {
			return buildManager;
		}
		
		public ProjectBuildInfo getBuildInfo() {
			return getBuildManager().getBuildInfo(getProject());
		}
		
		@Override
		public void run() {
			try {
				doRun();
			} catch(StatusException se) {
				UIOperationsStatusHandler.handleStatus(getStatusDialogTitle(), se);
			}
		}
		
		protected String getStatusDialogTitle() {
			return "Error during: " + getText();
		}
		
		public void doRun() throws StatusException {
			
			try(Disposable reg = buildManager.autoBuildsEnablement().enterDisable()) {
				// Note: this needs to run in UI
				BuildUtilities.saveEditors(list(getProject()));
			}
			
			new JobUIOperation(getJobTitle(), this::doJobRun).execute();
		}
		
		protected String getJobTitle() {
			return opName;
		}
		
		@SuppressWarnings("unused") 
		protected void doJobRun(IOperationMonitor om) throws CommonException, OperationCancellation {
		}
		
	}
	
	public static class BuildAllTargetsAction extends AbstractBuildElementAction {
		
		public BuildAllTargetsAction(BuildTargetsContainer buildTargetContainer) {
			super(buildTargetContainer, BuildManagerMessages.NAME_BuildAllTargetsAction);
		}
		
		@Override
		protected void doJobRun(IOperationMonitor om) throws CommonException, OperationCancellation {
			Collection2<BuildTarget> enabledTargets = getBuildInfo().getBuildTargets();
			getBuildManager().executeBuildTargetsOperation(om, getProject(), enabledTargets);
		}
	}
	
	public static class BuildEnabledTargetsAction extends AbstractBuildElementAction {
		
		public BuildEnabledTargetsAction(BuildTargetsContainer buildTargetContainer) {
			super(buildTargetContainer, BuildManagerMessages.NAME_BuildEnabledTargetsAction);
		}
		
		@Override
		protected void doJobRun(IOperationMonitor om) throws CommonException, OperationCancellation {
			ArrayList2<BuildTarget> enabledTargets = getBuildInfo().getEnabledTargets(true);
			getBuildManager().executeBuildTargetsOperation(om, getProject(), enabledTargets);
		}
	}
	
	/* -----------------  ----------------- */
	
	public static abstract class AbstractBuildTargetAction extends AbstractBuildElementAction {
		
		protected final BuildTargetElement buildTargetElement;
		protected final BuildTarget buildTarget;
		
		public AbstractBuildTargetAction(BuildTargetElement buildTargetElement, String text) {
			this(buildTargetElement, text, Action.AS_UNSPECIFIED);
		}
		
		public AbstractBuildTargetAction(BuildTargetElement buildTargetElement, String text, int style) {
			super(buildTargetElement.getBuildManager(), buildTargetElement.project, text, style);
			this.buildTargetElement = assertNotNull(buildTargetElement);
			this.buildTarget = buildTargetElement.getBuildTarget();
		}
		
		protected String getBuildTargetName() {
			return buildTarget.getBuildTargetName();
		}
		
	}
	
	public static class ToggleEnabledAction extends AbstractBuildTargetAction {
		
		public ToggleEnabledAction(BuildTargetElement buildTargetElement) {
			super(buildTargetElement, BuildManagerMessages.LABEL_EnableForNormalBuild, Action.AS_CHECK_BOX);
			setChecked(buildTarget.isNormalBuildEnabled());
		}
		
		@Override
		public void doRun() throws StatusException {
			try {
				doRun0();
			} catch(CommonException e) {
				throw e.toStatusException(Severity.ERROR);
			}
		}
		
		protected void doRun0() throws CommonException {
			getBuildInfo().changeEnable(getBuildTargetName(), isChecked(), buildTarget.isAutoBuildEnabled());
		}
		
	}
	
	public static class ToggleAutoBuildEnabledAction extends ToggleEnabledAction {
		
		public ToggleAutoBuildEnabledAction(BuildTargetElement buildTargetElement) {
			super(buildTargetElement);
			setText(BuildManagerMessages.LABEL_EnableForAutoBuild);
			setChecked(buildTarget.isAutoBuildEnabled());
		}
		
		@Override
		protected void doRun0() throws CommonException {
			getBuildInfo().changeEnable(getBuildTargetName(), buildTarget.isNormalBuildEnabled(), isChecked());
		}
		
	}
	
	public static class BuildSingleTargetAction extends AbstractBuildTargetAction {
		
		public BuildSingleTargetAction(BuildTargetElement buildTargetElement) {
			super(buildTargetElement, BuildManagerMessages.NAME_RunBuildTargetAction);
		}
		
		@Override
		protected String getJobTitle() {
			return format(BuildManagerMessages.INFO_BuildTargetAction, 
				getProject().getName(), buildTargetElement.getTargetDisplayName());
		}
		
		@Override
		public void doRun() throws StatusException {
			super.doRun();
		}
		
		@Override
		protected void doJobRun(IOperationMonitor om) throws CommonException, OperationCancellation {
			getBuildManager().executeBuildTargetOperation(om, getProject(), buildTarget);
		}
		
	}
	
	public class LaunchBuildTargetAction extends AbstractBuildTargetAction {
		
		public String mode;
		public BuildTargetLaunchCreator launchCreator;
		
		public LaunchBuildTargetAction(BuildTargetElement buildTargetElement, boolean isRun, 
				String exePathOverride, String launchNameSuggestion, String actionText) {
			super(buildTargetElement, "");
			mode = isRun ? "run" : "debug";
			
			setText(actionText);
			
			String targetName = buildTarget.getTargetName();
			launchCreator = buildTargetLaunchCreator(project, targetName, exePathOverride, launchNameSuggestion);
		}
		
		protected String getTargetName() {
			return buildTarget.getTargetName();
		}
		
		@Override
		public void doRun() throws StatusException {
			LangLaunchShortcut launchShortcut = createLaunchShortcut();
			BuildTargetLaunchable launchable = launchShortcut.new BuildTargetLaunchable(project, launchCreator);
			launchShortcut.launchTarget(launchable, mode);
		}
		
	}
	
	public static BuildTargetLaunchCreator buildTargetLaunchCreator(IProject project, 
			String targetName, String executablePath, String launchNameSuffixSuggestion) {
		
		BuildTargetData buildTargetData = new BuildTargetData(
			targetName,
			false,
			false,
			null,
			executablePath
		);
		
		return new BuildTargetLaunchCreator(project.getName(), buildTargetData, launchNameSuffixSuggestion);
	}
	
	protected abstract LangLaunchShortcut createLaunchShortcut();
	
	public class ConfigureBuildTargetAction extends AbstractBuildElementAction {
		
		protected String targetName;
		
		public ConfigureBuildTargetAction(BuildTargetsContainer buildTargetContainer) {
			super(buildTargetContainer.getBuildManager(), buildTargetContainer.getProject(), 
				BuildManagerMessages.NAME_ConfigureTargetsAction, Action.AS_UNSPECIFIED);
		}
		
		public ConfigureBuildTargetAction(BuildTargetElement buildTargetElement) {
			super(buildTargetElement.getBuildManager(), buildTargetElement.getProject(), 
				BuildManagerMessages.NAME_ConfigureTargetAction, Action.AS_UNSPECIFIED);
			
			targetName = buildTargetElement.getBuildTarget().getTargetName();
		}
		
		@Override
		public void doRun() throws StatusException {
			PreferenceDialog dialog = PreferencesUtil.createPropertyDialogOn(getShell(), getProject(), 
				getProjectConfigPropertyPage(), null, targetName);
			dialog.open();
		}
		
	}
	
	protected String getProjectConfigPropertyPage() {
		return LangUIPlugin_Actual.PLUGIN_ID + ".propertyPages.ProjectBuildConfiguration";
	}
	
}