#pragma once

#include <SIMDString.h>

#include <tchar.h>
#include <dbghelp.h>


// #include "spdlog/cfg/env.h"   // support for loading levels from the environment variable
// #include "spdlog/fmt/ostr.h"  // support for user defined types
#include "spdlog/spdlog.h"
#include "spdlog/common.h"
#include "spdlog/sinks/stdout_color_sinks.h"


class WLog {
	private:
		void get_stack_trace() {
			this->stack_trace = "";
			// Initialize symbol handler
			SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
			if (!SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
				// std::cerr << "SymInitialize failed with error " << GetLastError() << std::endl;
				return;
			}

			// Capture the current context
			CONTEXT context;
			RtlCaptureContext(&context);

			// Initialize stack frame
			STACKFRAME64 stackFrame;
			memset(&stackFrame, 0, sizeof(STACKFRAME64));

			stackFrame.AddrPC.Offset = context.Rip;
			stackFrame.AddrPC.Mode = AddrModeFlat;
			stackFrame.AddrFrame.Offset = context.Rbp;
			stackFrame.AddrFrame.Mode = AddrModeFlat;
			stackFrame.AddrStack.Offset = context.Rsp;
			stackFrame.AddrStack.Mode = AddrModeFlat;

			int i = 0;
			// Walk the stack
			while (StackWalk64(
				IMAGE_FILE_MACHINE_AMD64, // Assume x86_64 architecture
				GetCurrentProcess(),
				GetCurrentThread(),
				&stackFrame,
				&context,
				NULL,
				SymFunctionTableAccess64,
				SymGetModuleBase64,
				NULL)) {
				if (i > 5) {
					continue;
				}

				// Get function name
				char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
				PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
				pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
				pSymbol->MaxNameLen = MAX_SYM_NAME;

				if (SymFromAddr(GetCurrentProcess(), stackFrame.AddrPC.Offset, NULL, pSymbol)) {
					if(i > 1) {
						this->stack_trace += pSymbol->Name;
						this->stack_trace += " < ";
					}
					// std::cout << pSymbol->Name << " < ";
				} else {
					// std::cerr << "SymFromAddr failed with error " << GetLastError();
				}

				// // Print file and line information
				// IMAGEHLP_LINE64 lineInfo;
				// DWORD displacement;
				// lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
				//
				// if (SymGetLineFromAddr64(GetCurrentProcess(), stackFrame.AddrPC.Offset, &displacement, &lineInfo)) {
				//     std::cout << ", File: " << lineInfo.FileName << ", Line: " << lineInfo.LineNumber << std::endl;
				// } else {
				//     std::cerr << ", SymGetLineFromAddr64 failed with error " << GetLastError() << std::endl;
				// }
				i++;
			}


			// Cleanup
			SymCleanup(GetCurrentProcess());
		}
	public:
		SIMDString<1024> file_name = "";
		char* ptr = nullptr;
		SIMDString<1024> stack_trace = "";
		std::shared_ptr<spdlog::logger> console;

		WLog() {
			this->console = spdlog::stdout_color_mt("console");
			this->console->set_pattern("%v");
		}

		void disable_logging() {
		 spdlog::set_level(spdlog::level::off);
		}

		template <typename... Args>
		void log_info(int line, const char* file, spdlog::format_string_t<Args...> fmt, Args&&... args) {
			get_line_and_file(file, line);
			spdlog::info("[{}] ", ptr);
			this->console->info(fmt, std::forward<Args>(args)...);
			this->console->info('\n');
		}

		template <typename... Args>
		void log_err(int line, const char* file, spdlog::format_string_t<Args...> fmt, Args&&... args) {
			get_line_and_file(file, line);
			spdlog::error("[{}] ", ptr);
			this->console->info(fmt, std::forward<Args>(args)...);
			this->console->info('\n');
		}

		template <typename... Args>
		void log_warn(int line, const char* file, spdlog::format_string_t<Args...> fmt, Args&&... args) {
			get_line_and_file(file, line);
			spdlog::warn("[{}] ", ptr);
			this->console->info(fmt, std::forward<Args>(args)...);
			this->console->info('\n');
		}

		void get_line_and_file(const char* file, int line) {
			this->file_name = "[";
			this->file_name += file;
			this->ptr = &this->file_name[0];
			auto g = this->file_name.find_last_of('\\');
			this->file_name += ":";
			this->file_name += std::to_string(line);
			// this->file_name += " ";
			// this->file_name += this->stack_trace;
			this->file_name = "]";
			if (g > 0) {
				ptr += g + 1;
			}
		}
};
