

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.1 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.1 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.1 and/or modify it under 
//_/_/  the terms of the GNU General Public License as published by the Free 
//_/_/  Software Foundation, either version 3 of the License, or (at your 
//_/_/  option) any later version.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.1.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.1 is distributed in the hope that it will be useful,
//_/_/  but WITHOUT ANY WARRANTY; without even the implied warranty of
//_/_/  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//_/_/  GNU General Public License for more details.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#include "Logging.h"

namespace	cmlabs{


Logging::Logging() {
	logdir = "";
	maxLogStorage = 10000;
}

Logging::~Logging() {
	shutdown();
}

bool Logging::init(const char* logdir) {
	LogSystem::SetLogReceiver(this);
	if (logdir) {
		this->logdir = logdir;
		if (!ThreadManager::CreateThread(LoggingRun, this, threadID)) {
			LogPrint(0, LOG_SYSTEM, 0, "Could not start Logging thread...");
			return false;
		}
	}
	return true;
}

bool Logging::shutdown() {
	LogSystem::SetLogReceiver(NULL);
	receivers.clear();
	stop();
	fileQMutex.enter();
	while (fileQ.size()) {
		fileQ.pop();
	}
	fileQMutex.leave();
	while (logStorage.size()) {
		delete logStorage.front();
		logStorage.pop_front();
	}
	return true;
}

bool Logging::addLogEntryReceiver(LogReceiver* receiver) {
	if (receiver)
		receivers.push_back(receiver);
	return true;
}

std::list<LogEntry*> Logging::getLogEntries(uint32 source, uint8 subject, uint32 maxCount, uint8 maxLevel, uint32 maxAgeMS) {
	std::list<LogEntry*> list;

	LogEntry* entry;
	std::list<LogEntry*>::reverse_iterator logStorageIt = logStorage.rbegin(), logStorageEnd = logStorage.rend();

	uint32 count = 0;
	for (; logStorageIt != logStorageEnd; logStorageIt++) {
		if (source && ((*logStorageIt)->source != source))
			continue;
		if (subject && ((*logStorageIt)->subject != subject))
			continue;
		if (maxLevel && ((*logStorageIt)->level > maxLevel))
			continue;
		if (maxAgeMS && (GetTimeAgeMS((*logStorageIt)->time) > (int64)maxAgeMS))
			continue;

		entry = (LogEntry*)malloc((*logStorageIt)->size);
		memcpy(entry, *logStorageIt, (*logStorageIt)->size);
		list.push_front(entry);
		if (++count >= maxCount)
			break;
	}
	return list;
}

bool Logging::setModuleLogging(uint32 source, const char* name, int16 verbose, int16 debug, const char* logfile) {
	if (!source)
		return false;
	if ((verbose < 0) && (debug < 0) && (!logfile || !strlen(logfile))) {
		// nothing module-specific requested; remove any existing override
		moduleLogMutex.enter();
		moduleLogConfigs.erase(source);
		moduleLogMutex.leave();
		return true;
	}
	ModuleLogConfig cfg;
	cfg.name = name ? name : "";
	cfg.verbose = verbose;
	cfg.debug = debug;
	cfg.logfile = logfile ? logfile : "";
	moduleLogMutex.enter();
	moduleLogConfigs[source] = cfg;
	moduleLogMutex.leave();
	return true;
}

bool Logging::logEntry(LogEntry* entry) {
	if (!entry)
		return false;

	// Per-module logging: level gating and separate per-module logfile
	moduleLogMutex.enter();
	std::map<uint32, ModuleLogConfig>::iterator mit = moduleLogConfigs.find(entry->source);
	if (mit != moduleLogConfigs.end()) {
		ModuleLogConfig cfg = mit->second;
		moduleLogMutex.leave();
		// Module-specific verbose/debug level: drop entries above the level
		if ((entry->type == LOGPRINT) && (cfg.verbose >= 0) && (entry->level > cfg.verbose)) {
			free(entry);
			return true;
		}
		if ((entry->type == LOGDEBUG) && (cfg.debug >= 0) && (entry->level > cfg.debug)) {
			free(entry);
			return true;
		}
		// Module-specific logfile: write this module's lines to its own file
		if (cfg.logfile.size()) {
			std::string path = cfg.logfile;
			if (logdir.size() && (path.find('/') == std::string::npos) && (path.find('\\') == std::string::npos))
				path = logdir + "/" + path;
			std::string line = printEntry(entry);
			utils::AppendToAFile(path.c_str(), line.c_str(), (uint32)line.length());
		}
	}
	else
		moduleLogMutex.leave();

	// Output to subscribers
	uint32 len;
	if (receivers.size() == 0) {
		if (entry->type == LOGPRINT)
			std::cout << "" << PrintTimeString(entry->time) << " " << entry->getText(len) << std::endl;
		else
			std::cout << "Debug " << PrintTimeString(entry->time) << " " << entry->getText(len) << std::endl;
	}
	else {
		std::list<LogReceiver*>::iterator it, itEnd;
		for (it = receivers.begin(), itEnd = receivers.end(); it != itEnd; ++it) {
			if (*it)
				(*it)->logEntry(entry);
		}
	}

	// Save them to the log file queue
	if (logdir.size()) {
		fileQMutex.enter();
		fileQ.push(entry);
		fileQMutex.leave();
	}

	logStorage.push_back(entry);
	while (logStorage.size() > maxLogStorage) {
		free(logStorage.front());
		logStorage.pop_front();
	}

	return true;
}

std::string Logging::printEntry(LogEntry* entry) {
	if (!entry)
		return "";

	uint32 len;
	if (entry->type == LOGPRINT)
		return utils::StringFormat("%s %u %u %u %s\n", PrintTimeSortableMicrosecString(entry->time, true).c_str(), entry->level, entry->source, entry->subject, entry->getText(len));
	else
		return utils::StringFormat("Debug %s %u %u %u %s\n", PrintTimeSortableMicrosecString(entry->time, true).c_str(), entry->level, entry->source, entry->subject, entry->getText(len));
}

bool Logging::run() {

	isRunning = true;

	uint32 size = 0;
//	DataMessage* msg;
	LogEntry* entry;

	std::string logprint, filename;
	uint64 fileInterval = 3000000; // 3 seconds
	uint64 lastUpdate = GetTimeNow();

	while (shouldContinue) {
		// Move log messages to disk
		if (fileQ.size() && GetTimeAge(lastUpdate) > (int64)fileInterval) {

			// Rotate logfile every midnight
			lastUpdate = GetTimeNow();
			filename = logdir + "/" + PrintDateStringSortableDelimiter(lastUpdate, "-") + ".log";

			logprint = "";
			fileQMutex.enter();
			while (fileQ.size()) {
				entry = fileQ.front();
				fileQ.pop();
				logprint += printEntry(entry);
				//delete(entry); // don't delete, owned by logStorage
			}
			fileQMutex.leave();
			if (logprint.size())
				utils::AppendToAFile(filename.c_str(), logprint.c_str(), (uint32)logprint.size());
		}
		utils::Sleep(50);
	}

	isRunning = false;
	return true;
}


THREAD_RET THREAD_FUNCTION_CALL LoggingRun(THREAD_ARG arg) {
	if (arg == NULL) thread_ret_val(1);
	thread_ret_val((int)(((Logging*)arg)->run() ? 0 : 1));
}

}
