/*
 * Copyright (C)2005-2019 Haxe Foundation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package cpp.vm;

/**
	Parameter describes a function parameter. Instances of this class are
	embedded in stack frame objects to describe the function parameters that
	were used in the invocation of the function that defines that stack frame.
**/
class Parameter {
	public var name(default, null):String;
	public var value(default, null):Dynamic;

	public function new(name:String, value:Dynamic) {
		this.name = name;
		this.value = value;
	}
}

/**
	`StackFrame` describes one call stack frame.
**/
class StackFrame {
	public var fileName(default, null):String;
	public var lineNumber(default, null):Int;
	public var className(default, null):String;
	public var functionName(default, null):String;
	public var parameters(default, null):Array<Parameter>;

	public function new(fileName:String, lineNumber:Int, className:String, functionName:String) {
		this.fileName = fileName;
		this.lineNumber = lineNumber;
		this.className = className;
		this.functionName = functionName;
		this.parameters = new Array<Parameter>();
	}
}

/**
	`ThreadInfo` describes the state of a single thread.
**/
class ThreadInfo {
	public static inline var STATUS_RUNNING = 1;
	public static inline var STATUS_STOPPED_BREAK_IMMEDIATE = 2;
	public static inline var STATUS_STOPPED_BREAKPOINT = 3;
	public static inline var STATUS_STOPPED_UNCAUGHT_EXCEPTION = 4;
	public static inline var STATUS_STOPPED_CRITICAL_ERROR = 5;

	/** 0 is never a valid thread number **/
	public var number(default, null):Int;

	public var status(default, null):Int;

	/** If status is "stopped breakpoint", this is the breakpoint number **/
	public var breakpoint(default, null):Int;

	/** If status is "critical error", this describes the error **/
	public var criticalErrorDescription(default, null):String;

	/** Stack will be listed with the lowest frame first **/
	public var stack(default, null):Array<StackFrame>;

	public function new(number:Int, status:Int, breakpoint:Int = -1, criticalErrorDescription:String = null) {
		this.number = number;
		this.status = status;
		this.breakpoint = breakpoint;
		this.criticalErrorDescription = criticalErrorDescription;
		this.stack = new Array<StackFrame>();
	}
}

/**
	This class wraps the hxcpp C++ implementation to provide a Haxe interface
	to the low level debugging features
**/
class Debugger {
	public static inline var THREAD_CREATED = 1;
	public static inline var THREAD_TERMINATED = 2;
	public static inline var THREAD_STARTED = 3;
	public static inline var THREAD_STOPPED = 4;

	public static inline var STEP_INTO = 1;
	public static inline var STEP_OVER = 2;
	public static inline var STEP_OUT = 3;

	/**
		This tagging value is returned by `getStackVariableValue()` and
		`setStackVariableValue()` if the requested value does not exist at the
		requested stack frame
	**/
	public static var NONEXISTENT_VALUE = "NONEXISTENT_VALUE";

	/**
		This tagging value is returned by `getStackVariableValue()` and
		`setStackVariableValue()` if the stack variable that is being set is on a
		thread that is running, in which case the set does not take place.
	**/
	public static var THREAD_NOT_STOPPED = "THREAD_NOT_STOPPED";

	/**
		Sets the handler callback to be made when asynchronous events occur,
		specifically, when threads are created, terminated, started, or
		stopped. The calling thread becomes the "debugger" thread, which means
		that it will be excluded from any breakpoints and will not be reported
		on by any thread reporting requests.

		Be aware that this callback is made asynchronously and possibly by
		multiple threads simultaneously.

		Setting this to `null` prevents further callbacks.

		Throws a string exception if the program does not support debugging
		because it was not compiled with the `HXCPP_DEBUGGER` flag set.

		@param handler is a function that will be called back by asynchronous
			   thread events.  Note that this function is called directly from
			   the thread experiencing the event and the handler should return
			   quickly to avoid blocking the calling thread unnecessarily.
			   The parameters to handler are:
				 - `threadNumber`, the thread number of the event
				 - event, one of `THREAD_CREATED`, `THREAD_TERMINATED`,
				   `THREAD_STARTED`, or `THREAD_STOPPED`
				 - stackFrame, the stack frame number at which the thread is stopped,
				   undefined if event is not `THREAD_STOPPED`
				 - `className`, the class name at which the thread is stopped,
				   undefined if event is not `THREAD_STOPPED`
				 - `functionName`, the function name at which the thread is
				   stopped, undefined if event is not `THREAD_STOPPED`
				 - `fileName`, the file name at which the thread is stopped,
				   undefined if event is not `THREAD_STOPPED`
				 - `lineNumber`, the line number at which the thread is stopped,
				   undefined if event is not `THREAD_STOPPED`
	**/
	public static function setEventNotificationHandler(handler:(threadNumber:Int, event:Int, stackFrame:Int, className:String, functionName:String,
		fileName:String, lineNumber:Int) -> Void) {
		untyped __global__.__hxcpp_dbg_setEventNotificationHandler(handler);
	}

	/**
		This can be called to turn off (and then back on) all stopping of
		debugged threads temporarily. It should only be used by classes that
		actually implement the debugger to hide themselves from the debugger as
		necessary.
	**/
	public static function enableCurrentThreadDebugging(enabled:Bool) {
		untyped __global__.__hxcpp_dbg_enableCurrentThreadDebugging(enabled);
	}

	/**
		Returns the thread number of the calling thread.

		@return the thread number of the calling thread.
	**/
	public static function getCurrentThreadNumber():Int {
		return untyped __global__.__hxcpp_dbg_getCurrentThreadNumber();
	}

	/**
		Returns the set of source files known to the debugger. This is a copy
		of the original array and could be quite large. The caller should
		cache this value to avoid multiple copies needing to be made.

		@return the set of source files known to the debugger.
	**/
	public static function getFiles():Array<String> {
		return untyped __global__.__hxcpp_dbg_getFiles();
	}

	/**
		Returns the full paths of the set of source files known to the debugger.
		This is a copy of the original array and could be quite large.
		It is possible that this set will be empty, in which case the full paths are not known.
		The index of these files matches the index from `getFiles()`, so the full path for
		a given short path can be calculated.

		@return the known full paths of the set of source files
	**/
	public static function getFilesFullPath():Array<String> {
		return untyped __global__.__hxcpp_dbg_getFilesFullPath();
	}

	/**
		Returns the set of class names of all classes known to the debugger.
		This is a copy of the original array and could be quite large. The
		caller should cache this value to avoid multiple copies needing to be
		made.

		@return the set of class names of all classes known to the debugger.
	**/
	public static function getClasses():Array<String> {
		return untyped __global__.__hxcpp_dbg_getClasses();
	}

	/**
		Returns a `ThreadInfo` object describing every thread that existed at the
		moment that the call was made, except for the debugger thread.
	**/
	public static function getThreadInfos():Array<ThreadInfo> {
		return untyped __global__.__hxcpp_dbg_getThreadInfos();
	}

	/**
		Returns a `ThreadInfo` object describing a single thread, or `null` if
		there is no such thread or the thread queried about was the debugger
		thread and `unsafe` was not `true`.
	**/
	public static function getThreadInfo(threadNumber:Int, unsafe:Bool):ThreadInfo {
		return untyped __global__.__hxcpp_dbg_getThreadInfo(threadNumber, unsafe);
	}

	/**
		Adds a new `file:line` breakpoint. The breakpoint number of the newly
		added breakpoint is returned.
	**/
	public static function addFileLineBreakpoint(file:String, line:Int):Int {
		return untyped __global__.__hxcpp_dbg_addFileLineBreakpoint(file, line);
	}

	/**
		Adds a new `class:function` breakpoint. The breakpoint number of the
		newly added breakpoint is returned.
	**/
	public static function addClassFunctionBreakpoint(className:String, functionName:String):Int {
		return untyped __global__.__hxcpp_dbg_addClassFunctionBreakpoint(className, functionName);
	}

	/**
		Deletes a breakpoint, or all breakpoints.
	**/
	public static function deleteBreakpoint(number:Null<Int>) {
		if (number == null) {
			untyped __global__.__hxcpp_dbg_deleteAllBreakpoints();
		} else {
			untyped __global__.__hxcpp_dbg_deleteBreakpoint(cast(number, Int));
		}
	}

	/**
		Breaks all threads except the debugger thread (which should be the same
		as the calling thread!).

		If `wait` is `true`, waits up to 2 seconds for all threads to be broken.
		Threads which are in blocking system calls and cannot break after 2
		seconds remain running when this function returns.
	**/
	public static function breakNow(wait:Bool = true) {
		untyped __global__.__hxcpp_dbg_breakNow(wait);
	}

	/**
		Continue execution of all stopped threads. If `specialThreadNumber`
		is a valid thread number, then it will be continued past
		`continueCount` breakpoints instead of just 1 like all of the other
		threads.
	**/
	public static function continueThreads(specialThreadNumber:Int, continueCount:Int) {
		untyped __global__.__hxcpp_dbg_continueThreads(specialThreadNumber, continueCount);
	}

	/**
		Single steps the given thread.
	**/
	public static function stepThread(threadNumber:Int, stepType:Int, stepCount:Int = 1) {
		untyped __global__.__hxcpp_dbg_stepThread(threadNumber, stepType, stepCount);
	}

	/**
		Returns the list of local variables (including `this`, function
		arguments, and local variables) visible to the given thread at the
		given stack frame.

		Returns a list with a single entry, `THREAD_NOT_STOPPED`, if the
		thread is not stopped and thus variables cannot be fetched and
		`unsafe` is not `true`.

		@return the list of local variables (including `this`, function
				arguments, and local variables) visible to the given thread at
				the given stack frame.
	**/
	public static function getStackVariables(threadNumber:Int, stackFrameNumber:Int, unsafe:Bool):Array<String> {
		return untyped __global__.__hxcpp_dbg_getStackVariables(threadNumber, stackFrameNumber, unsafe, THREAD_NOT_STOPPED);
	}

	/**
		Returns the value of a stack variable, or `NONEXISTENT_VALUE` if the
		requested value does not exist.  If the thread is actively running
		and `unsafe` is not `true`, returns `THREAD_NOT_STOPPED`.
	**/
	public static function getStackVariableValue(threadNumber:Int, stackFrameNumber:Int, name:String, unsafe:Bool):Dynamic {
		return untyped __global__.__hxcpp_dbg_getStackVariableValue(threadNumber, stackFrameNumber, name, unsafe, NONEXISTENT_VALUE, THREAD_NOT_STOPPED);
	}

	/**
		Sets the value of a stack variable and returns that value. If the
		variable does not exist, on the stack, this function returns
		`NONEXISTENT_VALUE`. If the thread is actively running and `unsafe` is not
		`true`, returns `THREAD_NOT_STOPPED`, and the value is not set.
	**/
	public static function setStackVariableValue(threadNumber:Int, stackFrameNumber:Int, name:String, value:Dynamic, unsafe:Bool):Dynamic {
		return untyped __global__.__hxcpp_dbg_setStackVariableValue(threadNumber, stackFrameNumber, name, value, unsafe, NONEXISTENT_VALUE,
			THREAD_NOT_STOPPED);
	}

	#if scriptable
	/**
		Sets the callback to run whenever a new CPPIA script is loaded.

		This can be helpful for adding breakpoints to a script.
	**/
	public static function setOnScriptLoadedFunction(callback:Void->Void):Void {
		untyped __global__.__hxcpp_dbg_setOnScriptLoadedFunction(callback);
	}
	#end

	// The hxcpp runtime calls back through these functions to create Haxe
	// objects as needed, which allows the C++ implementation code to create
	// Haxe objects without having to actually know the structure of those
	// objects
	private static function __init__() {
		untyped __global__.__hxcpp_dbg_setNewParameterFunction(function(name:String, value:Dynamic):Dynamic {
			return new Parameter(name, value);
		});

		untyped __global__.__hxcpp_dbg_setNewStackFrameFunction(function(fileName:String, lineNumber:Int, className:String, functionName:String) {
			return new StackFrame(fileName, lineNumber, className, functionName);
		});

		untyped __global__.__hxcpp_dbg_setNewThreadInfoFunction(function(number:Int, status:Int, breakpoint:Int, criticalErrorDescription:String):Dynamic {
			return new ThreadInfo(number, status, breakpoint, criticalErrorDescription);
		});

		untyped __global__.__hxcpp_dbg_setAddParameterToStackFrameFunction(function(inStackFrame:Dynamic, inParameter:Dynamic) {
			var stackFrame:StackFrame = cast inStackFrame;
			var parameter:Parameter = cast inParameter;
			stackFrame.parameters.push(parameter);
		});

		untyped __global__.__hxcpp_dbg_setAddStackFrameToThreadInfoFunction(function(inThreadInfo:Dynamic, inStackFrame:Dynamic) {
			var threadInfo:ThreadInfo = cast inThreadInfo;
			var stackFrame:StackFrame = cast inStackFrame;
			threadInfo.stack.push(stackFrame);
		});
	}
}
