/*
 * 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;
					}
				}
			}
		}
	}
}
