/*******************************************************************************
 * Copyright (c) 2014 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.tools.console;

import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import melnorme.lang.ide.core.ILangOperationsListener;
import melnorme.lang.ide.core.utils.process.AbstractRunProcessTask.ProcessStartHelper;
import melnorme.lang.ide.ui.LangImages;
import melnorme.lang.ide.ui.LangUIPlugin_Actual;
import melnorme.lang.ide.ui.tools.console.ToolsConsole.IOConsoleOutputStreamExt;
import melnorme.lang.ide.ui.utils.ConsoleUtils;
import melnorme.lang.ide.ui.utils.StatusMessageDialog2;
import melnorme.lang.ide.ui.utils.StatusMessageDialogWithIgnore;
import melnorme.lang.ide.ui.utils.UIOperationsStatusHandler;
import melnorme.lang.ide.ui.utils.WorkbenchUtils;
import melnorme.util.swt.SWTUtil;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.ArrayUtil;
import melnorme.utilbox.misc.StringUtil;
import melnorme.utilbox.process.ExternalProcessNotifyingHelper.IProcessOutputListener;
import melnorme.utilbox.status.StatusException;
import melnorme.utilbox.status.StatusLevel;


public abstract class LangOperationsConsoleUIHandler implements ILangOperationsListener {
	
	public static final String MSG_IgnoreSimilar = "Ignore similar errors during this session.";
	
	public LangOperationsConsoleUIHandler() {
		super();
	}
	
	protected final Set<String> mutedMessages = Collections.synchronizedSet(new HashSet<>());
	
	@Override
	public void notifyMessage(String msgId, StatusLevel statusLevel, String title, String message) {
		SWTUtil.runInSWTThread(new Runnable() {
			@Override
			public void run() {
				if(msgId != null && mutedMessages.contains(msgId)) { 
					return;
				}
				StatusException statusMessage = new StatusException(statusLevel.toSeverity(), message);
				
				Shell shell = WorkbenchUtils.getActiveWorkbenchShell();
				StatusMessageDialog2 dialog;
				
				if(msgId == null) {
					dialog = new StatusMessageDialog2(shell, title, statusMessage);
				} else {
					dialog = new StatusMessageDialogWithIgnore(shell, title, statusMessage, MSG_IgnoreSimilar) {
						@Override
						protected void setIgnoreFutureMessages() {
							mutedMessages.add(msgId);
						};
					};
				}
				
				if(UIOperationsStatusHandler.isIgnoringHandling()) {
					Display.getCurrent().asyncExec(
						() -> dialog.okPressed()
					);
				}
				dialog.open();
			}
		});
	}
	
	/* -----------------  ----------------- */
	
	@SuppressWarnings("unused")
	protected String getBuildConsoleName(IProject project) {
		return LangUIPlugin_Actual.BUILD_ConsoleName;
	}
	
	protected ToolsConsole createBuildConsole(String name) {
		return new ToolsConsole(name, LangImages.BUILD_CONSOLE_ICON.getDescriptor());
	}
	
	protected ToolsConsole getBuildConsole(IProject project, boolean clearConsole) {
		String operationConsoleName = getBuildConsoleName(project);
		return ConsoleUtils.getOrCreateToolsConsole(operationConsoleName, clearConsole, ToolsConsole.class, 
			() -> createBuildConsole(operationConsoleName));
	}
	
	
	/* -----------------  ----------------- */
	
	@Override
	public IToolOperationMonitor beginOperation(ProcessStartKind kind, boolean clearConsole, 
			boolean activateConsole) {
		
		IToolOperationMonitor opHandler = doBeginOperation(kind, clearConsole);
		
		if(activateConsole){
			opHandler.activate();
		}
		
		return opHandler;
	}
	
	protected IToolOperationMonitor doBeginOperation(ProcessStartKind kind, boolean clearConsole) {
		switch (kind) {
		case BUILD: {
			ToolsConsole console = getBuildConsole(null, clearConsole);
			return createConsoleHandler(kind, console, console.stdOut, console.stdErr);
		}
		case CHECK_BUILD: {
			ToolsConsole console = getBuildConsole(null, clearConsole);
			return createConsoleHandler(kind, console, console.stdOut, console.stdErr_silent);
		}
		case ENGINE_SERVER: {
			EngineToolsConsole console = EngineToolsConsole.getConsole(clearConsole);
			return createConsoleHandler(kind, console, console.serverStdOut, console.serverStdErr);
		}
		case ENGINE_TOOLS: {
			EngineToolsConsole console = EngineToolsConsole.getConsole(clearConsole);
			return createConsoleHandler(kind, console, console.stdOut, console.stdErr);
		}
		}
		throw assertFail();
	}
	
	protected OperationConsoleMonitor createConsoleHandler(ProcessStartKind kind, ToolsConsole console, 
			IOConsoleOutputStreamExt stdOut, IOConsoleOutputStreamExt stdErr) {
		return new OperationConsoleMonitor(kind, console, stdOut, stdErr);
	}
	
	/* -----------------  ----------------- */
	
	public class OperationConsoleMonitor implements IToolOperationMonitor {
		
		protected final ProcessStartKind kind;
		protected final ToolsConsole console;
		protected final IOConsoleOutputStreamExt infoOut;
		protected final IOConsoleOutputStreamExt stdOut;
		protected final IOConsoleOutputStreamExt stdErr;
		
		public boolean errorOnNonZeroExitValueForBuild = false;
		
		public OperationConsoleMonitor(ProcessStartKind kind, ToolsConsole console, 
				IOConsoleOutputStreamExt stdOut, IOConsoleOutputStreamExt stdErr) {
			this.kind = assertNotNull(kind);
			this.console = assertNotNull(console);
			this.infoOut = console.infoOut;
			this.stdOut = stdOut;
			this.stdErr = stdErr;
		}
		
		@Override
		public void writeInfoMessage(String operationMessage) {
			console.writeOperationInfo(operationMessage);
		}
		
		@Override
		public void handleProcessStart(String prefixText, String suffixText, ProcessBuilder pb, 
			ProcessStartHelper processStartHelper) 
		{
			String infoPrefaceText = getPrefaceText(prefixText, suffixText, pb);
			
			if(infoPrefaceText != null) {
				infoOut.write(infoPrefaceText);
			}
			
			connectProcessOutputListener(processStartHelper);
		}
		
		protected void connectProcessOutputListener(ProcessStartHelper processStartHelper) {
			try {
				processStartHelper.addProcessListener(createOutputListener());
			} catch(CommonException ce) {
				
				String text = "  FAILED: " + ce.getMessage();
				Throwable cause = ce.getCause();
				if(cause != null) {
					text += "   Reason: " + cause.getMessage() + "\n";
				}
				infoOut.write(text);
			}
		}
		
		protected IProcessOutputListener createOutputListener() {
			return new ConsoleOutputProcessListener(stdOut, stdErr) {
				@Override
				public void notifyProcessTerminatedAndRead(int exitCode) {
					super.notifyProcessTerminatedAndRead(exitCode);
					handleProcessTerminated(exitCode);
				}
			};
		}
		
		protected void handleProcessTerminated(int exitCode) {
			boolean activateOnErrors = kind == ProcessStartKind.BUILD && 
					ToolsConsolePrefs.ACTIVATE_ON_ERROR_MESSAGES.get();
			
			if(errorOnNonZeroExitValueForBuild && exitCode != 0 && activateOnErrors) {
				console.activate();
			}
			
			infoOut.write(getProcessTerminatedMessage(exitCode));
			infoOut.flush();
		}
		
		@Override
		public void activate() {
			console.activate();
			// Try again - because of poort concurrency guarantes, the console view might have been activated
			// but with the wrong console page, so send another request, in UI thread.
			Display.getDefault().asyncExec(() -> console.activate());
		}
		
	}
	
	protected String getPrefaceText(String prefixText, String suffixText, ProcessBuilder pb) {
		List<String> commandLine = pb.command();
		
		prefixText = prefixText == null ? ">> Running: " : prefixText;
		
		String argsLabel = DebugPlugin.renderArguments(ArrayUtil.createFrom(commandLine, String.class), null);
		String infoPrefaceText = prefixText + argsLabel;
		return infoPrefaceText + StringUtil.nullAsEmpty(suffixText) + "\n";
	}
	
	protected String getProcessTerminatedMessage(int exitCode) {
		return "  ^^^ Terminated, exit code: " + exitCode +  " ^^^\n";
	}
	
}