/*
 * Copyright (C) 2011 log-tools.net
 *
 * Licensed 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.
 */

/*!
 *  \file	SequenceLog.cpp
 *  \brief	シーケンスログクラス
 *  \author	Copyright 2011 log-tools.net
 */
//nclude "SequenceLog.h"
#include "SequenceLogItem.h"

#include "slog/Mutex.h"
#include "slog/Socket.h"
#include "slog/SharedMemory.h"
#include "slog/TimeSpan.h"
#include "slog/Process.h"

/******************************************************************************
*
* C言語用
*
******************************************************************************/
extern "C"
{
	/*!
	 *  \brief	ステップイン時のログ出力
	 */
//	void* _slog_stepIn(const char* className, const char* funcName, SequenceLogOutputFlag outputFlag)
	void* _slog_stepIn(const char* className, const char* funcName, int32_t               outputFlag)
	{
		slog::SequenceLog* slog = new slog::SequenceLog(className, funcName, (slog::SequenceLogOutputFlag)outputFlag);
		return slog;
	}

	/*!
	 *  \brief	ステップイン時のログ出力
	 */
	void* _slog_stepIn2(uint32_t classID, const char* funcName, int32_t outputFlag)
	{
		slog::SequenceLog* slog = new slog::SequenceLog(classID, funcName, (slog::SequenceLogOutputFlag)outputFlag);
		return slog;
	}

	/*!
	 *  \brief	ステップイン時のログ出力
	 */
	void* _slog_stepIn3(uint32_t classID, uint32_t funcID, int32_t outputFlag)
	{
		slog::SequenceLog* slog = new slog::SequenceLog(classID, funcID, (slog::SequenceLogOutputFlag)outputFlag);
		return slog;
	}

	/*!
	 *  \brief	ステップアウト時のログ出力
	 */
	void _slog_stepOut(void* p)
	{
		slog::SequenceLog* slog = (slog::SequenceLog*)p;
		delete slog;
	}

	/*!
	 *  \brief	メッセージ出力
	 */
//	void  _slog_message(void* p, SequenceLogLevel level, const char* format, ...)
	void  _slog_message(void* p, int32_t          level, const char* format, ...)
	{
		slog::SequenceLog* slog = (slog::SequenceLog*)p;

		va_list arg;
		va_start(arg, format);

		slog->messageV((slog::SequenceLogLevel)level, format, arg);
	}

	/*!
	 *  \brief	メッセージ出力
	 */
	void  _slog_message2(void* p, int32_t level, uint32_t messageID)
	{
		slog::SequenceLog* slog = (slog::SequenceLog*)p;
		slog->message((slog::SequenceLogLevel)level, messageID);
	}
}

/*-----------------------------------------------------------------------------
|
| 静的メンバー変数定義
|
+----------------------------------------------------------------------------*/
namespace slog
{

static char						sSequenceLogFileName[MAX_PATH + 1] = "";	//!< シーケンスログファイル名
static SequenceLogOutputFlag	sRootFlag = ROOT;

class  SequenceLogClient;
static SequenceLogClient*		sClient = NULL;								//!< シーケンスログクライアントオブジェクト
static bool						sClientInitialized = false;					//!< 初期化フラグ

/*!
 *  \brief	シーケンスログソケットクラス
 */
class SequenceLogSocket : public Socket
{
			FixedString<MAX_PATH>	mShmName;		//!< 共有メモリ名

public:		void canUseService() const throw(Exception);
			const CoreString& recvSharedMemoryName() throw(Exception);
};

/*!
 *  \brief	シーケンスログサービスが使用可能か確認する
 */
void SequenceLogSocket::canUseService() const throw(Exception)
{
#if defined(__unix__)
	Exception e;

	int32_t len = sizeof(pthread_mutex_t);
	send(&len);

	int32_t canUse = 0;
	recv(&canUse);

	if (canUse == 0)
	{
#if defined(__x86_64)
		printf("slogsvc(32 bits) can't use, because this process is 64 bits.\n");
#else
		printf("slogsvc(64 bits) can't use, because this process is 32 bits.\n");
#endif

		e.setMessage("SequenceLogSocket::canUseService() / can not use");
		throw e;
	}

	if (canUse != 1)
	{
		e.setMessage("SequenceLogSocket::canUseService() / receive illegal value(%d)", canUse);
		throw e;
	}
#endif
}

/*!
 *  \brief	共有メモリ名受信
 */
const CoreString& SequenceLogSocket::recvSharedMemoryName() throw(Exception)
{
	int32_t len = 0;

	recv(&len);
	recv(&mShmName, len);

	if (mShmName[len - 1] != '\0')
	{
		Exception e;
		e.setMessage("SequenceLogSocket::recvSharedMemoryName() / receive name is not null-terminated");

		throw e;
	}

	return mShmName;
}

/*!
 *  \brief	シーケンスログ共有メモリクラス
 */
class SequenceLogSharedMemory : public SharedMemory<SLOG_SHM*>
{
public:		void validate() const throw(Exception);

			SequenceLogItem* getSequenceLogItem() const;
			uint32_t getSequenceNo() const;
};

/*!
 *  \brief	共有メモリが有効か確認する
 */
void SequenceLogSharedMemory::validate() const throw(Exception)
{
	if (getSize() < sizeof(SLOG_SHM) + sizeof(SequenceLogItem) * ((*this)->count - 1))
	{
		Exception e;
		e.setMessage("SequenceLogSharedMemory::validate() / illegal shared memory size");

		throw e;
	}
}

/*!
 *  \brief	シーケンスログアイテム取得
 */
SequenceLogItem* SequenceLogSharedMemory::getSequenceLogItem() const
{
	SLOG_SHM* shm = getBuffer();

	if (shm->index >= shm->count)
		return NULL;

	SequenceLogItem* itemArray = shm->itemArray;
	uint32_t index =             shm->index;

	SequenceLogItem* item = &itemArray[index];

	shm->index++;
	item->setCurrentDateTime();

	return item;
}

/*!
 *  \brief	シーケンス番号取得
 */
uint32_t SequenceLogSharedMemory::getSequenceNo() const
{
	SLOG_SHM* shm = getBuffer();
	uint32_t seq = shm->seq;

	shm->seq++;
	return seq;
}

/*!
 *  \brief	シーケンスログクライアントクラス
 */
class SequenceLogClient
{
			SequenceLogSocket		mSocket;			//!< ソケット
			SequenceLogSharedMemory	mSHM;				//!< 共有メモリ
			Mutex*					mMutex;				//!< ミューテックス

public:		 SequenceLogClient();
			~SequenceLogClient();

			void init();

public:		SequenceLogItem* lock(uint32_t* seq);
			Mutex* getMutex() const;
};

/*!
 *  \brief	コンストラクタ
 */
inline SequenceLogClient::SequenceLogClient()
{
//	TRACE("[S] SequenceLogClient::SequenceLogClient()\n", 0);

	Socket::startup();
	mMutex = NULL;

//	TRACE("[E] SequenceLogClient::SequenceLogClient()\n", 0);
}

/*!
 *  \brief	デストラクタ
 */
inline SequenceLogClient::~SequenceLogClient()
{
//	TRACE("[S] SequenceLogClient::~SequenceLogClient()\n", 0);

	delete mMutex;
	mSocket.close();

	Socket::cleanup();
//	TRACE("[E] SequenceLogClient::~SequenceLogClient()\n", 0);
}

/*!
 *  \brief	初期化
 */
void SequenceLogClient::init()
{
	TRACE("[S] SequenceLogClient::init()\n", 0);

	// シーケンスログファイル名取得
	const char* p = sSequenceLogFileName;
    TRACE("    sSequenceLogFileName='%s'", p);

	if (p[0] == '\0')
    {
        TRACE("    getSequenceLogFileName() calling...", 0);
		p = getSequenceLogFileName();
    }

	if (p == NULL || p[0] == '\0')
	{
		TRACE("[E] SequenceLogClient::init() - failed\n", 0);
		return;
	}

	FixedString<MAX_PATH> name = p;
	int32_t len = name.getLength() + 1;

	try
	{
		// 初期化処理のためのソケット作成
#if !defined(__ANDROID__)
		mSocket.open();
		mSocket.setRecvTimeOut(3000);
		mSocket.connect(FixedString<16>("127.0.0.1"), SERVICE_PORT);
#else
		mSocket.open(false);
		mSocket.setRecvTimeOut(3000);
		mSocket.connect(FixedString<108>("/dev/socket/slog"));
#endif

		// プロセスID送信
		Process process;
		uint32_t pid = process.getId();
		mSocket.send(&pid);

		// シーケンスログサービスが使用可能か調べる
		mSocket.canUseService();

		// シーケンスログファイル名送信
		mSocket.send(&len);
		mSocket.send(&name, len);

		// 共有メモリ名受信
		const CoreString& shmName = mSocket.recvSharedMemoryName();
		TRACE("    shmName='%s'\n", shmName.getBuffer());

		// 共有メモリ取得
		mSHM.open(shmName);
		mSHM.validate();

		// ミューテックス取得
#if defined(_WINDOWS)
		name.format("slogMutex%d", pid);
		mMutex = new Mutex(false, name);
#else
		mMutex = new Mutex(false, &mSHM->mutex);
#endif
	}
	catch (Exception e)
	{
		TRACE("    %s\n", e.getMessage());
		mSocket.close();
	}

	TRACE("[E] SequenceLogClient::init()\n", 0);
}

/*!
 *  \brief	ロック
 */
SequenceLogItem* SequenceLogClient::lock(uint32_t* seq)
{
	if (this == NULL)
		return NULL;

	if (mSocket.isOpen() == false)
		return NULL;

	TimeSpan timeSpan1;
	SequenceLogItem* item;

	while (true)
	{
		ScopedLock lock(mMutex);
		item = mSHM.getSequenceLogItem();

		if (item)
		{
			lock.release();
			break;
		}

		TimeSpan timeSpan2;

		if (timeSpan2 - timeSpan1 > 1000)
		{
			// 1000ミリ秒以上ログ出力出来なかったので以降のログ出力をキャンセルする
			TRACE("    SequenceLogClient::lock() %d > 1000 ms\n", timeSpan2 - timeSpan1);

			mSocket.close();
			return NULL;
		}
	}

	if (seq)
		*seq = mSHM.getSequenceNo();

//	mMutex->Unlock();
	return item;
}

/*!
 *  \brief	ミューテックス取得
 */
Mutex* SequenceLogClient::getMutex() const
{
	return mMutex;
}

/*!
 *  \brief	シーケンスログクライアントデリータクラス
 */
class SequenceLogClientDeleter
{
public:		~SequenceLogClientDeleter()
			{
				delete sClient;
				sClient = NULL;
			}
};

static SequenceLogClientDeleter s_deleter;

/******************************************************************************
*
* シーケンスログクラス
*
******************************************************************************/

/*!
 *  \brief	コンストラクタ
 */
SequenceLog::SequenceLog(
	const char* className,				//!< クラス名
	const char* funcName,				//!< メソッド名
	SequenceLogOutputFlag outputFlag)	//!< 出力フラグ
{
	init(outputFlag);
	SequenceLogItem* item = sClient->lock(&mSeqNo);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag, className, funcName);
	}
}

/*!
 *  \brief	コンストラクタ
 */
SequenceLog::SequenceLog(uint32_t classID, const char* funcName, SequenceLogOutputFlag outputFlag)
{
	init(outputFlag);
	SequenceLogItem* item = sClient->lock(&mSeqNo);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag, classID, funcName);
	}
}

/*!
 *  \brief	コンストラクタ
 */
SequenceLog::SequenceLog(uint32_t classID, uint32_t funcID, SequenceLogOutputFlag outputFlag)
{
	init(outputFlag);
	SequenceLogItem* item = sClient->lock(&mSeqNo);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag, classID, funcID);
	}
}

/*!
 *  \brief	デストラクタ
 */
SequenceLog::~SequenceLog()
{
	SequenceLogItem* item = sClient->lock(NULL);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag);
	}
}

/*!
 *  \brief	初期化
 */
void SequenceLog::init(SequenceLogOutputFlag outputFlag)
{
	if (sClientInitialized == false)
	{
		sClientInitialized = true;

		sClient = new SequenceLogClient;
		sClient->init();
	}

	mSeqNo = 0;
	mOutputFlag = (outputFlag != ROOT ? outputFlag : sRootFlag);
}

/*!
 *  \brief	メッセージ出力
 */
void SequenceLog::message(SequenceLogLevel level, const char* format, ...)
{
	va_list arg;
	va_start(arg, format);

	messageV(level, format, arg);
}

/*!
 *  \brief	メッセージ出力
 */
void SequenceLog::messageV(SequenceLogLevel level, const char* format, va_list arg)
{
	SequenceLogItem* item = sClient->lock(NULL);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag, level);
		item->mMessageId = 0;

		try
		{
			PointerString _Message = item->getMessage();
			_Message.formatV(format, arg);
		}
		catch (Exception /*e*/)
		{
			// 何もしない
		}
	}
}

/*!
 *  \brief	メッセージ出力
 */
void SequenceLog::message(SequenceLogLevel level, uint32_t messageID)
{
	SequenceLogItem* item = sClient->lock(NULL);

	if (item)
	{
		ScopedLock lock(sClient->getMutex(), false);
		item->init(mSeqNo, mOutputFlag, level);
		item->mMessageId = messageID;
	}
}

} // namespace slog

/*!
 *  \brief	シーケンスログファイル名を設定する
 */
extern "C" void setSequenceLogFileName(const char* fileName)
{
	strcpy(slog::sSequenceLogFileName, fileName);
}

/*!
 *  \brief	ROOTの既定値を設定する
 */
extern "C" void setRootFlag(int32_t outputFlag)
{
	slog::sRootFlag = (slog::SequenceLogOutputFlag)outputFlag;
}
