/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "director/director.h"
#include "director/debugger/dt-internal.h"

#include "director/debugger.h"

#include "director/lingo/lingo-object.h"

#include "director/lingo/lingodec/codewritervisitor.h"
#include "director/lingo/lingodec/names.h"
#include "director/lingo/lingodec/script.h"

namespace Director {
namespace DT {

class RenderScriptVisitor : public LingoDec::NodeVisitor {
public:
	RenderScriptVisitor(ImGuiScript &script, bool showByteCode) : _script(script), _showByteCode(showByteCode) {
		Common::Array<CFrame *> &callstack = g_lingo->_state->callstack;
		if (!callstack.empty()) {
			CFrame *head = callstack[callstack.size() - 1];
			_isScriptInDebug = (head->sp.ctx->_id == script.id.member) && (*head->sp.name == script.handlerId);
		}
		_script.startOffsets.clear();
	}

	virtual void visit(const LingoDec::HandlerNode &node) override {
		if (_showByteCode) {
			byteCode(node);
			return;
		}

		ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
		lingoCode(node);
		ImGui::PopStyleVar();
	}

	virtual void visit(const LingoDec::CommentNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._comment_color), "-- %s", node.text.c_str());
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::LiteralNode &node) override {
		write(*node.value);
	}

	virtual void visit(const LingoDec::NewObjNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), "new");
		ImGui::SameLine();
		ImGui::TextColored(ImColor(_state->_colors._type_color), "%s", node.objType.c_str());
		ImGui::SameLine();
		ImGui::Text(" (");
		ImGui::SameLine();
		node.objArgs->accept(*this);
		ImGui::Text(")");
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::ObjCallV4Node &node) override {
		if (node.isStatement) {
			renderLine(node._startOffset);
			renderIndentation();
		}

		node.obj->accept(*this);
		ImGui::SameLine();
		ImGui::Text(" (");
		ImGui::SameLine();
		node.argList->accept(*this);
		ImGui::SameLine();
		ImGui::Text(")");
		if (!node.isStatement) {
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::CallNode &node) override {
		int32 obj = 0;
		for (uint i = 0; i < _script.bytecodeArray.size(); i++) {
			if (node._startOffset == _script.bytecodeArray[i].pos) {
				obj = _script.bytecodeArray[i].obj;
				break;
			}
		}

		// new line only if it's a statement
		if (node.isStatement) {
			renderLine(node._startOffset);
			renderIndentation();
		}

		const ImVec4 color = (ImVec4)ImColor(g_lingo->_builtinCmds.contains(node.name) ? _state->_colors._builtin_color : _state->_colors._call_color);
		ImGui::TextColored(color, "%s", node.name.c_str());
		// TODO: we should test Director::builtins too (but inaccessible)
		if (!g_lingo->_builtinCmds.contains(node.name) && ImGui::IsItemHovered() && ImGui::BeginTooltip()) {
			ImGui::Text("Go to definition");
			ImGui::EndTooltip();
		}
		if (!g_lingo->_builtinCmds.contains(node.name) && ImGui::IsItemClicked()) {
			ImGuiScript script = toImGuiScript(_script.type, CastMemberID(obj, _script.id.castLib), node.name);
			script.moviePath = _script.moviePath;
			script.handlerName = node.name;
			setScriptToDisplay(script);
		}
		ImGui::SameLine();

		if (node.noParens()) {
			ImGui::Text(" ");
			ImGui::SameLine();
			node.argList->accept(*this);
		} else {
			ImGui::Text("(");
			ImGui::SameLine();
			node.argList->accept(*this);
			ImGui::Text(")");
			ImGui::SameLine();
		}

		if (node.isStatement) {
			ImGui::NewLine();
		}
	}

	virtual void visit(const LingoDec::BlockNode &node) override {
		indent();
		for (const auto &child : node.children) {
			child->accept(*this);
		}
		unindent();
	}

	virtual void visit(const LingoDec::PutStmtNode &node) override {
		write(node._startOffset, "put ", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.value->accept(*this);
		ImGui::Text(" ");
		ImGui::SameLine();
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), LingoDec::StandardNames::putTypeNames[node.type]);
		ImGui::SameLine();
		ImGui::Text(" ");
		ImGui::SameLine();
		node.variable->accept(*this);
		ImGui::NewLine();
	}

	virtual void visit(const LingoDec::TheExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s", node.prop.c_str());
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::ExitStmtNode &node) override {
		write(node._startOffset, "exit", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::WhenStmtNode &node) override {
		write(node._startOffset, "when ", _state->_colors._keyword_color);
		ImGui::SameLine();
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), LingoDec::StandardNames::whenEventNames[node.event]);
		ImGui::SameLine();
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " then ");
		ImGui::SameLine();
		ImGui::Text("%s", node.script.c_str());
	}

	virtual void visit(const LingoDec::RepeatWhileStmtNode &node) override {
		write(node._startOffset, "repeat while ", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.condition->accept(*this);
		ImGui::NewLine();
		node.block->accept(*this);
		write(node._endOffset, "end repeat", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::RepeatWithInStmtNode &node) override {
		write(node._startOffset, "repeat with ", _state->_colors._keyword_color);
		ImGui::SameLine();
		renderVar(node.varName);
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " in ");
		ImGui::SameLine();
		node.list->accept(*this);
		ImGui::NewLine();
		node.block->accept(*this);
		write(node._endOffset, "end repeat", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::RepeatWithToStmtNode &node) override {
		write(node._startOffset, "repeat with ", _state->_colors._keyword_color);
		ImGui::SameLine();
		renderVar(node.varName);
		ImGui::Text(" = ");
		ImGui::SameLine();
		node.start->accept(*this);
		if (node.up) {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
			ImGui::SameLine();
		} else {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " down to ");
			ImGui::SameLine();
		}
		node.end->accept(*this);
		ImGui::NewLine();
		node.block->accept(*this);
		write(node._endOffset, "end repeat", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::IfStmtNode &node) override {
		{
			write(node._startOffset, "if ", _state->_colors._keyword_color);
			ImGui::SameLine();
			node.condition->accept(*this);
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " then ");
		}
		node.block1->accept(*this);
		if (node.hasElse) {
			write(node.block2->_startOffset, "else ", _state->_colors._keyword_color);
			node.block2->accept(*this);
		}
		write(node._endOffset, "end if", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::TellStmtNode &node) override {
		write(node._startOffset, "tell ", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.window->accept(*this);
		ImGui::NewLine();
		node.block->accept(*this);
		write(node._endOffset, "end tell", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::EndCaseNode &node) override {
		write(node._endOffset, "end case", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::CaseLabelNode &node) override {
		renderLine(node._startOffset);
		renderIndentation();
		bool parenValue = node.value->hasSpaces(_dot);
		if (parenValue) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.value->accept(*this);
		if (parenValue) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		if (node.nextOr) {
			ImGui::Text(",");
			ImGui::SameLine();
			node.nextOr->accept(*this);
		} else {
			ImGui::Text(":");
			node.block->accept(*this);
		}
		if (node.nextLabel) {
			node.nextLabel->accept(*this);
		}
	}

	virtual void visit(const LingoDec::ChunkExprNode &node) override {
		ImGui::Text(LingoDec::StandardNames::chunkTypeNames[node.type]);
		ImGui::SameLine();
		ImGui::Text(" ");
		ImGui::SameLine();
		node.first->accept(*this);
		if (!(node.last->type == LingoDec::kLiteralNode && node.last->getValue()->type == LingoDec::kDatumInt && node.last->getValue()->i == 0)) {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
			ImGui::SameLine();
			node.last->accept(*this);
		}
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
		ImGui::SameLine();
		node.string->accept(*this);
	}

	virtual void visit(const LingoDec::InverseOpNode &node) override {
		ImGui::Text("-");
		ImGui::SameLine();

		bool parenOperand = node.operand->hasSpaces(_dot);
		if (parenOperand) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.operand->accept(*this);
		if (parenOperand) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::CaseStmtNode &node) override {
		write(node._startOffset, "case ", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.value->accept(*this);
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
		indent();
		if (node.firstLabel) {
			node.firstLabel->accept(*this);
		}
		if (node.otherwise) {
			node.otherwise->accept(*this);
		}
		unindent();
		write(node._endOffset, "end case", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::ObjCallNode &node) override {
		auto &rawArgs = node.argList->getValue()->l;

		auto &obj = rawArgs[0];
		bool parenObj = obj->hasSpaces(_dot);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::Text(".");
		ImGui::SameLine();
		ImGui::Text(node.name.c_str());
		ImGui::SameLine();
		ImGui::Text("(");
		ImGui::SameLine();
		for (size_t i = 1; i < rawArgs.size(); i++) {
			if (i > 1) {
				ImGui::Text(",");
				ImGui::SameLine();
			}
			rawArgs[i]->accept(*this);
		}
		ImGui::Text(")");
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::ObjPropExprNode &node) override {
		if (_dot) {
			bool parenObj = node.obj->hasSpaces(_dot);
			if (parenObj) {
				ImGui::Text("(");
				ImGui::SameLine();
			}
			node.obj->accept(*this);
			if (parenObj) {
				ImGui::Text(")");
				ImGui::SameLine();
			}

			ImGui::Text(".");
			ImGui::SameLine();
			ImGui::Text("%s", node.prop.c_str());
			ImGui::SameLine();
		} else {
			ImGui::TextColored(_state->_colors._the_color, "the %s", node.prop.c_str());
			ImGui::SameLine();
			ImGui::TextColored(_state->_colors._keyword_color, " of ");
			ImGui::SameLine();

			bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
			if (parenObj) {
				ImGui::Text("(");
				ImGui::SameLine();
			}
			node.obj->accept(*this);
			if (parenObj) {
				ImGui::Text(")");
				ImGui::SameLine();
			}
		}
	}

	virtual void visit(const LingoDec::BinaryOpNode &node) override {
		unsigned int precedence = node.getPrecedence();
		bool parenLeft = false;
		bool parenRight = false;
		if (precedence) {
			if (node.left->type == LingoDec::kBinaryOpNode) {
				auto leftBinaryOpNode = static_cast<LingoDec::BinaryOpNode *>(node.left.get());
				parenLeft = (leftBinaryOpNode->getPrecedence() != precedence);
			}
			parenRight = (node.right->type == LingoDec::kBinaryOpNode);
		}

		if (parenLeft) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.left->accept(*this);
		if (parenLeft) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::Text(" ");
		ImGui::SameLine();
		ImGui::Text(LingoDec::StandardNames::binaryOpNames[node.opcode]);
		ImGui::SameLine();
		ImGui::Text(" ");
		ImGui::SameLine();

		if (parenRight) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.right->accept(*this);
		if (parenRight) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::OtherwiseNode &node) override {
		write(node._startOffset, "otherwise:", _state->_colors._keyword_color);
		node.block->accept(*this);
	}

	virtual void visit(const LingoDec::MemberExprNode &node) override {
		bool hasCastID = node.castID && !(node.castID->type == LingoDec::kLiteralNode && node.castID->getValue()->type == LingoDec::kDatumInt && node.castID->getValue()->i == 0);
		ImGui::Text(node.type.c_str());
		ImGui::SameLine();
		ImGui::Text(" ");
		ImGui::SameLine();
		if (_dot) {
			ImGui::Text("(");
			ImGui::SameLine();
			node.memberID->accept(*this);
			if (hasCastID) {
				ImGui::Text(",");
				ImGui::SameLine();
				node.castID->accept(*this);
			}
			ImGui::Text(")");
			ImGui::SameLine();
		} else {
			bool parenMemberID = (node.memberID->type == LingoDec::kBinaryOpNode);
			if (parenMemberID) {
				ImGui::Text("(");
				ImGui::SameLine();
			}
			node.memberID->accept(*this);
			if (parenMemberID) {
				ImGui::Text(")");
				ImGui::SameLine();
			}

			if (hasCastID) {
				ImGui::TextColored(ImColor(_state->_colors._keyword_color), "of castLib");
				ImGui::SameLine();

				bool parenCastID = (node.castID->type == LingoDec::kBinaryOpNode);
				if (parenCastID) {
					ImGui::Text("(");
					ImGui::SameLine();
				}
				node.castID->accept(*this);
				if (parenCastID) {
					ImGui::Text(")");
					ImGui::SameLine();
				}
			}
		}
	}

	virtual void visit(const LingoDec::PlayCmdStmtNode &node) override {
		auto &rawArgs = node.argList->getValue()->l;

		write(node._startOffset, "play ", _state->_colors._keyword_color);
		ImGui::SameLine();

		if (rawArgs.size() == 0) {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " done");
			ImGui::SameLine();
			return;
		}

		auto &frame = rawArgs[0];
		if (rawArgs.size() == 1) {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " frame ");
			ImGui::SameLine();
			frame->accept(*this);
			return;
		}

		auto &movie = rawArgs[1];
		if (!(frame->type == LingoDec::kLiteralNode && frame->getValue()->type == LingoDec::kDatumInt && frame->getValue()->i == 1)) {
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " frame ");
			ImGui::SameLine();
			frame->accept(*this);
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
			ImGui::SameLine();
		}
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " movie ");
		ImGui::SameLine();
		movie->accept(*this);
	}

	virtual void visit(const LingoDec::ThePropExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), "the ");
		ImGui::SameLine();
		ImGui::Text(node.prop.c_str());
		ImGui::SameLine();
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of ");
		ImGui::SameLine();

		bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::MenuPropExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of menu ", LingoDec::StandardNames::menuPropertyNames[node.prop]);
		ImGui::SameLine();

		bool parenMenuID = (node.menuID->type == LingoDec::kBinaryOpNode);
		if (parenMenuID) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.menuID->accept(*this);
		if (parenMenuID) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::SoundCmdStmtNode &node) override {
		write(node._startOffset, "sound ", _state->_colors._keyword_color);
		ImGui::SameLine();
		ImGui::Text(node.cmd.c_str());
		ImGui::SameLine();
		ImGui::Text(" ");
		ImGui::SameLine();
		if (node.argList->getValue()->l.size() > 0) {
			node.argList->accept(*this);
		}
		ImGui::NewLine();
	}

	virtual void visit(const LingoDec::SoundPropExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of sound ", LingoDec::StandardNames::soundPropertyNames[node.prop]);
		ImGui::SameLine();

		bool parenSoundID = (node.soundID->type == LingoDec::kBinaryOpNode);
		if (parenSoundID) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.soundID->accept(*this);
		if (parenSoundID) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::AssignmentStmtNode &node) override {
		if (!_dot) {
			write(node._startOffset, "set ", _state->_colors._keyword_color);
			ImGui::SameLine();
			node.variable->accept(*this);
			ImGui::TextColored(ImColor(_state->_colors._keyword_color), " to ");
			ImGui::SameLine();
			node.value->accept(*this);
		} else {
			node.variable->accept(*this);
			ImGui::Text(" = ");
			ImGui::SameLine();
			node.value->accept(*this);
		}
		ImGui::NewLine();
	}

	virtual void visit(const LingoDec::ExitRepeatStmtNode &node) override {
		write(node._startOffset, "exit repeat", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::NextRepeatStmtNode &node) override {
		write(node._startOffset, "next repeat", _state->_colors._keyword_color);
	}

	virtual void visit(const LingoDec::ObjBracketExprNode &node) override {
		bool parenObj = node.obj->hasSpaces(_dot);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::Text("[");
		ImGui::SameLine();
		node.prop->accept(*this);
		ImGui::Text("]");
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::SpritePropExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of sprite ", LingoDec::StandardNames::spritePropertyNames[node.prop]);
		ImGui::SameLine();

		bool parenSpriteID = (node.spriteID->type == LingoDec::kBinaryOpNode);
		if (parenSpriteID) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.spriteID->accept(*this);
		if (parenSpriteID) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::ChunkDeleteStmtNode &node) override {
		write(node._startOffset, "delete", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.chunk->accept(*this);
	}

	virtual void visit(const LingoDec::ChunkHiliteStmtNode &node) override {
		write(node._startOffset, "hilite", _state->_colors._keyword_color);
		ImGui::SameLine();
		node.chunk->accept(*this);
	}

	virtual void visit(const LingoDec::MenuItemPropExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the %s of menuItem ", LingoDec::StandardNames::menuItemPropertyNames[node.prop]);
		ImGui::SameLine();

		bool parenItemID = (node.itemID->type == LingoDec::kBinaryOpNode);
		if (parenItemID) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.itemID->accept(*this);
		if (parenItemID) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " of menu ");
		ImGui::SameLine();

		bool parenMenuID = (node.menuID->type == LingoDec::kBinaryOpNode);
		if (parenMenuID) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.menuID->accept(*this);
		if (parenMenuID) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::ObjPropIndexExprNode &node) override {
		bool parenObj = node.obj->hasSpaces(_dot);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::Text(".");
		ImGui::SameLine();
		ImGui::Text(node.prop.c_str());
		ImGui::SameLine();
		ImGui::Text("[");
		ImGui::SameLine();
		node.index->accept(*this);
		if (node.index2) {
			ImGui::Text("..");
			ImGui::SameLine();
			node.index2->accept(*this);
		}
		ImGui::Text("]");
		ImGui::SameLine();
	}

	virtual void visit(const LingoDec::SpriteWithinExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), "sprite ");
		ImGui::SameLine();

		bool parenFirstSprite = (node.firstSprite->type == LingoDec::kBinaryOpNode);
		if (parenFirstSprite) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.firstSprite->accept(*this);
		if (parenFirstSprite) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " within ");
		ImGui::SameLine();

		bool parenSecondSprite = (node.secondSprite->type == LingoDec::kBinaryOpNode);
		if (parenSecondSprite) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.secondSprite->accept(*this);
		if (parenSecondSprite) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::LastStringChunkExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the last %s in ", LingoDec::StandardNames::chunkTypeNames[node.type]);
		ImGui::SameLine();

		bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::SpriteIntersectsExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._keyword_color), "sprite ");
		ImGui::SameLine();

		bool parenFirstSprite = (node.firstSprite->type == LingoDec::kBinaryOpNode);
		if (parenFirstSprite) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.firstSprite->accept(*this);
		if (parenFirstSprite) {
			ImGui::Text(")");
			ImGui::SameLine();
		}

		ImGui::TextColored(ImColor(_state->_colors._keyword_color), " intersects ");
		ImGui::SameLine();

		bool parenSecondSprite = (node.secondSprite->type == LingoDec::kBinaryOpNode);
		if (parenSecondSprite) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.secondSprite->accept(*this);
		if (parenSecondSprite) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::StringChunkCountExprNode &node) override {
		ImGui::TextColored(ImColor(_state->_colors._the_color), "the number of %ss in ", LingoDec::StandardNames::chunkTypeNames[node.type]);
		ImGui::SameLine();

		bool parenObj = (node.obj->type == LingoDec::kBinaryOpNode);
		if (parenObj) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.obj->accept(*this);
		if (parenObj) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void visit(const LingoDec::VarNode &node) override {
		renderVar(node.varName);
	}

	virtual void visit(const LingoDec::NotOpNode &node) override {
		ImGui::Text("not ");
		ImGui::SameLine();

		bool parenOperand = node.operand->hasSpaces(_dot);
		if (parenOperand) {
			ImGui::Text("(");
			ImGui::SameLine();
		}
		node.operand->accept(*this);
		if (parenOperand) {
			ImGui::Text(")");
			ImGui::SameLine();
		}
	}

	virtual void defaultVisit(const LingoDec::Node &node) override {
		LingoDec::CodeWriterVisitor code(_dot, false);
		node.accept(code);
		if (node.isStatement) {
			renderLine(node._startOffset);
			renderIndentation();
		}
		ImGui::Text("%s", code._str.c_str());
	}

private:
	void write(LingoDec::Datum &datum) {
		switch (datum.type) {
		case LingoDec::kDatumVoid:
			ImGui::TextColored(_state->_colors._keyword_color, "VOID");
			ImGui::SameLine();
			return;
		case LingoDec::kDatumSymbol:
			ImGui::Text("#%s", datum.s.c_str());
			ImGui::SameLine();
			return;
		case LingoDec::kDatumVarRef:
			ImGui::TextColored(_state->_colors._var_color, datum.s.c_str());
			ImGui::SameLine();
			return;
		case LingoDec::kDatumString:
			if (datum.s.empty()) {
				ImGui::TextColored(_state->_colors._keyword_color, "EMPTY");
				ImGui::SameLine();
				return;
			}
			if (datum.s.size() == 1) {
				switch (datum.s[0]) {
				case '\x03':
					ImGui::TextColored(_state->_colors._keyword_color, "ENTER");
					ImGui::SameLine();
					return;
				case '\x08':
					ImGui::TextColored(_state->_colors._keyword_color, "BACKSPACE");
					ImGui::SameLine();
					return;
				case '\t':
					ImGui::TextColored(_state->_colors._keyword_color, "TAB");
					ImGui::SameLine();
					return;
				case '\r':
					ImGui::TextColored(_state->_colors._keyword_color, "RETURN");
					ImGui::SameLine();
					return;
				case '"':
					ImGui::TextColored(_state->_colors._keyword_color, "QUOTE");
					ImGui::SameLine();
					return;
				default:
					break;
				}
			}
			ImGui::Text("\"%s\"", datum.s.c_str());
			ImGui::SameLine();
			return;
		case LingoDec::kDatumInt:
			ImGui::TextColored(_state->_colors._literal_color, "%d", datum.i);
			ImGui::SameLine();
			return;
		case LingoDec::kDatumFloat:
			ImGui::TextColored(_state->_colors._literal_color, "%g", datum.f);
			ImGui::SameLine();
			return;
		case LingoDec::kDatumList:
		case LingoDec::kDatumArgList:
		case LingoDec::kDatumArgListNoRet: {
			if (datum.type == LingoDec::kDatumList) {
				ImGui::Text("[");
				ImGui::SameLine();
			}
			for (size_t ii = 0; ii < datum.l.size(); ii++) {
				if (ii > 0) {
					ImGui::Text(", ");
					ImGui::SameLine();
				}
				datum.l[ii]->accept(*this);
			}
			if (datum.type == LingoDec::kDatumList) {
				ImGui::Text("]");
				ImGui::SameLine();
			}
		}
			return;
		case LingoDec::kDatumPropList: {
			ImGui::Text("[");
			if (datum.l.size() == 0) {
				ImGui::Text(":");
				ImGui::SameLine();
			} else {
				for (size_t ii = 0; ii < datum.l.size(); ii += 2) {
					if (ii > 0) {
						ImGui::Text(", ");
						ImGui::SameLine();
					}
					datum.l[ii]->accept(*this);
					ImGui::Text(": ");
					ImGui::SameLine();
					datum.l[ii + 1]->accept(*this);
				}
			}
			ImGui::Text("]");
			ImGui::SameLine();
		}
			return;
		}
	}

	void renderVar(const Common::String &varName) {
		ImGui::TextColored(_state->_colors._var_color, "%s", varName.c_str());
		if (ImGui::IsItemHovered() && g_lingo->_globalvars.contains(varName)) {
			const Datum &val = g_lingo->_globalvars.getVal(varName);
			ImGui::BeginTooltip();
			ImGui::Text("Click to add to watches.");
			ImGui::Text("= %s", val.asString(true).c_str());
			ImGui::EndTooltip();
		}
		if (ImGui::IsItemClicked()) {
			_state->_variables[varName] = true;
		}
		ImGui::SameLine();
	}

	void lingoCode(const LingoDec::HandlerNode &node) {
		if (_script.isGenericEvent) {
			node.block->accept(*this);
			return;
		}

		bool isMethod = _script.isMethod;
		write(node._startOffset, isMethod ? "method " : "on ", _state->_colors._keyword_color);
		ImGui::SameLine();
		ImGui::TextColored(_state->_colors._call_color, "%s", _script.handlerId.c_str());
		ImGui::SameLine();

		if (!_script.argumentNames.empty()) {
			ImGui::Text(" ");
			ImGui::SameLine();
			for (size_t i = 0; i < _script.argumentNames.size(); i++) {
				if (i > 0) {
					ImGui::Text(", ");
					ImGui::SameLine();
				}
				ImGui::Text("%s", _script.argumentNames[i].c_str());
				ImGui::SameLine();
			}
		}
		indent();

		if (isMethod && !_script.propertyNames.empty() && node.handler == &node.handler->script->handlers[0]) {
			ImGui::NewLine();
			write(node._startOffset, "instance ");
			ImGui::SameLine();
			for (size_t i = 0; i < _script.propertyNames.size(); i++) {
				if (i > 0)
					ImGui::Text(",");
				ImGui::SameLine();
				ImGui::TextColored(_state->_colors._var_color, "%s", _script.propertyNames[i].c_str());
				ImGui::SameLine();
			}
		}

		if (!_script.globalNames.empty()) {
			ImGui::NewLine();
			write(node._startOffset, "global ");
			ImGui::SameLine();
			for (size_t i = 0; i < _script.globalNames.size(); i++) {
				if (i > 0) {
					ImGui::Text(",");
					ImGui::SameLine();
				}
				ImGui::TextColored(_state->_colors._var_color, "%s", _script.globalNames[i].c_str());
				ImGui::SameLine();
			}
		}

		ImGui::NewLine();
		unindent();
		node.block->accept(*this);

		if (!isMethod) {
			write(node._endOffset, "end", _state->_colors._keyword_color);
		}
	}

	void byteCode(const LingoDec::HandlerNode &node) {
		bool isMethod = _script.isMethod;
		if (!_script.isGenericEvent) {
			Common::String code;
			if (isMethod) {
				code += "method ";
			} else {
				code += "on ";
			}
			code += _script.handlerId;
			if (_script.argumentNames.size() > 0) {
				code += " ";
				for (size_t i = 0; i < _script.argumentNames.size(); i++) {
					if (i > 0)
						code += ", ";
					code += _script.argumentNames[i];
				}
			}
			writeByteCode(0, code);
		}
		for (uint i = 0; i < _script.bytecodeArray.size(); i++) {
			LingoDec::CodeWriterVisitor code(_dot, true);
			code.indent();
			auto &bytecode = _script.bytecodeArray[i];
			code.write(LingoDec::StandardNames::getOpcodeName(bytecode.opID));
			switch (bytecode.opcode) {
			case LingoDec::kOpJmp:
			case LingoDec::kOpJmpIfZ:
				code.write(" ");
				code.write(posToString(bytecode.pos + bytecode.obj));
				break;
			case LingoDec::kOpEndRepeat:
				code.write(" ");
				code.write(posToString(bytecode.pos - bytecode.obj));
				break;
			case LingoDec::kOpPushFloat32:
				code.write(" ");
				code.write(Common::String::format("%g", (*(const float *)(&bytecode.obj))));
				break;
			default:
				if (bytecode.opID > 0x40) {
					code.write(" ");
					code.write(Common::String::format("%d", bytecode.obj));
				}
				break;
			}
			if (bytecode.translation) {
				code.write(" ...");
				while (code.lineWidth() < 49) {
					code.write(".");
				}
				code.write(" ");
				if (bytecode.translation->isExpression) {
					code.write("<");
				}
				bytecode.translation->accept(code);
				if (bytecode.translation->isExpression) {
					code.write(">");
				}
			}
			writeByteCode(bytecode.pos, code._str);
		}
		if (!_script.isGenericEvent) {
			if (!isMethod) {
				writeByteCode(node._endOffset, "end");
			}
		}
	}

	void write(uint32 offset, const Common::String &code, ImVec4 color = ImVec4(1, 1, 1, 1)) {
		renderLine(offset);
		renderIndentation();
		ImGui::TextColored(color, "%s", code.c_str());
	}

	void writeByteCode(uint32 offset, const Common::String &code) {
		renderLine(offset);
		Common::String s;
		for (int i = 0; i < _indent; i++) {
			s += "  ";
		}
		ImGui::Text("%s", (s + code).c_str());
	}

	void renderLine(uint p) {
		bool showCurrentStatement = false;
		p = MIN(p, _script.byteOffsets.size() - 1);
		uint pc = _script.byteOffsets[p];
		_script.startOffsets.push_back(pc);

		if (_isScriptInDebug && g_lingo->_exec._state == kPause) {
			// check current statement
			if (!_currentStatementDisplayed) {
				if (g_lingo->_state->pc <= pc) {
					showCurrentStatement = true;
					_currentStatementDisplayed = true;
				}
			}
		}

		ImDrawList *dl = ImGui::GetWindowDrawList();
		const ImVec2 pos = ImGui::GetCursorScreenPos();
		const float width = ImGui::GetContentRegionAvail().x;
		const ImVec2 mid(pos.x + 7, pos.y + 7);

		ImVec4 color = _state->_colors._bp_color_disabled;
		const Director::Breakpoint *bp = getBreakpoint(_script.handlerId, _script.id.member, pc);
		if (bp)
			color = _state->_colors._bp_color_enabled;

		ImGui::InvisibleButton("Line", ImVec2(16, ImGui::GetFontSize()));

		// click on breakpoint column?
		if (ImGui::IsItemClicked(0)) {
			if (color == _state->_colors._bp_color_enabled) {
				g_lingo->delBreakpoint(bp->id);
				color = _state->_colors._bp_color_disabled;
			} else {
				Director::Breakpoint newBp;
				newBp.type = kBreakpointFunction;
				newBp.scriptId = _script.id.member;
				newBp.funcName = _script.handlerId;
				newBp.funcOffset = pc;
				g_lingo->addBreakpoint(newBp);
				color = _state->_colors._bp_color_enabled;
			}
		}

		if (color == _state->_colors._bp_color_disabled && ImGui::IsItemHovered()) {
			color = _state->_colors._bp_color_hover;
		}

		// draw breakpoint
		if (!bp || bp->enabled)
			dl->AddCircleFilled(mid, 4.0f, ImColor(color));
		else
			dl->AddCircle(mid, 4.0f, ImColor(_state->_colors._line_color));

		// draw current statement
		if (showCurrentStatement) {
			dl->AddQuadFilled(ImVec2(pos.x, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 4.f), ImVec2(pos.x + 9.f, pos.y + 10.f), ImVec2(pos.x, pos.y + 10.f), ImColor(_state->_colors._current_statement));
			dl->AddTriangleFilled(ImVec2(pos.x + 8.f, pos.y), ImVec2(pos.x + 14.f, pos.y + 7.f), ImVec2(pos.x + 8.f, pos.y + 14.f), ImColor(_state->_colors._current_statement));
			if (_state->_dbg._isScriptDirty && !ImGui::IsItemVisible()) {
				ImGui::SetScrollHereY(0.5f);
			}
			dl->AddRectFilled(ImVec2(pos.x + 16.f, pos.y), ImVec2(pos.x + width, pos.y + 16.f), ImColor(IM_COL32(0xFF, 0xFF, 0x00, 0x20)), 0.4f);
		}
		// draw separator
		dl->AddLine(ImVec2(pos.x + 16.0f, pos.y), ImVec2(pos.x + 16.0f, pos.y + 17), ImColor(_state->_colors._line_color));

		ImGui::SetItemTooltip("Click to add a breakpoint");
		ImGui::SameLine();

		// draw offset
		ImGui::Text("[%5d] ", pc);
		ImGui::SameLine();
	}

	void renderIndentation(int indent) const {
		for (int i = 0; i < indent; i++) {
			ImGui::Text("  ");
			ImGui::SameLine();
		}
	}

	void renderIndentation() const {
		renderIndentation(_indent);
	}

	void indent() {
		_indent++;
	}

	void unindent() {
		if (_indent > 0)
			_indent--;
	}

	static Common::String posToString(int32 pos) {
		return Common::String::format("[%3d]", pos);
	}

private:
	ImGuiScript &_script;
	bool _showByteCode = false;
	bool _dot = false;
	int _indent = 0;
	bool _currentStatementDisplayed = false;
	bool _isScriptInDebug = false;
};

void renderScriptAST(ImGuiScript &script, bool showByteCode) {
	RenderScriptVisitor visitor(script, showByteCode);
	script.root->accept(visitor);
}


} // namespace DT
} // namespace Director
