import { commands } from 'vscode';
import { BreakpointEvent, InitializedEvent, LoggingDebugSession, OutputEvent, StoppedEvent, TerminatedEvent, Thread } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { DebugDispather } from './debugDispather';
import { Continue } from './struct/command';
import { VscodeScope } from './struct/scope';

/**
 * This interface describes the mock-debug specific launch attr
 * (which are not part of the Debug Adapter Protocol).
 * The schema for these attr lives in the package.json of the mock-debug extension.
 * The interface should always match this schema.
 */
export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
	/** An absolute path to the "program" to debug. */
	program: string;
	/** An absolute path to the AutoHotkey.exe. */
	runtime: string;
	dbgpSettings: {
		max_children: number;
		max_data: number;
	};
}

/**
 * debug session for vscode.
 */
export class DebugSession extends LoggingDebugSession {

	private static THREAD_ID = 1;
	private dispather: DebugDispather;

	public constructor() {
		super("ahk-debug.txt");

		// this debugger uses zero-based lines and columns
		this.setDebuggerLinesStartAt1(false);
		this.setDebuggerColumnsStartAt1(false);

		this.dispather = new DebugDispather();
		this.dispather.on('break', (reason: string) => {
			this.sendEvent(new StoppedEvent(reason, DebugSession.THREAD_ID));
		}).on('breakpointValidated', (bp: DebugProtocol.Breakpoint) => {
			this.sendEvent(new BreakpointEvent('changed', { verified: bp.verified, id: bp.id } as DebugProtocol.Breakpoint));
		}).on('output', (text) => {
			this.sendEvent(new OutputEvent(`${text}\n`));
			commands.executeCommand('workbench.debug.action.focusRepl')
		}).on('end', () => {
			this.sendEvent(new TerminatedEvent());
		});

	}

	protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {

		response.body = {
			...response.body,
			completionTriggerCharacters: [".", "["],
			supportsConfigurationDoneRequest: false,
			supportsEvaluateForHovers: true,
			supportsStepBack: false,
			supportsDataBreakpoints: false,
			supportsCompletionsRequest: true,
			supportsCancelRequest: true,
			supportsRestartRequest: true,
			supportsBreakpointLocationsRequest: false,
			supportsSetVariable: true,
		}

		this.sendResponse(response);
		this.sendEvent(new InitializedEvent());
	}

	protected restartRequest(response: DebugProtocol.RestartResponse, args: DebugProtocol.RestartArguments, request?: DebugProtocol.Request): void {
		this.dispather.restart()
		this.sendResponse(response);
	}

	protected async launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
		this.dispather.start(args);
		this.sendResponse(response);
	}

	protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request): void {
		this.dispather.stop();
		this.sendResponse(response);
	}

	protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
		response.body = { breakpoints: this.dispather.buildBreakPoint(args.source.path, args.breakpoints), };
		this.sendResponse(response);
	}

	protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise<void> {
		response.body = { stackFrames: await this.dispather.stack(args) };
		this.sendResponse(response);
	}

	protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
		response.body = { scopes: this.dispather.scopes(args.frameId) };
		this.sendResponse(response);
	}

	protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments, request?: DebugProtocol.Request) {
		response.body = { variables: await this.dispather.listVariables(args) };
		this.sendResponse(response);
	}

	protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments, request?: DebugProtocol.Request): Promise<void> {
		try {
			response.body = await this.dispather.setVariable(args);
			this.sendResponse(response);
		} catch (error) {
			this.sendErrorResponse(response, {
				id: args.variablesReference,
				format: error.message,
			})
		}
	}

	protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments, request?: DebugProtocol.Request): void {
		this.dispather.sendComand(Continue.BREAK)
		this.sendResponse(response);
	}


	protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
		this.dispather.sendComand(Continue.RUN)
		this.sendResponse(response);
	}

	protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
		this.dispather.sendComand(Continue.STEP_OVER);
		this.sendResponse(response);
	}

	protected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments, request?: DebugProtocol.Request): void {
		this.dispather.sendComand(Continue.STEP_INTO);
		this.sendResponse(response);
	}

	protected stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments, request?: DebugProtocol.Request): void {
		this.dispather.sendComand(Continue.STEP_OUT);
		this.sendResponse(response);
	}

	protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
		response.body = { threads: [new Thread(DebugSession.THREAD_ID, "main thread")] };
		this.sendResponse(response);
	}

	protected async completionsRequest(response: DebugProtocol.CompletionsResponse, args: DebugProtocol.CompletionsArguments): Promise<void> {

		response.body = {
			targets: [...(await this.dispather.listVariables({ variablesReference: VscodeScope.LOCAL })),
			...(await this.dispather.listVariables({ variablesReference: VscodeScope.GLOBAL }))]
				.map((variable) => {
					return {
						type: "variable",
						label: variable.name,
						sortText: variable.name
					}
				})
		};
		this.sendResponse(response);
	}

	protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise<void> {

		const exp = args.expression.split("=")
		let reply: string;
		if (exp.length == 1) {
			reply = await this.dispather.getVariableByEval(args.expression)
		} else {
			this.dispather.setVariable({ name: exp[0], value: exp[1], variablesReference: VscodeScope.LOCAL })
			reply = `execute: ${args.expression}`
		}

		response.body = {
			result: reply ? reply : `null`,
			variablesReference: 0
		};
		this.sendResponse(response);
	}

}