/*******************************************************************************
 * Copyright (c) 2008, 2014 QNX Software Systems 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:
 *     QNX Software Systems - Initial API and implementation
 *     Wind River Systems   - Modified for new DSF Reference Implementation
 *     Ericsson AB          - Additional handling of events
 *     Ericsson             - Version 7.0
 *     Mikhail Khodjaiants (Mentor Graphics) - Refactor common code in GDBControl* classes (bug 372795)
 *******************************************************************************/

package org.eclipse.cdt.dsf.mi.service.command;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.dsf.debug.service.ISignals.ISignalsDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
import org.eclipse.cdt.dsf.mi.service.command.commands.MIInterpreterExecConsole;
import org.eclipse.cdt.dsf.mi.service.command.events.MIBreakpointChangedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIDetachedEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MIRunningEvent;
import org.eclipse.cdt.dsf.mi.service.command.events.MISignalChangedEvent;

/**
 * GDB debugger output listener.
 * @since 1.1
 */
@ConfinedToDsfExecutor("fConnection#getExecutor")
public class CLIEventProcessor_7_0 implements IEventProcessor {
	private final ICommandControlService fCommandControl;

	public CLIEventProcessor_7_0(ICommandControlService connection, ICommandControlDMContext controlDmc) {
		fCommandControl = connection;
		fCommandControl.addCommandListener(this);
		fCommandControl.addEventListener(this);
	}

	@Override
	public void dispose() {
		fCommandControl.removeCommandListener(this);
		fCommandControl.removeEventListener(this);
	}

	@Override
	public void commandSent(ICommandToken token) {
		if (token.getCommand() instanceof CLICommand<?>) {
			processStateChanges((CLICommand<?>) token.getCommand());
		} else if (token.getCommand() instanceof MIInterpreterExecConsole<?>) {
			processStateChanges((MIInterpreterExecConsole<?>) token.getCommand());
		}
	}

	@Override
	public void commandDone(ICommandToken token, ICommandResult result) {
		if (token.getCommand() instanceof CLICommand<?>) {
			processSettingChanges((CLICommand<?>) token.getCommand());
		} else if (token.getCommand() instanceof MIInterpreterExecConsole<?>) {
			processSettingChanges((MIInterpreterExecConsole<?>) token.getCommand());
		}
	}

	@Override
	public void commandQueued(ICommandToken token) {
		// No action
	}

	@Override
	public void commandRemoved(ICommandToken token) {
		// No action
	}

	@Override
	public void eventReceived(Object output) {
	}

	private void processStateChanges(CLICommand<? extends ICommandResult> cmd) {
		String operation = cmd.getOperation().trim();
		// In refactoring we are no longer generating the token id as
		// part of the command. It is passed here and stored away  and
		// then never really used. So it has just been changed to 0.
		processStateChanges(0, operation);
	}

	private void processStateChanges(MIInterpreterExecConsole<? extends ICommandResult> exec) {
		String[] operations = exec.getParameters();
		if (operations != null && operations.length > 0) {
			// In refactoring we are no longer generating the token id as
			// part of the command. It is passed here and stored away  and
			// then never really used. So it has just been changed to 0.
			processStateChanges(0, operations[0]);
		}
	}

	private void processStateChanges(int token, String operation) {
		// Get the command name.
		int indx = operation.indexOf(' ');
		if (indx != -1) {
			operation = operation.substring(0, indx).trim();
		} else {
			operation = operation.trim();
		}

		// Check the type of command
		int type = getSteppingOperationKind(operation);
		if (type != -1) {
			// Should set MIrunControlEventProcessor_7_0.fLastRunningCmdType
		}
	}

	/**
	 * An attempt to discover the command type and
	 * fire an event if necessary.
	 */
	private void processSettingChanges(CLICommand<?> cmd) {
		String operation = cmd.getOperation().trim();
		// In refactoring we are no longer genwerating the token id as
		// part of the command. It is passed here and stored away  and
		// then never really used. So it has just been changed to 0.
		processSettingChanges(cmd.getContext(), 0, operation);
	}

	private void processSettingChanges(MIInterpreterExecConsole<?> exec) {
		String[] operations = exec.getParameters();
		if (operations != null && operations.length > 0) {
			// In refactoring we are no longer genwerating the token id as
			// part of the command. It is passed here and stored away  and
			// then never really used. So it has just been changed to 0.
			processSettingChanges(exec.getContext(), 0, operations[0]);
		}
	}

	private void processSettingChanges(IDMContext dmc, int token, String operation) {
		// Get the command name.
		int indx = operation.indexOf(' ');
		if (indx != -1) {
			operation = operation.substring(0, indx).trim();
		} else {
			operation = operation.trim();
		}

		// Check the type of command

		if (isSettingBreakpoint(operation) || isSettingWatchpoint(operation) || isChangeBreakpoint(operation)
				|| isDeletingBreakpoint(operation)) {
			// We know something change, we just do not know what.
			// So the easiest way is to let the top layer handle it.
			IBreakpointsTargetDMContext bpTargetDmc = DMContexts.getAncestorOfType(dmc,
					IBreakpointsTargetDMContext.class);
			if (bpTargetDmc != null) {
				MIEvent<?> event = new MIBreakpointChangedEvent(bpTargetDmc, 0);
				fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties());
			}
		} else if (isSettingSignal(operation)) {
			// We do no know which signal let the upper layer find it.
			ISignalsDMContext signalDmc = DMContexts.getAncestorOfType(dmc, ISignalsDMContext.class);
			if (signalDmc != null) {
				MIEvent<?> event = new MISignalChangedEvent(signalDmc, ""); //$NON-NLS-1$
				fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties());
			}
		} else if (isDetach(operation)) {
			// if it was a "detach" command change the state.
			ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
			if (controlDmc != null) {
				MIEvent<?> event = new MIDetachedEvent(controlDmc, token);
				fCommandControl.getSession().dispatchEvent(event, fCommandControl.getProperties());
			}
		}
	}

	private static int getSteppingOperationKind(String operation) {
		int type = -1;
		/* execution commands: n, next, s, step, si, stepi, u, until, finish, rerurn,
		   c, continue, fg */
		if (operation.equals("n") || operation.equals("next")) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.NEXT;
		} else if (operation.equals("ni") || operation.equals("nexti")) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.NEXTI;
		} else if (operation.equals("s") || operation.equals("step")) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.STEP;
		} else if (operation.equals("si") || operation.equals("stepi")) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.STEPI;
		} else if (operation.equals("u") || //$NON-NLS-1$
				(operation.startsWith("unt") && "until".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.UNTIL;
		} else if (operation.startsWith("fin") && "finish".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.FINISH;
		} else if (operation.startsWith("ret") && "return".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.RETURN;
		} else if (operation.equals("c") || operation.equals("fg") || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("cont") && "continue".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.CONTINUE;
		} else if (operation.startsWith("sig") && "signal".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.CONTINUE;
		} else if (operation.startsWith("j") && "jump".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.CONTINUE;
		} else if (operation.equals("r") || operation.equals("run")) { //$NON-NLS-1$ //$NON-NLS-2$
			type = MIRunningEvent.CONTINUE;
		}
		return type;
	}

	/**
	 * Return true if the operation is a stepping operation.
	 *
	 * @param operation
	 * @return
	 */
	public static boolean isSteppingOperation(String operation) {
		int type = getSteppingOperationKind(operation);
		return type != -1;
	}

	private boolean isSettingBreakpoint(String operation) {
		boolean isbreak = false;
		/* breakpoints: b, break, hbreak, tbreak, rbreak, thbreak */
		/* watchpoints: watch, rwatch, awatch, tbreak, rbreak, thbreak */
		if ((operation.startsWith("b") && "break".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("tb") && "tbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("hb") && "hbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("thb") && "thbreak".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("rb") && "rbreak".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$
			isbreak = true;
		}
		return isbreak;
	}

	private boolean isSettingWatchpoint(String operation) {
		boolean isWatch = false;
		/* watchpoints: watch, rwatch, awatch, tbreak, rbreak, thbreak */
		if ((operation.startsWith("wa") && "watch".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("rw") && "rwatch".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("aw") && "awatch".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$
			isWatch = true;
		}
		return isWatch;
	}

	private boolean isDeletingBreakpoint(String operation) {
		boolean isDelete = false;
		/* deleting breaks: clear, delete */
		if ((operation.startsWith("cl") && "clear".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.equals("d") || (operation.startsWith("del") && "delete".indexOf(operation) != -1))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			isDelete = true;
		}
		return isDelete;
	}

	private boolean isChangeBreakpoint(String operation) {
		boolean isChange = false;
		/* changing breaks: enable, disable */
		if ((operation.equals("dis") || operation.equals("disa") || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("disa") && "disable".indexOf(operation) != -1)) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.equals("en") || (operation.startsWith("en") && "enable".indexOf(operation) != -1)) || //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				(operation.startsWith("ig") && "ignore".indexOf(operation) != -1) || //$NON-NLS-1$ //$NON-NLS-2$
				(operation.startsWith("cond") && "condition".indexOf(operation) != -1)) { //$NON-NLS-1$ //$NON-NLS-2$
			isChange = true;
		}
		return isChange;
	}

	private boolean isSettingSignal(String operation) {
		boolean isChange = false;
		/* changing signal: handle, signal */
		if (operation.startsWith("ha") && "handle".indexOf(operation) != -1) { //$NON-NLS-1$ //$NON-NLS-2$
			isChange = true;
		}
		return isChange;
	}

	/**
	 * @param operation
	 * @return
	 */
	private boolean isDetach(String operation) {
		return (operation.startsWith("det") && "detach".indexOf(operation) != -1); //$NON-NLS-1$ //$NON-NLS-2$
	}

}
