/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/

#include "BLogger.h"
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/regex.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/support/date_time.hpp>

#include <ctime>

namespace org
{
	namespace apache
	{
		namespace plc4x
		{
			namespace cpp
			{
				namespace utils
				{
					namespace fs = boost::filesystem;
					namespace logging = boost::log;
					namespace attrs = boost::log::attributes;

					const char* BLogger::_pDefaultFileTitle = "plc4x";
					const char* BLogger::_pDefaultFileExtension = ".log";

					BLogger* BLogger::m_pInstance = NULL;

					BLogger* BLogger::Instance()
					{
						if (!m_pInstance)
						{
							m_pInstance = new BLogger;
						}

					return m_pInstance;
					}

					void BLogger::Release()
					{
						if (m_pInstance != NULL)
						{
							delete m_pInstance;
							m_pInstance = NULL;
						}

					return;
					}

					BLogger::BLogger() :
						_iMaxFileSize(10 * 1024 * 1024),			/* default max. filesize = 10 MB */
						_uiMaxNumberOfFiles(5),
						_strFileTitle(_pDefaultFileTitle),
						_strFileExtension(_pDefaultFileExtension),
						_logFilter (BoostLogger::trivial::severity >= BoostLogger::trivial::trace),
						_severityLevel(BoostLogger::trivial::trace),
						_uiFlushCounter(0)
					{
						_iSeverityLevelNumber=getNumberfromSeverityLevel(_severityLevel);

						_uiMaxFlushEntrys[0] = 20 ; // trace
						_uiMaxFlushEntrys[1] = 10 ; // debug
						_uiMaxFlushEntrys[2] = 20 ; // info // 0 -> write once
						_uiMaxFlushEntrys[3] = 0 ; // warning // 0 -> write once
						_uiMaxFlushEntrys[4] = 0 ; // error // 0 -> write once
						_uiMaxFlushEntrys[5] = 0 ; // fatal // 0 -> write once
						
                        Initialize();

                        return;
					}

					BLogger::~BLogger()
					{
						_uiFlushCounter = _uiMaxFlushEntrys[_iSeverityLevelNumber];
						FlushLog();

                        return;
					}

					void BLogger::setFileTitle(string strFileTitle)
					{
						if (strFileTitle != _strFileTitle)
						{
							_strFileTitle = strFileTitle;
                        }

                        return;
					}

					void BLogger::setFileExtension(string strFileExtension)
					{
						if (strFileExtension != _strFileExtension)
						{
							_strFileExtension = strFileExtension;
						}

                        return;
					}

					void BLogger::setMaxFileSize(int iMaxFileSize)
					{
						if (iMaxFileSize != _iMaxFileSize)
						{
							_iMaxFileSize = iMaxFileSize; /* MB */
						}

                        return;
					}

					void BLogger::setMaxNumberOfFiles(unsigned int uiMaxNumberOfFiles)
					{
						if (uiMaxNumberOfFiles != _uiMaxNumberOfFiles)
						{
							_uiMaxNumberOfFiles = uiMaxNumberOfFiles;
						}

                        return;
					}

					void BLogger::setSeverityLogLevel(severity_level severityLevel)
					{
						_severityLevel = severityLevel;

						_iSeverityLevelNumber = getNumberfromSeverityLevel(_severityLevel);

						_logFilter = BoostLogger::trivial::severity >= severityLevel;

						BoostLogger::core::get()->set_filter(_logFilter);

                        return;
					}

					
					void BLogger::clearLogDir()
					{
						const boost::regex logFilter( "(.*\\" + _strFileExtension + ")" );

						fs::path phPathFile(_strFileTitle);
						fs::path phDir = phPathFile.parent_path();
						if (fs::exists(phDir))
						{
							fs::directory_iterator it{phDir};
							while (it != boost::filesystem::directory_iterator{})
							{
								if (fs::is_regular_file(*it))
								{
									std::string logFilename = it->path().filename().string();

									if( boost::regex_match( logFilename, logFilter ) )
									{
										boost::filesystem::remove(it->path());
									}
								}
								it++;
							}
						}

                        return;
					}

					void BLogger::Initialize()
					{

						if (_strFileTitle.length() == 0) _strFileTitle = _pDefaultFileTitle;
						if (_strFileExtension.length() == 0) _strFileExtension = _pDefaultFileExtension;
						string strLogFileName = _strFileTitle + "_%m%d%Y_%H%M%S_%N" + _strFileExtension;

						boost::shared_ptr< logging::core > core = logging::core::get();
						core->flush();
						core->remove_all_sinks();
						core->add_global_attribute("TimeStamp", attrs::local_clock());

						boost::shared_ptr< sinks::text_file_backend > backend =
								boost::make_shared< sinks::text_file_backend >
						(
								keywords::file_name = strLogFileName.c_str(),
								keywords::rotation_size = _iMaxFileSize * 1024 * 1024,
								keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0)
						);

						fs::path phDirPath(strLogFileName);

						backend->set_file_collector(sinks::file::make_collector
								(
									keywords::target = phDirPath.parent_path().c_str(),
									keywords::max_size = (_uiMaxNumberOfFiles - 1)  * _iMaxFileSize * 1024 * 1024,
									keywords::min_free_space = 50 * 1024 * 1024
								));

						typedef sinks::synchronous_sink< sinks::text_file_backend > sink_t;
						boost::shared_ptr< sink_t > sink(new sink_t(backend));

						sink->set_formatter
							(
									expr::stream
									<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "[%d.%m. %H:%M:%S.%f] ")
									<< " <" << BoostLogger::trivial::severity
									<< "> : " << expr::message
							);

						core->add_sink(sink);

                        return;
					}

					void  BLogger::Log(const char* pMessage, severity_level logLevel /*= info*/)
					{
						try
						{
							if (isLog(logLevel))
							{
								_mtxLock.lock();

								BOOST_LOG_SEV(_lg, severity_level::info) << "ThreadID 0x" << boost::this_thread::get_id() << " " << pMessage;

								if(logLevel == severity_level::fatal)
									ForceFlushLog();
								else
									FlushLog();

								_mtxLock.unlock();
							}
						}
						catch(...)
						{
							_mtxLock.unlock();
						}

                        return;
					}

					void  BLogger::Log(string strMessage, severity_level logLevel /* = info */)
					{
						try
						{
							if (isLog(logLevel))
							{
								_mtxLock.lock();

								BOOST_LOG_SEV(_lg, logLevel) << "ThreadID 0x" << boost::this_thread::get_id() << " " << strMessage;

								if(logLevel == severity_level::fatal)
									ForceFlushLog();
								else
									FlushLog();

								_mtxLock.unlock();
							}
						}
						catch(...)
						{
							_mtxLock.unlock();
						}

                        return;
					}

					void  BLogger::LogBool(bool b, severity_level logLevel /*= info*/)
					{

						try
						{
							if (isLog(logLevel))
							{
								_mtxLock.lock();

								string strBoolMessage = b ? _strTrueMessage : _strFalseMessage;
								BOOST_LOG_SEV(_lg, logLevel) << "ThreadID 0x" << boost::this_thread::get_id() << " " << strBoolMessage;

								if(logLevel == severity_level::fatal)
									ForceFlushLog();
								else
									FlushLog();

								_mtxLock.unlock();
							}
						}
						catch(...)
						{
							_mtxLock.unlock();
						}

                        return;
					}

					void  BLogger::LogBool(string strMessage, bool b, severity_level logLevel /*= info*/)
					{
						try
						{
							if (isLog(logLevel))
							{
								_mtxLock.lock();

								string strBoolMessage = b ? _strTrueMessage : _strFalseMessage;
								BOOST_LOG_SEV(_lg, logLevel) << "ThreadID 0x" << boost::this_thread::get_id() << " " << strMessage << ' ' << strBoolMessage;

								if(logLevel == severity_level::fatal)
									ForceFlushLog();
								else
									FlushLog();

								_mtxLock.unlock();
							}
						}
						catch(...)
						{
							_mtxLock.unlock();
						}

                        return;
					}

					void BLogger::LogHexDump(unsigned char* pBinaryData, unsigned int uiNumBytes, const char* pDumpPrefix /*= NULL*/, severity_level logLevel /*= info*/)
					{
						try
						{
							if (isLog(logLevel))
							{
								_mtxLock.lock();

								DatDmp dump;
								dump.setLeadingNewLine(true);
								dump.setPrefix(pDumpPrefix);
								string strDumpMessage = dump.Show(pBinaryData, uiNumBytes);
								BOOST_LOG_SEV(_lg, logLevel) << "ThreadID 0x" << boost::this_thread::get_id() << " " << strDumpMessage;

                                if (logLevel == severity_level::fatal)
                                {
                                    ForceFlushLog();
                                }
                                else
                                {
                                    FlushLog();
                                }

								_mtxLock.unlock();
							}
						}
						catch(...)
						{
							_mtxLock.unlock();
						}

                        return;
					}

					void BLogger::FlushLog(void)
					{
						if(_uiFlushCounter >= _uiMaxFlushEntrys[_iSeverityLevelNumber])
						{
							BOOST_LOG_SEV(_lg, boost::log::trivial::severity_level::info) << "ThreadID 0x" << boost::this_thread::get_id() << " Flush LogEntries";
							logging::core::get()->flush();
							_uiFlushCounter = 0;
						}
						else
						{
							_uiFlushCounter++;
						}

                        return;
					}

					void BLogger::ForceFlushLog(void)
					{
						unsigned int ui = _uiMaxFlushEntrys[_iSeverityLevelNumber];
						_uiMaxFlushEntrys[_iSeverityLevelNumber] = 0;
						FlushLog();
						_uiMaxFlushEntrys[_iSeverityLevelNumber] = ui;

                        return;
					}

					void BLogger::setSeverityLogLevelNumber(int iSeverityLevelNumber)
					{

						_iSeverityLevelNumber = iSeverityLevelNumber;

						_severityLevel = getSeverityLevelfromNumber(_iSeverityLevelNumber);

						setSeverityLogLevel(_severityLevel);

                        return;
					}

					bool BLogger::isLog(severity_level logLevel)
					{
						bool bResult = false;

						try
						{
							int ilogLevel = getNumberfromSeverityLevel(logLevel);

							if (_iSeverityLevelNumber <= ilogLevel)
							{
								bResult = true;
							}
						}
						catch(...)
						{
						}

						return bResult;
					}

					severity_level BLogger::getSeverityLevelfromNumber(int iSeverityLevelNumber)
					{
						severity_level logLevel = boost::log::trivial::severity_level::trace;

						switch (iSeverityLevelNumber)
						{
							case 0: logLevel = boost::log::trivial::severity_level::trace; break;
							case 1: logLevel = boost::log::trivial::severity_level::debug; break;
							case 2: logLevel = boost::log::trivial::severity_level::info; break;
							case 3: logLevel = boost::log::trivial::severity_level::warning; break;
							case 4: logLevel = boost::log::trivial::severity_level::error; break;
							case 5: logLevel = boost::log::trivial::severity_level::fatal; break;
							default: logLevel = boost::log::trivial::severity_level::trace; break;
						}

						return logLevel;
					}

					int BLogger::getNumberfromSeverityLevel(severity_level severityLevel)
					{
						int iSeverityLevelNumber = 0;

						switch (severityLevel)
						{
							case boost::log::trivial::severity_level::trace: iSeverityLevelNumber = 0; break;
							case boost::log::trivial::severity_level::debug: iSeverityLevelNumber = 1; break;
							case boost::log::trivial::severity_level::info: iSeverityLevelNumber = 2; break;
							case boost::log::trivial::severity_level::warning: iSeverityLevelNumber = 3; break;
							case boost::log::trivial::severity_level::error: iSeverityLevelNumber = 4; break;
							case boost::log::trivial::severity_level::fatal: iSeverityLevelNumber = 5; break;
						}

						return iSeverityLevelNumber;
					}
				}
			}
		}
	}
}
