//debugData => contain all debug states.
//statesList => ListView

var currentSelectedState = null;
var currentDisplayedState = null;
var debugData = null;
var locations = [];
var locationMap = {};
var breakpoints = {};

function init(data)
{
	jumpOutBackAction.enabled(false);
	jumpIntoBackAction.enabled(false);
	jumpIntoForwardAction.enabled(false);
	jumpOutForwardAction.enabled(false);
	jumpOverBackAction.enabled(false);
	jumpOverForwardAction.enabled(false);

	if (data === null) {
		statesList.model.clear();
		statesSlider.maximumValue = 0;
		statesSlider.value = 0;
		currentSelectedState = null;
		currentDisplayedState = null;
		debugData = null;
		locations = [];
		locationMap = {};
		return;
	}

	debugData = data;
	currentSelectedState = 0;
	currentDisplayedState = 0;
	setupInstructions(currentSelectedState);
	setupCallData(currentSelectedState);
	initLocations();
	initSlider();
	selectState(currentSelectedState);
}

function updateMode()
{
	initSlider();
}

function initLocations()
{
	locations = [];
	if (debugData.states.length === 0)
		return;

	var nullLocation = { start: -1, end: -1, documentId: "", state: 0 };
	var prevLocation = nullLocation;

	for (var i = 0; i < debugData.states.length - 1; i++) {
		var code = debugData.states[i].code;
		var location = code.documentId ? debugData.states[i].solidity : nullLocation;
		if (location.start !== prevLocation.start || location.end !== prevLocation.end || code.documentId !== prevLocation.documentId)
		{
			prevLocation = { start: location.start, end: location.end, documentId: code.documentId, state: i };
			locations.push(prevLocation);
		}
		locationMap[i] = locations.length - 1;
	}
	locations.push({ start: -1, end: -1, documentId: code.documentId, state: i });

	locationMap[debugData.states.length - 1] = locations.length - 1;
}

function setBreakpoints(bp)
{
	breakpoints = bp;
}

function srcMode()
{
	return !assemblyMode && locations.length;
}

function initSlider()
{
	if (!debugData)
		statesSlider.maximumValue = 0;
	else if (srcMode()) {
		statesSlider.maximumValue = locations.length - 1;
	} else {
		statesSlider.maximumValue = debugData.states.length - 1;
	}
	statesSlider.value = 0;
}

function setupInstructions(stateIndex)
{
	var instructions = debugData.states[stateIndex].code.instructions;
	statesList.model.clear();
	for (var i = 0; i < instructions.length; i++)
		statesList.model.append(instructions[i]);

	callDataDump.listModel = debugData.states[stateIndex].callData.items;
}

function setupCallData(stateIndex)
{
	callDataDump.listModel = debugData.states[stateIndex].callData.items;
}

function moveSelection(incr)
{
	if (srcMode()) {
		var locationIndex = locationMap[currentSelectedState];
		if (locationIndex + incr >= 0 && locationIndex + incr < locations.length)
			selectState(locations[locationIndex + incr].state);
	} else {
		if (currentSelectedState + incr >= 0 && currentSelectedState + incr < debugData.states.length)
			selectState(currentSelectedState + incr);
	}
}

function display(stateIndex)
{
	if (stateIndex < 0)
		stateIndex = 0;
	if (stateIndex >= debugData.states.length)
		stateIndex = debugData.states.length - 1;
	if (debugData.states[stateIndex].codeIndex !== debugData.states[currentDisplayedState].codeIndex)
		setupInstructions(stateIndex);
	if (debugData.states[stateIndex].dataIndex !== debugData.states[currentDisplayedState].dataIndex)
		setupCallData(stateIndex);
	var state = debugData.states[stateIndex];
	var codeLine = state.instructionIndex;
	highlightSelection(codeLine);
	completeCtxInformation(state);
	currentDisplayedState = stateIndex;
	var docId = debugData.states[stateIndex].code.documentId;
	if (docId)
		debugExecuteLocation(docId, debugData.states[stateIndex].solidity);
}

function displayFrame(frameIndex)
{
	var state = debugData.states[currentSelectedState];
	if (frameIndex === 0)
		display(currentSelectedState);
	else
		display(state.levels[frameIndex - 1]);
}

function select(index)
{
	if (srcMode())
		selectState(locations[index].state);
	else
		selectState(index);
}

function selectState(stateIndex)
{
	display(stateIndex);
	currentSelectedState = stateIndex;
	var state = debugData.states[stateIndex];
	jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1)
	jumpIntoBackAction.enabled(stateIndex > 0);
	jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1);
	jumpOverBackAction.enabled(stateIndex > 0);
	jumpOutBackAction.enabled(state.levels.length > 1);
	jumpOutForwardAction.enabled(state.levels.length > 1);
	runForwardAction.enabled(stateIndex < debugData.states.length - 1)
	runBackAction.enabled(stateIndex > 0);

	var callStackData = [];
	for (var l = 0; l < state.levels.length; l++) {
		var address = debugData.states[state.levels[l] + 1].code.address;
		callStackData.push(address);
	}
	callStackData.push(debugData.states[0].code.address);
	callStack.listModel = callStackData;
	if (srcMode())
		statesSlider.value = locationMap[stateIndex];
	else
		statesSlider.value = stateIndex;
}

function highlightSelection(index)
{
	if (statesList.visible)
		statesList.positionViewAtRow(index, ListView.Visible);
	statesList.selection.clear();
	statesList.selection.select(index);
}

function completeCtxInformation(state)
{
	currentStep.update(state.step);
	mem.update(state.newMemSize.value() + " " + qsTr("words"));
	stepCost.update(state.gasCost.value());
	gasSpent.update(debugData.states[0].gas.subtract(state.gas).value());

	stack.listModel = state.debugStack;
	storage.listModel = state.debugStorage;
	memoryDump.listModel = state.debugMemory;
	if (state.solidity) {
		solLocals.setData(state.solidity.locals.variables, state.solidity.locals.values);
		solStorage.setData(state.solidity.storage.variables, state.solidity.storage.values);
		solCallStack.listModel = state.solidity.callStack;
	} else {
		solLocals.setData([], {});
		solStorage.setData([], {});
		solCallStack.listModel = [];
	}
}

function isCallInstruction(index)
{
	var state = debugData.states[index];
	return state.instruction === "CALL" || state.instruction === "CREATE";
}

function isReturnInstruction(index)
{
	var state = debugData.states[index];
	return state.instruction === "RETURN"
}

function locationsIntersect(l1, l2)
{
	return l1.start <= l2.end && l1.end >= l2.start;
}

function breakpointHit(i)
{
	var bpLocations = breakpoints[debugData.states[i].code.documentId];
	if (bpLocations) {
		var location = debugData.states[i].solidity;
		if (location.start >= 0 && location.end >= location.start)
			for (var b = 0; b < bpLocations.length; b++)
				if (locationsIntersect(location, bpLocations[b]))
					return true;
	}
	return false;
}

function stepIntoBack()
{
	moveSelection(-1);
}

function stepOverBack()
{
	if (currentSelectedState > 0 && isReturnInstruction(currentSelectedState - 1))
		stepOutBack();
	else
		moveSelection(-1);
}

function stepOverForward()
{
	if (isCallInstruction(currentSelectedState))
		stepOutForward();
	else
		moveSelection(1);
}

function stepIntoForward()
{
	moveSelection(1);
}

function runBack()
{
	var i = currentSelectedState - 1;
	while (i > 0 && !breakpointHit(i)) {
		--i;
	}
	selectState(i);
}

function runForward()
{
	var i = currentSelectedState + 1;
	while (i < debugData.states.length - 1 && !breakpointHit(i)) {
		++i;
	}
	selectState(i);
}

function stepOutBack()
{
	var i = currentSelectedState - 1;
	var depth = 0;
	while (--i >= 0) {
		if (breakpointHit(i))
			break;
		if (isCallInstruction(i))
			if (depth == 0)
				break;
			else depth--;
		else if (isReturnInstruction(i))
			depth++;
	}
	selectState(i);
}

function stepOutForward()
{
	var i = currentSelectedState;
	var depth = 0;
	while (++i < debugData.states.length) {
		if (breakpointHit(i))
			break;
		if (isReturnInstruction(i))
			if (depth == 0)
				break;
			else
				depth--;
		else if (isCallInstruction(i))
			depth++;
	}
	selectState(i + 1);
}

function jumpTo(value)
{
	select(value);
}