﻿
//  copyleft by jinzhongjie(xjinzhongjie@gmail.com).anyone who want change the code please tell writer.
//  Everyone is permitted to copy and distribute verbatim copies
//  of this license document, but changing it is not allowed.
#ifndef _LOGGER_
#define _LOGGER_
#include <cstdarg>
#include <vector>
#include <string>
#include<sstream>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <map>
#include "log2.h"
#include "rt_sts.h"

// #undef  __GNUC__
#if defined(__linux__) || defined(__CYGWIN__)
#include <sys/time.h>
#else
#include <sys/timeb.h>
//#ifndef _timeb
//#define  _timeb  timeb
//#endif
//#ifndef _ftime
//#define  _ftime   ftime
//#endif
#endif

#if defined(_MSC_VER) && _MSC_VER < 1600
#define va_copy(dest, src) (dest = src)
#endif

using namespace std;
/*
* ロガークラス。 
*
*    author: ぶらざーほわいつ
*    date: 2006/09/13 10:10:13
* 
* 説明: 
* JavaのLoggerもどきです。
*
* 使い方： 
*
* #include "Logger.h"
*
* Logger::configure("./logger.ini");
* Logger::info("いんふぉ", __FILE__, __func__);
* Logger::debug("でばっぐ", __FILE__, __func__);
* Logger::warn("わーん", __FILE__, __func__);
* Logger::error("えらー", __FILE__, __func__); 
*
* 上記の処理で "./logger.log" というログファイルが吐き出されます。
* 出力レベルの調整は "./logger.ini" で行えます。 
*
*/


class Logger:public Log
{ 
private:
	class LoggerMsg{
public:
		struct tm oTs;
		int nMilli;
		int nStatus;
		std::string sFile;
		std::string sFunc;
		int nLine;
		std::string sMessage;
		// friend void Logger::makeMsg(const LoggerMsg& );
		
		LoggerMsg(
			struct tm& _timestamp
			, int &_nMilli
			, int &_nStatus
			, std::string &_sFile
			, int &_nLine
			, std::string &_sFunc
			, std::string &_sMessage
		){
			this->oTs = _timestamp;
			this->nMilli = _nMilli;
			this->nStatus = _nStatus;
			this->sFile = _sFile;
			this->nLine = _nLine;
			this->sFunc = _sFunc;
			this->sMessage = _sMessage;
		};
	};
	string sLogTimeStamp;
	bool open;
	int	bLogOutPut;
	int	nLogLevel;
	// std::queue<std::shared_ptr<LoggerMsg>>   msg_queue;
	std::queue<LoggerMsg*> msg_queue;
	// std::mutex                               trans_mutex;
	// std::condition_variable                  run_cv;
	//std::thread                              exec_thread;
	PthreadManager  exec_thread;
	Mutex  trans_mutex;
	
	Log  *pLogger;
	map<string, string > iniMap;
	map<int, string >  *pErrorMap;
	
	Logger& operator = (Logger const&);
protected:
	virtual void thread_handle(){
		while (1){
			// unique_lock<mutex> lck(this->trans_mutex);
			Locker  lck(this->trans_mutex);
			while (msg_queue.empty()){
				if (!open){
					return;
				}
				// this->run_cv.wait(lck);
				lck.Wait();
			}

			// shared_ptr<LoggerMsg> msg = msg_queue.front();
			LoggerMsg* msg = msg_queue.front();
			lck.Unlock();
			makeMsg(*msg);
			delete msg;
			lck.Lock();
			msg_queue.pop();
		}
	};
	Logger(): msg_queue(),exec_thread(){
		open = false;
		bLogOutPut = false;
		nLogLevel = MSK_M_FATAL;
		pLogger = NULL;
		pErrorMap = NULL;
		/*
		nPolicy = SCHED_OTHER;
		nScope = PTHREAD_SCOPE_SYSTEM;
		oParam.sched_priority = 0;
		*/
		// init();
	}
	
private:
	void makeMsg(const LoggerMsg& _oLogMsg)
	{
		if(!bLogOutPut)
			return;
		int  nStatus = _oLogMsg.nStatus & 0xFF;
		if (nStatus < nLogLevel)
		{
			return;
		}
		
		//timeval curTime;
		// gettimeofday(&curTime, NULL);
#if defined(__linux__) || defined(__CYGWIN__)
	timeval curTime;
	gettimeofday(&curTime, NULL);
	 int nMilli = curTime.tv_usec / 1000;
	struct tm *pTm = localtime(&curTime.tv_sec);
#else
	_timeb  curTime;
	_ftime(&curTime);

	int nMilli = curTime.millitm;
	struct tm *pTm = localtime(&curTime.time);
#endif

		/*
		string	file = _oLogMsg.sFile;
		const size_t last_slash_idx = file.find_last_of("\\/");
		if (std::string::npos != last_slash_idx)
		{
			file.erase(0, last_slash_idx + 1);
		}
		*/
		std::stringstream  s;
		
		string  sStatus = "UNKNOWN";
		if (pErrorMap)
		{
			map<int, string>::iterator itr;
			// キーから値を検索
			itr = pErrorMap->find(_oLogMsg.nStatus);
			if (itr != pErrorMap->end()){
				sStatus = itr->second;
			}
		}
		
		s << std::left << setw(15)<< sStatus   << "[" << setw(7)<< _oLogMsg.sFunc << " ] " << _oLogMsg.sMessage  << " (" <<  _oLogMsg.sFile << ":" << _oLogMsg.nLine << ")";
		
		string   sMsg(s.str());
		if (pLogger){
			pLogger->write(*pTm, nMilli, sMsg);
		}
		
		return;
	}
	
	string getValue(string key){
		// イテレータを生成
		map<string, string>::iterator itr;
		// キーから値を検索
		itr = iniMap.find(key);
		if (itr != iniMap.end()){
			// キーを取得します。
			string key = itr->first;
			// 値も取得します。
			string value = itr->second;
			return value;
		}
		return "";
	}
	
	/**
	* split
	* @param target string
	* @param delimiter
	* @param first  of two splited
	* @param second of two splited
	*/
	void splitstring(string str, string seperater, string &first, string &second)
	{
		string::size_type i = str.find(seperater); //find seperator
		if (i != string::npos)
		{
			string::size_type y = 0;
			if (!str.empty())
			{
				while (y != i)
				{
					first += str[y++]; //creating first string
				}
				y = y+seperater.length(); //jumping forward seperater length
				while (y != str.length())
				{
					second += str[y++]; //creating second string
				}
				
			}
		}
		else
		{
			first = str;
			second = "NULL"; //if seperator is not there then second string == null 
		}
	}
	
public:
	void write_Format(int _nStatus, std::string& _sFile, int& _nLine, std::string& _sFunc, const char * sFormat, ...)
	{
		if(!bLogOutPut)
			return;
		// initialize use of the variable argument array
		va_list vaArgs;
		va_start(vaArgs, sFormat);
		// reliably acquire the size from a copy of
		// the variable argument array
		// and a functionally reliable call
		// to mock the formatting
		va_list vaCopy;
		va_copy(vaCopy, vaArgs);
		const int iLen = vsnprintf(NULL, 0, sFormat, vaCopy);
		va_end(vaCopy);
		
		// return a formatted string without
		// risking memory mismanagement
		// and without assuming any compiler
		// or platform specific behavior
		std::vector<char> zc(iLen+1);
		vsnprintf(&zc[0], zc.size(), sFormat, vaArgs);
		va_end(vaArgs);

		std::string	sMsg(&zc[0], zc.size()-1);
		write(_nStatus, _sFile, _nLine, _sFunc, sMsg);
		
		return;
	} 
	
	void write(int _nStatus, std::string& _sFile, int& _nLine, std::string& _sFunc, std::string& _sMsg)
	{
		if(!bLogOutPut)
			return;
		// timeval curTime;
		// gettimeofday(&curTime, NULL);
#if defined(__linux__) || defined(__CYGWIN__)
	timeval curTime;
	gettimeofday(&curTime, NULL);
	 int nMilli = curTime.tv_usec / 1000;
	struct tm *pTm = localtime(&curTime.tv_sec);
#else
	_timeb  curTime;
	_ftime(&curTime);

	int nMilli = curTime.millitm;
	struct tm *pTm = localtime(&curTime.time);
#endif
		// int nMilli = curTime.tv_usec / 1000;
		// struct tm *pTm = localtime(&curTime.tv_sec);
		// shared_ptr<LogMsg> msg = make_shared<LogMsg>(ts, message);
		// shared_ptr<LoggerMsg> msg = make_shared<LoggerMsg>(*pTm, nMilli, _nStatus, _sFile, _nLine, _sFunc, _sMsg);
		LoggerMsg* msg = new LoggerMsg(*pTm, nMilli, _nStatus, _sFile, _nLine, _sFunc, _sMsg);
		if (!open){
			// lck.unlock();
			// lck.Unlock();
			std::string  sErr = "(Logger::write)Log is not open!"; //"test--------------";  // 
			std::string  sOutput=sErr+ " [" + _sMsg + "] ";
			
			throw runtime_error( sOutput);
		}
		// unique_lock<mutex> lck(trans_mutex);
		Locker  lck(this->trans_mutex);
		bool queue_empty = msg_queue.empty();
		msg_queue.push(msg);
		if (queue_empty){
			// run_cv.notify_one();
			lck.notify_one();
		}
	}
	
	bool init(string inifilePath = "./Debug/logger.ini", map<int, string >  *_pErrorMap = NULL){
		Log& oLog = Log::getInstance();
		pLogger = &oLog;
		pErrorMap = _pErrorMap;

#if defined(__linux__) || defined(__CYGWIN__)
	timeval curTime;
	gettimeofday(&curTime, NULL);
	 int nMilli = curTime.tv_usec / 1000;
	struct tm *pTm = localtime(&curTime.tv_sec);
#else
	_timeb  curTime;
	_ftime(&curTime);

	int nMilli = curTime.millitm;
	struct tm *pTm = localtime(&curTime.time);
#endif
		
		
		
		
		std::stringstream  s;

		s << pTm->tm_year + 1900 << setfill('0') << setw(2) << pTm->tm_mon+1 << setw(2) << pTm->tm_mday << setw(2) << pTm->tm_hour << setw(2) << pTm->tm_min << setw(2) << pTm->tm_sec;

		sLogTimeStamp = s.str();
	
		iniMap.clear();
		// 読み込み用ファイルストリーム
		ifstream fin;
		// ファイルオープン
		fin.open(inifilePath.c_str());
		if (fin.fail()){
			cout << "Opening the input file failed." << endl;
			// cout << "何か整数値を入力して終了。" << endl;
			// int ck;
			// cin >> ck;
			return false;
		}
		while (!fin.eof()){
			string line;
			string key;
			string value;
			fin >> line;
			Logger::splitstring(line, "=", key, value);
			if (key == "NULL" || value == "NULL"){
				continue;
			}
			iniMap.insert(pair<string, string>(key, value));
		}
		// ファイルクローズ
		fin.close();
		

		// キーから値を検索
		string key = "LOG_OUTPUT";
		string logFlg = getValue(key);
		if (logFlg == "ON"){
			bLogOutPut=true;
		}

		// キーから値を検索
		key = "LOG_LEVEL";
		string logLevelStringFromIni = getValue(key);
		
		nLogLevel = MSK_M_FATAL;
		if (logLevelStringFromIni == "DEBUG"){
			nLogLevel = MSK_M_DEBUG;
		} else if (logLevelStringFromIni == "INFO"){
			nLogLevel = MSK_M_INFORMATION;
		} else if (logLevelStringFromIni == "WARN"){
			nLogLevel = MSK_M_WARNING;
		} else if (logLevelStringFromIni == "ERROR"){
			nLogLevel = MSK_M_ERROR;
		}
		
		return true;
	}
	void openLog(const char* filename){
		if(!bLogOutPut)
			return;

		string  sfile(filename);
		sfile += "_" + sLogTimeStamp + ".log";
		if (pLogger){
			pLogger->openLog(sfile.c_str());
		}
		open = true;
		// thread t(&Logger::thread_handle, this);
		// exec_thread.swap(t);
		exec_thread.run(_run_thread_log , this);
		/*
		int nResult=0;
		pthread_attr_t oAttr;
		memset(&oAttr, 0x00, sizeof(oAttr));
		pthread_attr_init(&oAttr);

		nResult = pthread_attr_setschedpolicy(&oAttr, nPolicy);
		if (nResult != 0)
		{
			throw ios::failure("Error pthread_attr_setschedpolicy");	
		}

		oParam.sched_priority = 0;
		nResult = pthread_attr_setschedparam(&oAttr, &this->oParam);
		if (nResult != 0)
		{
			throw ios::failure("Error pthread_attr_setschedparam");	
		}
		nResult = pthread_attr_setscope(&oAttr, nScope);
		if (nResult != 0)
		{
			throw ios::failure("Error pthread_attr_setschedparam");	
		}
		this->nThreadID = {0};
		nResult = pthread_create(&(this->nThreadID), &oAttr, _run_thread_log, this);
		*/
	}
	void closeLog(){

		if(!bLogOutPut)
			return;
		// unique_lock<mutex> lck(trans_mutex);
		Locker  lck(this->trans_mutex);
		if (!open)
			return;
		open = false;
		// run_cv.notify_one();
		lck.notify_one();
		// lck.unlock();
		lck.Unlock();
		// exec_thread.join();

		exec_thread.join();
		/*
		void * pThreadStatus;
		pThreadStatus = NULL;
		int nStatus=0;
		nStatus = pthread_join(this->nThreadID, &pThreadStatus);
		switch(nStatus)
		{
			case 0:
				break;
			case ESRCH:
				throw ios::failure("Error ESRCH:NOT FOUND");
				break;
			case EDEADLK:
				throw ios::failure("Error EDEADLK:DEADLOCK");
				break;
			default: 
				throw ios::failure("Error  ");
				break;
		}
		*/
		
		if (pLogger){
			pLogger->closeLog();
			pLogger = NULL;
		}
	}
	
	static Logger& getInstance(){
		static Logger instance;
		return instance;
	}
};




#endif