#ifndef SCENE_SYNCHRONIZER_DEBUGGER_H
#define SCENE_SYNCHRONIZER_DEBUGGER_H

/*************************************************************************/
/*  scene_synchronizer_debugger.h                                        */
/*************************************************************************/
/*                         This file is part of:                         */
/*                          PANDEMONIUM ENGINE                           */
/*             https://github.com/Relintai/pandemonium_engine            */
/*************************************************************************/
/* Copyright (c) 2022-present Péter Magyar.                              */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* 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.                */
/*************************************************************************/

#include "scene/main/node.h"

#ifdef DEBUG_ENABLED

#include "core/containers/oa_hash_map.h"

#endif

class SceneSynchronizer;
class SceneTree;

class SceneSynchronizerDebugger : public Node {
	GDCLASS(SceneSynchronizerDebugger, Node);

	static SceneSynchronizerDebugger *the_singleton;

public:
	struct TrackedNode {
		Node *node;
		List<PropertyInfo> *properties;
		TrackedNode() = default;
		TrackedNode(Node *p_node) :
				node(p_node) {}
		TrackedNode(Node *p_node, List<PropertyInfo> *p_properties) :
				node(p_node), properties(p_properties) {}
		bool operator==(const TrackedNode &p_t) const { return p_t.node == node; }
	};

	enum DataBufferDumpMode {
		NONE,
		WRITE,
		READ
	};

	enum FrameEvent : uint32_t {
		EMPTY = 0,
		CLIENT_DESYNC_DETECTED = 1 << 0,
		CLIENT_DESYNC_DETECTED_SOFT = 1 << 0,
	};

public:
	static SceneSynchronizerDebugger *singleton();
	static void _bind_methods();

private:
#ifdef DEBUG_ENABLED
	bool dump_enabled = false;
	LocalVector<StringName> dump_classes;
	bool setup_done = false;

	uint32_t log_counter = 0;
	SceneTree *scene_tree = nullptr;
	String main_dump_directory_path;
	String dump_name;

	LocalVector<TrackedNode> tracked_nodes;
	// HaskMap between class name and property list: to avoid fetching the property list per object each frame.
	OAHashMap<StringName, List<PropertyInfo>> classes_property_lists;

	// Dictionary of dictionary containing nodes info.
	Dictionary frame_dump__begin_state;

	// Dictionary of dictionary containing nodes info.
	Dictionary frame_dump__end_state;

	// The dictionary containing the data buffer operations performed by the controllers.
	Dictionary frame_dump__node_log;

	// The controller name for which the data buffer operations is in progress.
	String frame_dump__data_buffer_name;

	// A really small description about what happens on this frame.
	FrameEvent frame_dump__frame_events = FrameEvent::EMPTY;

	// This Array contains all the inputs (stringified) written on the `DataBuffer` from the
	// `_controller_process` function
	Array frame_dump__data_buffer_writes;

	// This Array contains all the inputs (stringified) read on the `DataBuffer` from the
	// `_controller_process` function
	Array frame_dump__data_buffer_reads;

	// This Dictionary contains the comparison (`_are_inputs_different`) fetched by this frame, and
	// the result.
	Dictionary frame_dump__are_inputs_different_results;

	DataBufferDumpMode frame_dump_data_buffer_dump_mode = NONE;

	bool frame_dump__has_warnings = false;
	bool frame_dump__has_errors = false;
#endif

public:
	SceneSynchronizerDebugger();
	~SceneSynchronizerDebugger();

	void set_dump_enabled(bool p_dump_enabled);
	bool get_dump_enabled() const;

	void register_class_for_node_to_dump(Node *p_node);
	void register_class_to_dump(const StringName &p_class);
	void unregister_class_to_dump(const StringName &p_class);

	void setup_debugger(const String &p_dump_name, int p_peer, SceneTree *p_scene_tree);

private:
	void prepare_dumping(int p_peer, SceneTree *p_scene_tree);
	void setup_debugger_python_ui();
	void track_node(Node *p_node, bool p_recursive);
	void on_node_added(Node *p_node);
	void on_node_removed(Node *p_node);

public:
	void write_dump(int p_peer, uint32_t p_frame_index);
	void start_new_frame();

	void scene_sync_process_start(const SceneSynchronizer *p_scene_sync);
	void scene_sync_process_end(const SceneSynchronizer *p_scene_sync);

	void databuffer_operation_begin_record(Node *p_node, DataBufferDumpMode p_mode);
	void databuffer_operation_end_record();
	void databuffer_write(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable);
	void databuffer_read(uint32_t p_data_type, uint32_t p_compression_level, Variant p_variable);

	void notify_input_sent_to_server(Node *p_node, uint32_t p_frame_index, uint32_t p_input_index);
	void notify_are_inputs_different_result(Node *p_node, uint32_t p_other_frame_index, bool p_is_similar);

	void add_node_message(Node *p_node, const String &p_message);
	void add_node_message_by_path(const String &p_node_path, const String &p_message);

	void debug_print(Node *p_node, const String &p_message, bool p_silent = false);
	void debug_warning(Node *p_node, const String &p_message, bool p_silent = false);
	void debug_error(Node *p_node, const String &p_message, bool p_silent = false);

	void notify_event(FrameEvent p_event);

private:
	void dump_tracked_objects(const SceneSynchronizer *p_scene_sync, Dictionary &p_dump);
};

#endif