//
//	Copyright  Nemetschek Vectorworks, Inc.
//	Use of this file is governed by the Nemetschek Vectorworks SDK License Agreement
//	http://developer.vectorworks.net/index.php?title=Vectorworks_SDK_License
//


#include "StdHeaders.h"	// DO NOT MOVE THIS LINE FROM BEING FIRST NON-COMMENT LINE.  [DMB, 10/30/95]

#include <StdArg.h>
#include <time.h>
#if _WINDOWS
#include <process.h>
#else
#include <unistd.h>
#include <sys/time.h>
#endif

#if GS_MAC
#include "NNAVectorworksSDKProbes.h"
#endif

#ifndef _MINICAD_
#include "VectorworksSDK.h"
#endif

#ifdef _MINICAD_
	#include "rapidjson/rapidjson.h"
	#include "rapidjson/document.h"
	#include "rapidjson/stringbuffer.h"
	#include "rapidjson/writer.h"
	#include "File.X.h"
	#include "ApplicationFolders.h"
    #include "AppProtection.h"
    #include "ApplicationUpdater.h"
    #include "UVersionNumber.h"
	#include "Kernel/GSLogging.h"
	#include "Singleton.X.h"
	#include "TMutex.X.h"
	// from Builtins.h/.cpp
	double GetSecondsSinceLaunch(void);
    double GetTimeAtLaunch(void);
	long long GetStartupTime(void);
	double gLogTime = 0.0;
#elif _WINDOWS
	//
#endif

#if _WINDOWS
#include "..\..\..\..\ShellWin\Resource.h"
#include "..\..\..\..\Include\URegKey.h"
#endif

#if _WINDOWS
#include "..\..\..\..\ShellWin\Resource.h"
#include "..\..\..\..\Include\URegKey.h"
#endif

#include "Kernel/MiniCadHookIntf.h"

// This must follow all header files.
#undef THIS_FILE
DECLARE_THIS_FILE


Boolean _setmember(Sint32 c, ...)
/*
	Tests if c == any of the following parameters.  The parameter
	list is zero-terminated, so c == 0 must be tested separately.
*/
{
	va_list	ap;
	Sint32		i;

	va_start(ap, c);

	while ((i = va_arg(ap, Sint32)) != 0) {
		if (c == i) { va_end(ap); return true; }
	}
	va_end(ap);
	return false;
}

IntegerSet::IntegerSet()
{
	for (short i = numLongs-1; i >= 0; i--) this->bits[i] = 0;
}

IntegerSet::IntegerSet(short i0, short i1, short i2, short i3,
							short i4, short i5, short i6, short i7,
							short i8, short i9)
{
	for (short i = numLongs-1; i >= 0; i--) this->bits[i] = 0;
	if (i0 >= 0) *this += i0;
	if (i1 >= 0) *this += i1;
	if (i2 >= 0) *this += i2;
	if (i3 >= 0) *this += i3;
	if (i4 >= 0) *this += i4;
	if (i5 >= 0) *this += i5;
	if (i6 >= 0) *this += i6;
	if (i7 >= 0) *this += i7;
	if (i8 >= 0) *this += i8;
	if (i9 >= 0) *this += i9;
}

IntegerSet::IntegerSet(const IntegerSet &s)
{
	for (short i = numLongs-1; i >= 0; i--) this->bits[i] = s.bits[i];
}

void IntegerSet::operator +=(short i)
{
	if (i < 0 || i > maxElement) {
		#if 0
		DebugStr("\p### Set element out of range.");
		#endif
		return;
	}
	this->bits[i / 32] |= 1L << (i % 32);
}

Boolean IntegerSet::contains(short i) const
{
	if (i < 0 || i > maxElement) {
		#if 0
		DebugStr("\p### Set element out of range.");
		#endif
		return false;
	}
	return ((this->bits[i / 32] & (1L<<(i % 32))) != 0);
}

void IntegerSet::operator -=(short i)
{
	if (i < 0 || i > maxElement) {
		#if 0
		DebugStr("\p### Set element out of range.");
		#endif
		return;
	}
	this->bits[i / 32] &= ~(1L << (i % 32));
}

void IntegerSet::operator +=(const IntegerSet &s)
{
	for (short i = numLongs-1; i >= 0; i--)
		this->bits[i] |= s.bits[i];
}

void IntegerSet::operator -=(const IntegerSet &s)
{
	for (short i = numLongs-1; i >= 0; i--)
		this->bits[i] &= ~s.bits[i];
}

#ifdef _MINICAD_

Boolean OSIsMetric()
//
// Description:
//   This function determines if the operating system is set to use metric units.
//
// Returns:
//   Boolean - whether it is set to metric.
//
// Created by Bruce Ferguson on 10/29/02.  Moved to this file on 10/24/05.
//
{
	Boolean isMetric;

#if GS_WIN
	const short kCharBufSize = 2;	// 1-character string plus 1 for null char
	wchar_t buffer[kCharBufSize];

	GetRegistryValue(HKEY_CURRENT_USER, IDS_REGKEY_INTERNATIONAL, IDS_REGVAL_MEASURE, &buffer, kCharBufSize * 2);
	isMetric = (buffer[0] == '0');
#else
	isMetric = IsMetric();
#endif
	return isMetric;
}
#endif

/////////////////////////
// Cross-Platform byte swapping functions
/////////////////////////

// ByteSwapDWORD swaps 4 consecutive bytes (msb to lsb)

#if 0
void GS_API ByteSwapLONG(Sint32 *pl)
{
	char t;
	char* p1 = (char*)pl;
	char* p2 = p1 + 3;

	t = *p1;
	*p1 = *p2;
	*p2 = t;

	++p1; --p2;

	t = *p1;
	*p1 = *p2;
	*p2 = t;
}
#endif

TJsonType::TJsonType(){
	fStringData = "";
	fNonStringData.uint32_val = 0;
	fDataType = EJsonTypeTypes::EString;
}

TJsonType::TJsonType(const TXString& msg){
    fStringData = msg;
	fNonStringData.uint32_val = 0;
	fDataType = EJsonTypeTypes::EString;
}

TJsonType::TJsonType(const char* msg) {
	fStringData = msg;
	fNonStringData.uint32_val = 0;
	fDataType = EJsonTypeTypes::EString;
}

TJsonType::TJsonType(Sint64 val){
    fStringData = "";
	fNonStringData.sint64_val = val;
	fDataType = EJsonTypeTypes::ESint64;
}


TJsonType::TJsonType(Sint32 val){
    fStringData = "";
    fNonStringData.sint32_val = val;
	fDataType = EJsonTypeTypes::ESint32;
}

TJsonType::TJsonType(Uint64 val){
    fStringData = "";
    fNonStringData.uint64_val = val;
	fDataType = EJsonTypeTypes::EUint64;
}

TJsonType::TJsonType(Uint32 val){
    fStringData = "";
    fNonStringData.uint32_val = val;
	fDataType = EJsonTypeTypes::EUint32;
}


TJsonType::TJsonType(double val){
    fStringData = "";
    fNonStringData.double_val = val;
	fDataType = EJsonTypeTypes::EDouble;
}

TJsonType::TJsonType(long val){
    fStringData = "";
    fNonStringData.long_val = val;
    fDataType = EJsonTypeTypes::ELong;
}

TJsonType::TJsonType(unsigned long val){
    fStringData = "";
    fNonStringData.ulong_val = val;
    fDataType = EJsonTypeTypes::EULong;
}

TJsonType::TJsonType(bool val){
    fStringData = "";
    fNonStringData.bool_val = val;
	fDataType = EJsonTypeTypes::EBoolean;
}

TJsonType::TJsonType(std::nullptr_t val){
    fStringData = "";
	fDataType = EJsonTypeTypes::ENull;
}

#ifdef _MINICAD_
const TXString noLogTagPlaceholder = txu("\"sn\":\"\"");
TXString ReplaceSerialNumberToLog(const TXString & logEntryIn){
	TXString logEntry = logEntryIn;
    //If N/A is not even in the log just return
    if(logEntry.Find(noLogTagPlaceholder) == -1){
        return logEntry;
    }

    TXString sn = TAppProtection::MaskSerialNumber(TAppProtection::GetCurrentSerialNumber(), true);
    TXString buffer;
    buffer += txu("\"sn\":\"");
    buffer += sn;
    buffer += txu("\"");
    logEntry.Replace(noLogTagPlaceholder, buffer);
    return logEntry;
}

template<typename T>
T addToAtomic(std::atomic<T> *obj, T arg) {
	T expected = obj->load();
	while (!atomic_compare_exchange_weak(obj, &expected, expected + arg))
		;
	return expected;
}

TXString GetNextLogStageFileName(const TXString& logFolderPath, const TXString& baseName, const TXString& ext) {
	TFolderIdentifier logFolder(logFolderPath);
	TXString nextName = "";
	TXString stageLogName;
	TFileIdentifier logFileStageHandle;
	Sint32 count = 0;
	// Add a 30s failsafe, so there's no way we hit some infinite loop
	double startTime = ::GetSecondsSinceLaunch();
	double endTime = 0;
	bool fileExists = true;
	// Find the last Staging file and save the log file as that + 1;
	do {
		stageLogName = baseName;
		if (count > 0)
			stageLogName << count;
		stageLogName << ext;
		TFileIdentifier trialFile(logFolder, (const char*)stageLogName);
		if (trialFile.ExistsOnDisk()) {
			count += 1;
		}
		else {
			fileExists = false;
			nextName = stageLogName;
		}

		endTime = ::GetSecondsSinceLaunch();
		if (fileExists && endTime - startTime >= kLogStageFetchTimeout) {
			fileExists = false;
			nextName = "";
		}
	} while (fileExists);
	return nextName;
}

typedef std::vector<std::pair<Uint16, TXString> > TLogBufferList;
/*
 TApplicationLogger is a singleton that controls the logging variables as well as the logging file.
 */
class TApplicationLogger : public TSingleton<TApplicationLogger>
{
public:
	~TApplicationLogger();
	void SendToLogFile(Uint16 level, const TXString & message);
	void CloseLog();
	bool StageLogFiles(const TXString& stageLogBaseName, const TXString& ext);
	bool IsLoggingInitialized();
	void SetLoggingInitialized(bool initialized);
	bool IsLoggingBuffering();
	void SetLogBuffering(bool isBuffering);
	Uint16 GetLoggingLevel();
	void SetLoggingLevel(Uint16 level);
    double GetLoggingTime();
	void CopyToMainLogFile(TFileIdentifier& logFileHandle, TFileIdentifier& stageFileHandle);
	void SetInitialUploadMode(bool enabled);
	bool GetInitialUploadMode() { return fInitialUploadMode; };
protected:
	friend class TSingleton<TApplicationLogger>;
	TApplicationLogger();
private:
	FILE* fLogFile;
    TMutex fLogFileGuard;
	bool fIsLoggingInitialized;
	std::atomic<bool> fAreLogsBuffering;
	std::atomic<bool> fIsBufferFlushed;
	std::atomic<bool> fInitialUploadMode;
	TLogBufferList fLogBuffer;
	std::atomic<Uint16> fLoggingLevel;
	std::atomic<double> fLoggingTime;
	void SendToLogBuffer(Uint16 level, const TXString & message);
	void WriteToLogFile(Uint16 level, const TXString & message);
	void FlushLogBuffer();
};

TApplicationLogger::TApplicationLogger(){
	fLogFile = NULL;
	fIsLoggingInitialized = false;
	fAreLogsBuffering.store(true);
	fInitialUploadMode.store(false);
	fLoggingLevel.store(kDefaultLoggingLevel);
	fLoggingTime.store(0.0);
	fIsBufferFlushed.store(false);
}

TApplicationLogger::~TApplicationLogger(){
	CloseLog();
}

//Renames log file to a staging file then touches a new log file
//That is blank. This allows us to upload the log in the background
//By working on a file separate from the log file being written to
bool TApplicationLogger::StageLogFiles(const TXString& stageLogBaseName, const TXString& ext = ".txt") {
	TFolderIdentifier logFolder;
	bool success = false;
	if (GetAppDataFolder(logFolder)) {
		TMutex::TAutoLock autoLock(&fLogFileGuard);
		this->CloseLog();
		TFileIdentifier logFileHandle(logFolder, (const char*)kUserLogFileName);
		TXString stageLogName = GetNextLogStageFileName(logFolder.GetFolderPath(), stageLogBaseName, ext);
		if (stageLogName.GetLength() > 0) {
			TFileIdentifier logFileStageHandle(logFolder, stageLogName);

			TFile logFile;
			//If there isn't a log file create one to prevent crashes


			if (!logFileHandle.ExistsOnDisk()) {
				logFile.OpenNewWrite(logFileHandle);
				logFile.Close();
			}
			if (logFileHandle.RenameOnDisk(logFileStageHandle.GetFileName())) {
				logFileHandle.Set(logFolder, (const char*)kUserLogFileName);
				if (logFile.OpenNewWrite(logFileHandle)) {
					logFile.Close();
					success = true;
				}
			}
		}
	}
	return success;
}

void TApplicationLogger::CopyToMainLogFile(TFileIdentifier& logFileHandle, TFileIdentifier& stageFileHandle) {
	if (stageFileHandle.ExistsOnDisk() && logFileHandle.ExistsOnDisk()) {
		TMutex::TAutoLock autoLock(&fLogFileGuard);
		TFile logFile;
		TFile stageFile;
		logFile.OpenReadWrite(logFileHandle, false, false);
		stageFile.OpenRead(stageFileHandle, false);
		if (logFile.IsOpen() && stageFile.IsOpen()) {
			logFile.SeekToEnd();
			stageFile.SeekToStart();
			bool eof = false;
			char* buffer = NULL;
			buffer = new char[50000];
			Uint64 bytesLeft = 0;
			stageFile.GetBytesLeft(bytesLeft);
			while (bytesLeft > sizeof(buffer) && stageFile.Read(sizeof(buffer), buffer)) {
				logFile.Write(sizeof(buffer), buffer);
				stageFile.AtEndOfFile(eof);
				if (eof)
					break;
				stageFile.GetBytesLeft(bytesLeft);
			}

			if (!eof && bytesLeft > 0 && bytesLeft < sizeof(buffer) && stageFile.Read((Uint32)bytesLeft, buffer)) {
				logFile.Write((Uint32)bytesLeft, buffer);
			}
			logFile.Close();
			stageFile.Close();
			stageFileHandle.DeleteOnDisk();
			delete[] buffer;
			buffer = NULL;
		}
	}
}

void TApplicationLogger::CloseLog(){
    TMutex::TAutoLock autoLock(&fLogFileGuard);
	if(fLogFile){
		fclose(fLogFile);
		fLogFile = NULL;
	}
}

//Buffers Log/writes message to disk regardless of level. Level is checked when writing buffer (Once we load current logging level setting)
void TApplicationLogger::SendToLogFile(Uint16 level, const TXString & message){
	if(fAreLogsBuffering.load()){
		SendToLogBuffer(level, message);
	}
	else{
		if(!fIsBufferFlushed.exchange(true)){
			FlushLogBuffer();
		}
		WriteToLogFile(level, message);
	}
}

bool TApplicationLogger::IsLoggingInitialized(){
	return fIsLoggingInitialized;
}

void TApplicationLogger::SetLoggingInitialized(bool initialized){
	fIsLoggingInitialized = initialized;
}

bool TApplicationLogger::IsLoggingBuffering(){
	return fAreLogsBuffering.load();
}

void TApplicationLogger::SetLogBuffering(bool isBuffering) {
	//If we are setting to false be sure to close the log first
	fAreLogsBuffering.store(isBuffering);
}

void TApplicationLogger::SetInitialUploadMode(bool enabled) {
	fInitialUploadMode.store(enabled);
}

double TApplicationLogger::GetLoggingTime(){
	return fLoggingTime.load();
}

Uint16 TApplicationLogger::GetLoggingLevel(){
	return fLoggingLevel.load();
}

void TApplicationLogger::SetLoggingLevel(Uint16 level){
#if GS_BETA_PROGRAM_SUPPORT
    fLoggingLevel.store(kDefaultLoggingLevel);
#else
	fLoggingLevel.store(level);
#endif
}

void TApplicationLogger::SendToLogBuffer(Uint16 level, const TXString & message){
	fLogBuffer.push_back(std::make_pair(level, message));
}

void TApplicationLogger::FlushLogBuffer(){
	if(fLogBuffer.size() > 0){
		for(Uint32 i=0; i < fLogBuffer.size(); i++)
		{
			if (fLogBuffer[i].first <= fLoggingLevel) {
				//Process each log to add the S/N as it is now avaliable
				TXString newLogLine = ReplaceSerialNumberToLog(fLogBuffer[i].second);
				WriteToLogFile(fLogBuffer[i].first, newLogLine);
			}
		}
		fLogBuffer.clear();
	}
	fIsBufferFlushed.store(true);
}

void TApplicationLogger::WriteToLogFile(Uint16 level, const TXString & message){
	if(level <= fLoggingLevel.load()){
		TMutex::TAutoLock autoLock(&fLogFileGuard);
		double startTime = ::GetSecondsSinceLaunch();
		if(!fLogFile){
			TFolderIdentifier logFolder;
				if ( GetAppDataFolder(logFolder) ) {
					TFileIdentifier logFile(logFolder, kUserLogFileName);
					#if GS_MAC
					fLogFile = fopen((const char*) logFile.GetPosixFilePath(), "a+");
					#else
					fLogFile = _wfopen(logFile.GetFilePath().GetData(), L"a+");
					#endif
				}
		}
        if(fLogFile) {
            fprintf(fLogFile, "%s", (const char*) message);
			if (fInitialUploadMode.load()) {
				CloseLog();
			}
			else {
				fflush(fLogFile);
			}
        }
		double endTime = ::GetSecondsSinceLaunch();
		addToAtomic<double>(&fLoggingTime, (endTime - startTime));
	}
}

double LocalLog_GetLoggingTime(){
	return TApplicationLogger::Instance().GetLoggingTime();
}

void LocalLog_SetLoggingInitialized(bool isInitialized){
	TApplicationLogger::Instance().SetLoggingInitialized(isInitialized);
}

void LocalLog_SetLogBuffering(bool isBuffering){
	TApplicationLogger::Instance().SetLogBuffering(isBuffering);
}

bool LocalLog_GetInitialUploadMode() {
	return TApplicationLogger::Instance().GetInitialUploadMode();
}

Uint16 LocalLog_GetLoggingLevel(){
	return TApplicationLogger::Instance().GetLoggingLevel();
}

void LocalLog_SetLoggingLevel(Uint16 level){
#if GS_BETA_PROGRAM_SUPPORT
	TApplicationLogger::Instance().SetLoggingLevel(kDefaultLoggingLevel);
#else
	TApplicationLogger::Instance().SetLoggingLevel(level);
#endif
}

bool LocalLog_StageLogFiles(const TXString& stageLogBaseName, const TXString& ext) {
	return TApplicationLogger::Instance().StageLogFiles(stageLogBaseName, ext);
}

void LocalLog_CopyToMainLogFile(const TXString& destName) {
	TFolderIdentifier logFolder;
	if (GetAppDataFolder(logFolder)) {
		TFileIdentifier logFileHandle(logFolder, (const char*)kUserLogFileName);
		TFileIdentifier stageFileHandle(logFolder, (const char*)destName);
		TApplicationLogger::Instance().CopyToMainLogFile(logFileHandle, stageFileHandle);
	}
}

void LocalLog_SetInitialUploadMode(bool enabled) {
	TApplicationLogger::Instance().SetInitialUploadMode(enabled);
}

//----------------------------------------------
void CheckForBackgroundLogUpload(){
	//Every ~5 minutes spawn a thread. (It's approximiate because its the next log call after 5 minutes.
    //If TErrorReporting is disabled once it is enabled the next staging will flush all of those logs
    //And upload them ALL in the background once it is enabled.
    if (TErrorReporting::Instance().IsEnabled() && !TErrorReporting::Instance().IsUploadInProgress() && TErrorReporting::Instance().IsLaunchUploadComplete()
        && TErrorReporting::Instance().IsTimeForBackgroundUpload()){
        //Prep logs to be uploaded in background
        //Copy them over to a staging file
        //Start thread to upload in background
		TApplicationLogger::Instance().CloseLog(); //Close the log file as staging will not occur
        TErrorReporting::Instance().BeginThreadedLogUpload();
    }
}

#define JSON_TXESCAPE(str)	"\"" + str + "\""
#define JSON_KEY_PAIR(msg, key, val)	msg << "\"";\
                                        msg << txu(key);\
                                        msg << "\"";\
										msg << ":";\
										msg << val;
#if GS_MAC
const TXString kLocalLogOSName = txu("MAC");
#else
const TXString kLocalLogOSName = txu("WIN");
#endif

TXString SearchForLogType(const TXString& jsonDescription){
	TXString logType = txu("INFO");
	if(jsonDescription.Find("error", 0, true) != -1 && jsonDescription.Find("ferror", 0, true) == -1)
		logType = txu("ERROR");
	if(jsonDescription.Find("fail", 0, true) != -1)
		logType = txu("ERROR");
	if(jsonDescription.Find("exception", 0, true) != -1)
		logType = txu("ERROR");
	if(jsonDescription.Find("failure", 0, true) != -1)
		logType = txu("ERROR");
	if(jsonDescription.Find("warn", 0, true) != -1)
		logType = txu("WARN");
	if(jsonDescription.Find("warning", 0, true) != -1)
		logType = txu("WARN");
	return logType;
}

rapidjson::Value CreateValueFromTJsonType(TJsonType& object, rapidjson::Document& document) {
	EJsonTypeTypes type = object.GetType();
	rapidjson::Value value;
	if (type == EJsonTypeTypes::EString) {
		value = rapidjson::Value(rapidjson::kStringType);
		TXString stringData = object.GetStringData();
		value.SetString(stringData.GetStdString().c_str(), static_cast<rapidjson::SizeType>(stringData.GetLength()), document.GetAllocator());
	}
	else if (type == EJsonTypeTypes::ENull) {
		value = rapidjson::Value(rapidjson::kNullType);
	}
	else if (type == EJsonTypeTypes::EBoolean) {
		bool boolData = object.GetNonStringData().bool_val;
		if (boolData) {
			value = rapidjson::Value(rapidjson::kTrueType);
		}
		else {
			value = rapidjson::Value(rapidjson::kFalseType);
		}
	}
	else {
		value = rapidjson::Value(rapidjson::kNumberType);
		if (type == EJsonTypeTypes::ESint64) {
			value.SetInt64(object.GetNonStringData().sint64_val);
		}
		else if (type == EJsonTypeTypes::EUint64) {
			value.SetUint64(object.GetNonStringData().uint64_val);
		}
		else if (type == EJsonTypeTypes::EDouble) {
			value.SetDouble(object.GetNonStringData().double_val);
		}
		else if (type == EJsonTypeTypes::ESint32) {
			value.SetInt(object.GetNonStringData().sint32_val);
		}
		else {
			value.SetUint(object.GetNonStringData().uint32_val);
		}
	}
	return value;
}

void AddLogFieldsToDocument(const TJsonLogDataMap& logFields, rapidjson::Document& document){
	TJsonLogDataMap::const_iterator mapIterator;
	TXString logMessageFields = "";
	for(mapIterator = logFields.begin(); mapIterator != logFields.end(); mapIterator++){
		TXString key = mapIterator->first;
		TJsonType second = mapIterator->second;
		rapidjson::Value toInsert = CreateValueFromTJsonType(second, document);
		rapidjson::Value keyVal(key.GetStdString().c_str(), document.GetAllocator());
		document.AddMember(keyVal.Move(), toInsert, document.GetAllocator());
	}
}

rapidjson::Document GetMetaDataDocument(Uint16 level) {
	rapidjson::Document document;
	document.SetObject();
	static const std::string osString = GetUniformOSSystemVersion(false).GetStdString();
	static const char* os = osString.c_str();
	static const rapidjson::SizeType osLen = static_cast<rapidjson::SizeType>(osString.length());
	static const std::string sessionString = TXString::ToStringHex(static_cast<long long>(::GetStartupTime()), false).GetStdString();
	static const char* session = sessionString.c_str();
	static const rapidjson::SizeType sessionLen = static_cast<rapidjson::SizeType>(sessionString.length());
	static const std::string vwVersionString = GetVersionNumberString(eShortWithBuild).GetStdString();
	static const char* vwVersion = vwVersionString.c_str();
	static const rapidjson::SizeType vwVersionLen = static_cast<rapidjson::SizeType>(vwVersionString.length());

	std::string serialNumber;
	if (TAppProtection::IsInViewerMode()) {
		serialNumber = "viewer";
	}
	else if (TAppProtection::IsInDemoMode()) {
		serialNumber = "demo";
	}
	else {
		serialNumber = TAppProtection::MaskSerialNumber(TAppProtection::GetCurrentSerialNumber()).GetStdString();;
	}

	time_t theTime;
	char dateBuffer[255];
	time(&theTime);
	tm* tp = gmtime(&theTime);
	strftime(dateBuffer, 255, "%m/%d/%Y %H:%M:%S", tp);
	TXString dateStr;
	dateStr << dateBuffer;
	dateStr << txu(" ");
	dateStr << ::GetSecondsSinceLaunch();
	dateStr << txu(" +0000");

	// Important this is first for processing
	rapidjson::Value timestampJsonString(rapidjson::kStringType);
	timestampJsonString.SetString((const char*)dateStr, static_cast<rapidjson::SizeType>(dateStr.GetLength()), document.GetAllocator());
	document.AddMember("ts", timestampJsonString, document.GetAllocator());

	rapidjson::Value logLevelJsonNumber(rapidjson::kNumberType);
	logLevelJsonNumber.SetUint(level);
	document.AddMember("log_lvl", logLevelJsonNumber, document.GetAllocator());

	rapidjson::Value serialNumberJson(rapidjson::kStringType);
	serialNumberJson.SetString(serialNumber.c_str(), static_cast<rapidjson::SizeType>(serialNumber.length()), document.GetAllocator());
	document.AddMember("sn", serialNumberJson, document.GetAllocator());

	rapidjson::Value sessionJsonString(rapidjson::kStringType);
	sessionJsonString.SetString(session, sessionLen, document.GetAllocator());
	document.AddMember("session", sessionJsonString, document.GetAllocator());

	rapidjson::Value versionJsonString(rapidjson::kStringType);
	versionJsonString.SetString(vwVersion, vwVersionLen, document.GetAllocator());
	document.AddMember("vw_ver", versionJsonString, document.GetAllocator());

	rapidjson::Value platformJsonString(rapidjson::kStringType);
	platformJsonString.SetString(kLocalLogOSName, 3, document.GetAllocator());
	document.AddMember("platform", platformJsonString, document.GetAllocator());

	rapidjson::Value osVerJsonString(rapidjson::kStringType);
	osVerJsonString.SetString(os, osLen, document.GetAllocator());
	document.AddMember("os_ver", osVerJsonString, document.GetAllocator());
	return document;
}

TXString DocumentToString(rapidjson::Document& document) {
	rapidjson::StringBuffer sb;
	rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
	document.Accept(writer);
	return TXString(sb.GetString()) + "\n";

}

rapidjson::Document LegacyCreateLogDocument(const TXString& description) {
	rapidjson::Document document;
	document.SetObject();
	if (description.GetLength() > 1) {
		//Special Case for OpenGL driver
		//Its message is json so just return it
		bool isBaseJson = false;
		if (description[0] == '{' && description.GetLast() == '}') {
			if (!document.Parse(description.GetStdString().c_str()).HasParseError()) {
				isBaseJson = true;
			}
		}
		rapidjson::Value::ConstMemberIterator itr = document.FindMember("type");
		if (itr == document.MemberEnd()) {
			TXString type = SearchForLogType(description);
			rapidjson::Value typeJsonString(rapidjson::kStringType);
			typeJsonString.SetString((const char*)type, static_cast<rapidjson::SizeType>(type.GetLength()), document.GetAllocator());
			document.AddMember("type", typeJsonString, document.GetAllocator());
		}
		if (!isBaseJson) {
			ptrdiff_t categoryIndex = description.Find(": ");
			TXString jsonLog;
			TXString category;
			if (categoryIndex != -1) {
				category = description.Left(categoryIndex);
			}
			else {
				categoryIndex = description.Find(' ');
				if (categoryIndex != -1) {
					category = description.Left(categoryIndex);
				}
				else {
					category = txu("no_cat");
				}
			}
			rapidjson::Value categoryJsonString(rapidjson::kStringType);
			categoryJsonString.SetString(category.GetStdString().c_str(), static_cast<rapidjson::SizeType>(category.GetLength()), document.GetAllocator());
			document.AddMember("cat", categoryJsonString, document.GetAllocator());

			rapidjson::Value messageJsonString(rapidjson::kStringType);
			messageJsonString.SetString(description.GetStdString().c_str(), static_cast<rapidjson::SizeType>(description.GetLength()), document.GetAllocator());
			document.AddMember("message", messageJsonString, document.GetAllocator());
		}
	}
	return document;
}

void LegacyWriteLocalLog(Uint16 level, bool doDebugWindow, const char *description)
{
	// we do not want to respond to log requests at early init time
	if (TApplicationLogger::Instance().IsLoggingInitialized()) {

		// levelzero is defined to output nothing to the log

		// 1-3 should happen on a session frequency
		// 4-6 can happen on a per-user-event frequency
		// 7-10 can happen up to once every few seconds on average
		// > 10 are special purpose and should not be used without
		// prior arrangement - Paul

		ASSERTN(kPaul, level > 0);

		//Check if we're going to write this log before parsing it into a JSON string.
		if (TApplicationLogger::Instance().IsLoggingBuffering() || level <= TApplicationLogger::Instance().GetLoggingLevel()) {
			rapidjson::Document document = GetMetaDataDocument(level);
			rapidjson::Document content = LegacyCreateLogDocument(TXString(description));
			for (rapidjson::Value::MemberIterator itr = content.MemberBegin(); itr != content.MemberEnd(); ++itr) {
				document.AddMember(itr->name, itr->value, document.GetAllocator());
			}
			TXString logMessage = DocumentToString(document);
			TApplicationLogger::Instance().SendToLogFile(level, logMessage);

			if (doDebugWindow) {
				#if !DaveD	// Too much noise in the debug window from resource lists.  -DLD 1/12/2012
					// send to the debug window - in future need to pass time and process also
					DMSG((kEveryone, "%s\n", description));
				#endif	// !DaveD
			}

            CheckForBackgroundLogUpload();
		}
	}
}

void WriteLocalLog(Uint16 level, const TJsonLogDataMap& logFields){
	if (TApplicationLogger::Instance().IsLoggingInitialized()) {
        ASSERTN(kOKadry, level > 0);
		if (TApplicationLogger::Instance().IsLoggingBuffering() || level <= TApplicationLogger::Instance().GetLoggingLevel()) {
			rapidjson::Document document = GetMetaDataDocument(level);
			AddLogFieldsToDocument(logFields, document);
			TXString logMessage = DocumentToString(document);
			TApplicationLogger::Instance().SendToLogFile(level, logMessage);

            CheckForBackgroundLogUpload();
        }
    }
}
#endif


static void LegacyRouteLocalLog(Uint16 level, bool doDebugWindow, const char *description)
{

#ifdef _MINICAD_
		// the VCOM interface is not yet registered
		// use default in-place implementation

		LegacyWriteLocalLog(level, doDebugWindow, description);

#else
	using namespace VectorWorks::Debug;
	::GS_InitializeVCOM( gCBP );
	VCOMPtr<IDebugOutputLog> pOutInst( IID_DebugOutputLog );

	if ( pOutInst ) {
		pOutInst->LocalLogStr(level, doDebugWindow, description);
	}
	else {
		// remove log being called before VCOM initialization
		ASSERTN( kVStanev, false );
	}
#endif

}

void LocalLogStr(Uint16 level, const char *description)
{
	LegacyRouteLocalLog(level, true, description);
}

void LocalLogStrTight(Uint16 level, const char *description)
{
	LegacyRouteLocalLog(level, false, description);
}

Sint32 LegacyFormatAndWriteLog(Uint16 level, bool doDebugWindow, const char *format, va_list ap)
//
//	Log to local file.  Accept variable arguments.
//	This is similar to the function _Log( ) above.
//	JAK 1/31/05
//
{
	const size_t kBuf_size = 1024;

	static Sint32 r = 0;
	char buf[kBuf_size];
#if GS_MAC
	r = vsnprintf(buf, sizeof(buf), format, ap);
    // result is negative on error, or >= size on truncation.
    if (r >= sizeof(buf))
    {
        // for truncation, adjust return value.
        r = sizeof(buf) - 1;
    }
#else
    errno = 0;
    r = _vsnprintf_s(buf, sizeof(buf), _TRUNCATE, format, ap);
    // result is -1 on error or truncation
    if ((r == -1 && errno == 0) || r >= sizeof(buf))
    {
        // for truncation, adjust return value. for exact length, need to fix null termination.
        r = sizeof(buf) - 1;
        buf[r] = 0;
    }
#endif
    if (r > 0)  // nothing to do with an empty string/erronous result
       LegacyRouteLocalLog(level, doDebugWindow, buf);
	return r;
}

Sint32 LocalLog(Uint16 level, const char *format, ...)
{
	va_list	ap;
	va_start(ap, format);
	Sint32 result = LegacyFormatAndWriteLog(level, true, format, ap);
	va_end(ap);

	return result;
}

Sint32 LocalLogTight(Uint16 level, const char *format, ...)
{
	va_list	ap;
	va_start(ap, format);
	Sint32 result = LegacyFormatAndWriteLog(level, false, format, ap);
	va_end(ap);

	return result;
}

Sint32 LocalLogTiming(Uint16 level, double timing, const char *format, ...)
{
	const size_t kBuf_size = 1124;

	va_list	ap;
	va_start(ap, format);

	char modifiedFormat[kBuf_size];
	sprintf(modifiedFormat, "TIMER: %f sec | %s", timing, format);

	Sint32 result = LegacyFormatAndWriteLog(level, true, modifiedFormat, ap);
	va_end(ap);

	return result;
}

//Route depending on if this is called from the main application or an SDK plugin
void RouteLocalLog(Uint16 level, const TJsonLogDataMap& logFields) {
#ifdef _MINICAD_
	WriteLocalLog(level, logFields);
#else
	using namespace VectorWorks::Debug;
	::GS_InitializeVCOM(gCBP);
	VCOMPtr<IDebugOutputLog> pOutInst(IID_DebugOutputLog);

	if (pOutInst) {
		pOutInst->SendToLogWriter(level, logFields);
	}
#endif
}

//=======================================================================================
// VFormat(...) is used in Format(...). Note the buffer size is 1024.
static void _VFormat(TXString& outString, const char* format, va_list argPtr)
{
	static const Sint32 kBufferSize = 1024;
	char localBuffer[kBufferSize];

	Sint32 len = vsnprintf(localBuffer, kBufferSize, (const char *)format, argPtr);

	ASSERTN(kPChang, len >= 0 && len < kBufferSize);

	if (len >= 0)
		outString = localBuffer;
	else
		outString.Clear();
}

// Start new Logging Calls
const TXString kInfoLogStr = "INFO";
const TXString kErrorLogStr = "ERROR";
const TXString kWarnLogStr = "WARN";
TXString GetLogType(Uint16 logType) {
	if (logType == kErrorLog) {
		return kErrorLogStr;
	}
	if (logType == kWarnLog) {
		return kWarnLogStr;
	}
	return kInfoLogStr;
}

void LocalLog(Uint16 level, Uint16 type, const TXString& cat, const TXString& message){
	TJsonLogDataMap logFields;
    logFields.insert(KEYPAIR("cat", cat));
	TXString typeStr = GetLogType(type);
    logFields.insert(KEYPAIR("type", typeStr));
    logFields.insert(KEYPAIR("message", message));
	DMSG((kEveryone, "%s\n", (const char*)message));
    RouteLocalLog(level, logFields);
}

void LocalLog(Uint16 level, Uint16 type, const TXString& cat, const char* format, ...){
    va_list ap;
    va_start (ap, format);
    TXString formattedMessage;
    ::_VFormat(formattedMessage, format, ap);
    va_end(ap);
	LocalLog(level, type, cat, formattedMessage);
}


void LocalLog(Uint16 level, Uint16 type, const TXString& cat,
                TJsonLogDataMap logFields){
    logFields.insert(KEYPAIR("cat", cat));
    logFields.insert(KEYPAIR("type", GetLogType(type)));
    RouteLocalLog(level, logFields);
}

void LocalLog(const TLogEnum& logEnum, const TXString& message){
	LocalLog(logEnum.GetLevel(), logEnum.GetType(), logEnum.GetCategory(), message);
}

void LocalLog(const TLogEnum& logEnum, const char* format, ...){
	va_list ap;
    va_start (ap, format);
    TXString formattedMessage;
    ::_VFormat(formattedMessage, format, ap);
    va_end(ap);
	LocalLog(logEnum.GetLevel(), logEnum.GetType(), logEnum.GetCategory(), formattedMessage);

}

void LocalLog(const TLogEnum& logEnum, const TJsonLogDataMap customData){
	LocalLog(logEnum.GetLevel(), logEnum.GetType(), logEnum.GetCategory(), customData);
}

void LocalLogTiming(Uint16 level, Uint16 type, double timing, const TXString& cat,
                      const TXString& message){
    TJsonLogDataMap logFields;
    logFields.insert(KEYPAIR("cat", cat));
    logFields.insert(KEYPAIR("type", GetLogType(type)));
    logFields.insert(KEYPAIR("timing", timing));
    logFields.insert(KEYPAIR("message", message));
	DMSG((kEveryone, "TIMER: %f sec | %s\n", timing, (const char*)message));
    RouteLocalLog(level, logFields);
}

void LocalLogTiming(Uint16 level, Uint16 type, double timing, const TXString& cat,
                      const char* format, ...){
    va_list ap;
    va_start (ap, format);
    TXString formattedMessage;
    ::_VFormat(formattedMessage, format, ap);
    va_end(ap);
	LocalLogTiming(level, type, timing, cat, formattedMessage);
}

void LocalLogTiming(Uint16 level, Uint16 type, double timing, const TXString& cat,
                     TJsonLogDataMap logFields){
    logFields.insert(KEYPAIR("cat", cat));
    logFields.insert(KEYPAIR("type", GetLogType(type)));
    logFields.insert(KEYPAIR("timing", timing));
    RouteLocalLog(level, logFields);
}

void LocalLogTiming(const TLogEnum& logEnum, double timing, const TXString& message){
	LocalLogTiming(logEnum.GetLevel(), logEnum.GetType(), timing, logEnum.GetCategory(), message);
}

void LocalLogTiming(const TLogEnum& logEnum, double timing, const char* format, ...){
	va_list ap;
    va_start (ap, format);
    TXString formattedMessage;
    ::_VFormat(formattedMessage, format, ap);
    va_end(ap);
	LocalLogTiming(logEnum.GetLevel(), logEnum.GetType(), timing, logEnum.GetCategory(), formattedMessage);
}

void LocalLogTiming(const TLogEnum& logEnum, double timing, TJsonLogDataMap customData){
	LocalLogTiming(logEnum.GetLevel(), logEnum.GetType(), timing, logEnum.GetCategory(), customData);
}

/////////////////////////////////////////////////////////////////////////////////////////
// Timers

TFractionalTimer::TFractionalTimer()
: fStartTime { 0,0 }
{
	FetchTime(fStartTime);
}

void TFractionalTimer::FetchTime(TimeBuffer_& buffer)
{
#if _WINDOWS
	ASSERTN(kRBerge, sizeof(TimeBuffer_) >= sizeof(LARGE_INTEGER));
	PLARGE_INTEGER ptr = reinterpret_cast<PLARGE_INTEGER>(buffer);
	QueryPerformanceCounter(ptr);
#else
	ASSERTN(kRBerge, sizeof(TimeBuffer_) >= sizeof(timeval));
	timeval* ptr = reinterpret_cast<timeval*>(&buffer);
	gettimeofday(ptr, nullptr);
#endif
}

double TFractionalTimer::DiffTime(const TimeBuffer_& start, const TimeBuffer_& end)
{

#if _WINDOWS
	const LARGE_INTEGER* s = reinterpret_cast<const LARGE_INTEGER*>(&start);
	const LARGE_INTEGER* e = reinterpret_cast<const LARGE_INTEGER*>(&end);
	Sint64 elapsed = e->QuadPart - s->QuadPart;

	LARGE_INTEGER perSecond;
	QueryPerformanceFrequency(&perSecond);
	double interval = elapsed / (double)perSecond.QuadPart;
#else
	const timeval* s = reinterpret_cast<const timeval*>(&start);
	const timeval* e = reinterpret_cast<const timeval*>(&end);

	timeval elapsed;
	timersub(e, s, &elapsed);
	double interval = elapsed.tv_sec + ((double)(elapsed.tv_usec)/1000000.0);
#endif

	return interval;
}

double TFractionalTimer::GetElapsed(bool reset)
{
	TimeBuffer_ now;
	FetchTime(now);

	double interval = DiffTime(fStartTime, now);

	if (reset) {
		// restart the timer
		*fStartTime = *now;
	}

	return interval;
}

TLogTimer::TLogTimer(Uint16 level, const char* context, double logThreshhold)
	: fLevel(level), fContext(context),	fLogThreshhold(logThreshhold),
	timer(std::make_unique<TFractionalTimer>())
{
}

TLogTimer::~TLogTimer()
{
	double interval = timer->GetElapsed();
	if (interval > fLogThreshhold) {
		REMOTELOGTIMINGF((fLevel, interval, "%s", fContext));
	}
}
