#include "stdafx.h"
#include "resource.h"

#include "UDPSendActionInvoker.hpp"

#include "Utility.hpp"

#include <vector>
#include <algorithm>

#pragma comment(lib, "Ws2_32.lib")



// UDPSendActionInvokerの識別子
// {75BB2D04-8C25-4fcf-8124-CF155D998AF2}
static const GUID UDP_SEND_ACTIONINVOKE_SIG =
	{ 0x75bb2d04, 0x8c25, 0x4fcf, { 0x81, 0x24, 0xcf, 0x15, 0x5d, 0x99, 0x8a, 0xf2 } };

/*!
 * 監視設定を指定してアクションを構築します。
 * \param 監視設定
 */
UDPSendActionInvoker::UDPSendActionInvoker(const CSettingInfo& v_settingInfo) throw()
	: ActionInvoker(v_settingInfo)
	, initResult_(0)
	, msgid_(0)
{
	ZeroMemory(&wsaData_, sizeof(WSAData));
	initResult_ = WSAStartup(MAKEWORD(2,0), &wsaData_);
}

/*!
 * デストラクタ
 */
UDPSendActionInvoker::~UDPSendActionInvoker() throw()
{
	if (initResult_ == 0) {
		WSACleanup();
	}
}

/*!
 * 永続化状態をリセットします。
 */
void UDPSendActionInvoker::Reset()
{
	msgid_ = 0;
}

/*!
 * 永続化状態を読み込みます。
 * \param reader オブジェクトリーダ
 */
void UDPSendActionInvoker::Load(CObjectReader &reader)
{
	reader.VerifySig(UDP_SEND_ACTIONINVOKE_SIG);
	msgid_ = reader.ReadDWORD();
}

/*!
 * 永続化状態を保存します。
 * \param writer オブジェクトライタ
 */
void UDPSendActionInvoker::Save(CObjectWriter &writer)
{
	writer.WriteSig(UDP_SEND_ACTIONINVOKE_SIG);
	writer.WriteDWORD(msgid_);
}

/*!
 * アクションが実行可能かチェックします。
 * \return 最大数が指定されていないか、最大数を超えていなければtrue
 */
bool UDPSendActionInvoker::canDoAction() throw()
{
	return true;
}

/*!
 * ガベージコレクトします。
 *終了済みのプロセス等をチェックするために定期的に呼び出す必要があります。
 */
unsigned int UDPSendActionInvoker::sweepGarbage() throw()
{
	return 0;
}

/*!
 * 特殊変数COUNTの展開に使用する総実行回数を取得ます。
 * \return 総実行回数
 */
DWORD UDPSendActionInvoker::internalGetRunCount()
{
	return msgid_;
}

/*!
 * UDP送信パラメータの調整。
 * アクションに応じて特殊変数・環境変数の展開し、非印字文字列を補正するなどを行う。
 * パラメータについては非印字文字の空白置換を行わない。(そのまま。)
 * \param expander 置換変数の展開
 * \param v_absolutePath 絶対パス
 * \param fileInfo ファイル情報
 * \param verb アクションタイプを保存する
 * \param appName アプリケーションを保存する
 * \param param パラメータを保存する。
 * \param appCurrentDir カレントディレクトリを保存する。
 */
void UDPSendActionInvoker::adjustParameters(
	PropertyStringExpander expander,
	const tstring &, //v_absoluePath
	const CFileInfo &, //fileInfo
	tstring& verb,
	tstring& appName, 
	tstring& param,
	tstring& appCurrentDir) throw()
{
	verb = EliminateUnprintableChars(settingInfo_.getAction());

	appName = EliminateUnprintableChars(expander.expandString(settingInfo_.getAppName()));
	param = expander.expandString(settingInfo_.getParam()); // 改行等はそのまま。
	appCurrentDir = EliminateUnprintableChars(expander.expandString(settingInfo_.getAppCurrentDir()));
}

/*!
 * UDP送信の実行
 * \param v_absoluePath ターゲットの絶対パス
 * \param v_fileInfo ターゲットのファイル情報
 * \param verb アクションタイプ
 * \param appName アプリケーション名(展開済み)
 * \param param パラメータ(展開済み)
 * \param appCurrentDir カレントディレクトリ(展開済み)
 */
bool UDPSendActionInvoker::internalDoAction(
	const tstring v_absolutePath,
	const CFileInfo& , // v_fileInfo
	const tstring& , // verb
	const tstring& appName, 
	const tstring& param,
	const tstring& appCurrentDir) throw()
{
	if (initResult_) {
		// 初期化に失敗している場合
		if (pLogger_) {
			pLogger_->LogMessage(0, NULL, (DWORD) initResult_, IDS_LOGMES_UDPSENDFAIL);
		}
		return false;
	}

	if (appName.empty()) {
		// アプリケーション名が省略されていれば送信先なしとして戻る
		return true;
	}

	// ホスト名とポートに分離する。
	tstring hostName;
	tstring serviceName;
	size_t pos = appName.find(_T(":"));
	if (pos == appName.npos) {
		hostName = appName;
		serviceName = _T("2425"); // IPMsgのデフォルトポート
	}
	else {
		hostName = appName.substr(0, pos);
		serviceName = appName.substr(pos + 1);
	}

	// 送信者と送信もとホストの分離
	tstring senderName;
	tstring localHostName;
	if ( !appCurrentDir.empty() && appCurrentDir.find(_T(":")) == appCurrentDir.npos) {
		// appCurrentDirに「user@host」があれば、それを用いる。
		// ただしコロンが含まれる場合は無効 = 指定されていないものと同じとみなす。
		// (IpMsgのフォーマット形式でユーザ名とホスト名にコロンを入れることはできないため。)
		size_t pos = appCurrentDir.find(_T("@"));
		if (pos == appCurrentDir.npos) {
			senderName = appCurrentDir;
		}
		else {
			senderName = appCurrentDir.substr(0, pos);
			localHostName = appCurrentDir.substr(pos + 1);
		}
	}
	if (senderName.empty()) {
		TCHAR szUserName[MAX_PATH] = {0};
		DWORD szUserNameBufSiz = MAX_PATH;
		if (GetUserName(szUserName, &szUserNameBufSiz)) {
			senderName = szUserName;
		}
	}
	if (localHostName.empty()) {
		CHAR szLocalHostName[MAX_PATH] = {0};
		if (gethostname(szLocalHostName, MAX_PATH) == 0) {
			CA2T localHostNameT(szLocalHostName);
			localHostName = localHostNameT;
		}
	}
	if (senderName.empty()) {
		senderName = _T("fwatch");
	}
	if (localHostName.empty()) {
		localHostName = _T("fwatch");
	}
	

	// UNICODEならばANSIに変換
	CT2A hostNameA(hostName.c_str());
	CT2A serviceA(serviceName.c_str());

	// 送信先アドレスとポートの決定
	sockaddr_in addr = {0};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(2425);

	addrinfo addrHints = {0};
	addrHints.ai_family = AF_INET;
	addrHints.ai_socktype  = SOCK_DGRAM;
	addrHints.ai_protocol = IPPROTO_UDP;

	addrinfo *pAddrInfoHead = NULL;
	int addrErrCode = getaddrinfo(hostNameA, serviceA, &addrHints, &pAddrInfoHead);
	bool bFoundAddr = false;
	if (addrErrCode == 0) {
		addrinfo *pAddrInfo = pAddrInfoHead;
		while (pAddrInfo) {
			addr.sin_addr.S_un = ((struct sockaddr_in *)(pAddrInfo->ai_addr))->sin_addr.S_un;
			addr.sin_port = ((struct sockaddr_in *)(pAddrInfo->ai_addr))->sin_port;
			pAddrInfo = pAddrInfo->ai_next;
			bFoundAddr = true;
			break;
		}
		freeaddrinfo(pAddrInfoHead);
	}

	// アドレスが発見できなかった場合
	if (addrErrCode || !bFoundAddr) {
		if (pLogger_) {
			pLogger_->LogMessage(0, NULL, (DWORD) addrErrCode, IDS_LOGMES_UDPSENDFAIL);
		}
		return false;
	}

	if (pLogger_) {
		CA2T receiverAddrTmp(inet_ntoa(addr.sin_addr));
		tstring receiverAddr(receiverAddrTmp);
		pLogger_->LogMessage(
			2,
			NULL,
			0,
			IDS_LOGMES_UDPSEND,
			hostName.c_str(), // receiver
			receiverAddr.c_str(), // receiver addr
			senderName.c_str(), // sender name
			localHostName.c_str(), // sender host
			param.c_str() // message
			);
	}

	// メッセージの構築
	msgid_++;
	tstring msg = ToString(
		_T("1:%d:%s:%s:%d:%s"),
		msgid_,
		senderName.c_str(),
		localHostName.c_str(),
		0x20, // メッセージ送信コマンド
		param.c_str() // メッセージ本文
		);

	// CR除去後のサイズを求める。
	std::string msgA = convertByteStrWithoutCR(msg);
	int lenMsgA = msgA.length();

	// UDPソケットの生成
	int sendErrCode = 0;
	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock != INVALID_SOCKET) {
		// UDPで送信
		int sentLen = sendto(
			sock,
			&msgA[0],
			lenMsgA,
			0,
			(SOCKADDR *) &addr,
			sizeof(addr)
			);
		if (sentLen == SOCKET_ERROR) {
			sendErrCode = WSAGetLastError();
		}
		closesocket(sock);
	}
	else {
		// ソケットの構築に失敗
		sendErrCode = WSAGetLastError();
	}
	if (sendErrCode) {
		// エラーをログに出力する
		if (pLogger_) {
			pLogger_->LogMessage(0, NULL, (DWORD) addrErrCode, IDS_LOGMES_UDPSENDFAIL);
		}
	}

	return sendErrCode == 0;
}

/*!
 * メッセージをLF改行のバイト文字列に変換します。
 * \param msg メッセージ
 * \return LF改行のバイト文字列
 */
std::string UDPSendActionInvoker::convertByteStrWithoutCR(const tstring &msg)
{
	if (msg.empty()) {
		return "";
	}

#ifdef _UNICODE
	// メッセージはANSI(SJIS)で送信する。
	// UNICODEをSJISに変換するために必要なバッファサイズを求める。
	int bufsiz = ::WideCharToMultiByte(
		CP_ACP,
		WC_COMPOSITECHECK | WC_DISCARDNS | WC_SEPCHARS | WC_DEFAULTCHAR,
		msg.c_str(),
		-1, // 長さ自動判定
		NULL,
		0, // バッファサイズ計算指示
		NULL,
		NULL
		);
	if ( !bufsiz) {
		// 予期せぬ失敗によりメッセージが変換できない。
		if (pLogger_) {
			DWORD err = GetLastError();
			pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_UDPSENDFAIL);
		}
		return false;
	}
#else
	int bufsiz = msg.length() + 1; // 終端を含めたバッファサイズ
#endif

	// バッファ確保(SJIS用)
	std::vector<CHAR> msgA(bufsiz + 1); // ダブルヌル終端でガード

#ifdef _UNICODE
	if (!::WideCharToMultiByte(
		CP_ACP,
		WC_COMPOSITECHECK | WC_DISCARDNS | WC_SEPCHARS | WC_DEFAULTCHAR,
		msg.c_str(),
		-1, // 長さ自動判定
		&msgA[0], // バッファ
		bufsiz,
		NULL,
		NULL
		)) {
		// 予期せぬ失敗によりメッセージが変換できない。
		if (pLogger_) {
			DWORD err = GetLastError();
			pLogger_->LogMessage(0, NULL, err, IDS_LOGMES_UDPSENDFAIL);
		}
		return false;
	}
#else
	memcpy(&msgA[0], msg.c_str(), bufsiz);
#endif

	// CRLFをLFにする。CRのうしろにLFが続かない場合はLFにする。
	// IpMsgではLF改行でないと改行とみなさないため。
	LPSTR p = &msgA[0];
	LPSTR s = p;
	while (*s) {
		if (*s == '\r') {
			if (*(s + 1) != '\n') {
				*p++ = '\n';
			}
		}
		else {
			*p++ = *s;
		}
		s++;
	}
	*p = 0;

	return std::string(&msgA[0]);
}


