#pragma once

#ifdef WIN32
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
//#include <Windows.h>
#include "win32.h"
#endif

// uncomment line below to disable OutputDebugString
// #define NO_WINDOWS

#include <assert.h>
#include <mutex>
#include <fstream>
#include <string>
#include <memory>
#include <cstring>
#include <cstdint>
#include <cstdarg>
#include <algorithm>
#include <boost/noncopyable.hpp>
#include "utf8.h"
#include "chrono_wrapper.h"
#include "dp/singleton.h"

namespace jlib
{

#define JLOG_ORIGIN

#define JLOG jlib::log::log_utf16
#define JLOGA jlib::log::log_utf8
#define JLOGW jlib::log::log_utf16
#define JLOGB(b, l) jlib::log::dump_hex(b, l)
#define JLOGASC(b, l) jlib::log::dump_ascii(b, l)

#define JLOG_ERRO JLOGA
#define JLOG_INFO JLOGA
#define JLOG_HEX JLOGB
#define JLOG_ASC JLOGASC

#ifdef __GNUG__
inline int _vsnwprintf_s(wchar_t* __restrict__ __s, size_t n, size_t, const wchar_t* __restrict__ __format, __gnuc_va_list __arg) {
	return std::vswprintf(__s, n, __format, __arg);
}
inline int _vsnprintf_s(char* __restrict__ __s, size_t __max_len, size_t, const char* __restrict__ __format, __gnuc_va_list __arg) {
	return std::vsnprintf(__s, __max_len, __format, __arg);
}
#endif

class log : public dp::singleton<log>
{
	enum { max_output_size = 1024 * 64, max_single_log_file_size = 1024 * 1024 * 10 };

private:
	bool log_to_file_ = false;
	bool log_to_dbg_view_ = true;
	bool log_to_console_ = true;
	bool running_ = false;
	std::ofstream log_file_;
	std::string log_file_foler_ = "";
	std::string log_file_path_ = "";
	std::string line_prefix_ = "";
	std::string log_file_prefix_ = "";
	std::mutex lock_;

public:
	// initializers, they should be called right after get_instance
	void set_line_prifix(const std::string& prefix) { line_prefix_ = prefix; }

	void set_output_to_dbg_view(bool b = true) { log_to_dbg_view_ = b; }
	
	void set_output_to_console(bool b = true) { log_to_console_ = b; }

	// 3 functions below must be called by sequence
	void set_log_file_foler(const std::string& folder_path) { log_file_foler_ = folder_path.empty() ? "" : folder_path + "\\"; }
	void set_log_file_prefix(const std::string& prefix) { log_file_prefix_ = prefix; }
	void set_output_to_file(bool b = true) { log_to_file_ = b; if (b) create_file_name(); }

	std::string get_log_file_path() const { return log_file_path_; }

public:
	
	~log()
	{
		running_ = false;

		if (log_file_.is_open()) {
			log_file_.close();
		}

#ifdef _DEBUG
		output_to_dbg_view("log::~log\n");
#endif
	}

public:

	// static operations

	static void dump_hex(const char* buff, size_t buff_len)
	{
		try {
			auto instance = log::get_instance();

			if (instance->log_to_file_ || instance->log_to_dbg_view_ || instance->log_to_console_) {
				size_t output_len = buff_len * 6 + 64;
				std::unique_ptr<char[]> output = std::unique_ptr<char[]>(new char[output_len]);
				output[0] = 0;
				char c[64] = { 0 };
				std::sprintf(c, "len %d\n", buff_len);
				std::strcat(output.get(), c);
				for (size_t i = 0; i < buff_len; i++) {
					std::sprintf(c, "%02X ", static_cast<unsigned char>(buff[i]));
					std::strcat(output.get(), c);
					if (i > 0 && (i + 1) % 16 == 0) {
						std::strcat(output.get(), "\n");
					}
				}
				std::strcat(output.get(), "\n");
				instance->output(output.get());
			}
		} catch (...) {
			assert(0);
		}
	}

	static void dump_ascii(const char* buff, size_t buff_len)
	{
		try {
			auto instance = log::get_instance();

			if (instance->log_to_file_ || instance->log_to_dbg_view_ || instance->log_to_console_) {
				size_t output_len = buff_len * 6 + 64;
				std::unique_ptr<char[]> output = std::unique_ptr<char[]>(new char[output_len]);
				output[0] = 0;
				char c[64] = { 0 };
				std::sprintf(c, "len %d\n", buff_len);
				std::strcat(output.get(), c);
				for (size_t i = 0; i < buff_len; i++) {
					std::sprintf(c, "%C ", static_cast<unsigned char>(buff[i]));
					std::strcat(output.get(), c);
					if (i > 0 && (i + 1) % 16 == 0) {
						std::strcat(output.get(), "\n");
					}
				}
				std::strcat(output.get(), "\n");
				instance->output(output.get());
			}
		} catch (...) {
			assert(0);
		}
	}

	static void log_utf16(const wchar_t* format, ...)
	{
		try {
			auto instance = log::get_instance();

			if (instance->log_to_file_ || instance->log_to_dbg_view_ || instance->log_to_console_) {
				wchar_t buf[max_output_size], *p;
				p = buf;
				va_list args;
				va_start(args, format);
				p += _vsnwprintf_s(p, max_output_size - 1, max_output_size - 1, format, args);
				va_end(args);
				while ((p > buf) && (*(p - 1) == '\r' || *(p - 1) == '\n'))
					*--p = '\0';
				*p++ = '\r';
				*p++ = '\n';
				*p = '\0';

#if defined(WIN32)
				auto msg = instance->format_msg(utf8::u16_to_mbcs(buf));
#else
				auto msg = instance->format_msg(utf8::w2a(buf));
#endif
				instance->output(msg);
			}
		} catch (...) {
			assert(0);
		}
	}

	static void log_utf8(const char* format, ...)
	{
		try {
			auto instance = log::get_instance();

			if (instance->log_to_file_ || instance->log_to_dbg_view_ || instance->log_to_console_) {
				char buf[max_output_size], *p;
				p = buf;
				va_list args;
				va_start(args, format);
				p += _vsnprintf_s(p, max_output_size - 1, max_output_size - 1, format, args);
				va_end(args);
				while ((p > buf) && (*(p - 1) == '\r' || *(p - 1) == '\n'))
					*--p = '\0';
				*p++ = '\r';
				*p++ = '\n';
				*p = '\0';

				instance->output(instance->format_msg(buf));
			}
		} catch (...) {
			assert(0);
		}
	}

	std::string format_msg(const std::string& msg) {
		return line_prefix_ + " " + now_to_string(true) + " ---- " + msg;
	}


protected:

	log() {
		running_ = true;
		char out[128] = { 0 };
		sprintf(out, "log construction addr: %p\n", this);
		output_to_dbg_view(out);
	}

	bool open_log_file() {
		if (!log_file_.is_open()) {
			log_file_.open(log_file_path_);
		}
		return log_file_.is_open();
	}

	void create_file_name() {
		auto s = now_to_string();
		std::replace(s.begin(), s.end(), ' ', '_');
		std::replace(s.begin(), s.end(), ':', '-');
		log_file_path_ = log_file_foler_ + log_file_prefix_ + "." + s + ".log";
	}

	void output(const std::string& msg) {
		std::lock_guard<std::mutex> lock(lock_);
		if (log_to_file_) {
			output_to_log_file(msg);
		}

		if (log_to_dbg_view_) {
			output_to_dbg_view(msg);
		}

		if (log_to_console_) {
			std::printf(msg.c_str());
		}
	}

	void output_to_log_file(const std::string& msg) {
		if (log_file_.is_open()) {
			auto size = log_file_.tellp();
			if (size > max_single_log_file_size) {
				log_file_.close();
				create_file_name();
				open_log_file();
			}
		}

		if (!log_file_.is_open()) {
			open_log_file();
		}

		if (log_file_.is_open()) {
			log_file_.write(msg.c_str(), msg.size());
			log_file_.flush();
		}
	}

	void output_to_dbg_view(const std::string& msg) {
#if defined(WIN32) && !defined(NO_WINDOWS)
		OutputDebugStringA(msg.c_str());
#else
		std::printf(msg.c_str());
#endif
	}

	

};


class range_log
{
private:
	std::string msg_;
	std::chrono::steady_clock::time_point begin_;

public:
	range_log(const std::string& msg) : msg_(msg) {
		JLOGA((msg_ + " in").c_str());
		begin_ = std::chrono::steady_clock::now();
	}

	range_log(const std::wstring& msg) : msg_() {
		msg_ = utf8::w2a(msg);
		JLOGA((msg_ + " in").c_str());
		begin_ = std::chrono::steady_clock::now();
	}

	~range_log() {
		auto diff = std::chrono::steady_clock::now() - begin_;
		auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(diff);
		JLOGA("%s out, duration: %d(ms)\n", msg_.c_str(), msec.count());
	}
};

#define AUTO_LOG_FUNCTION jlib::range_log __log_function_object__(__func__);





};