/** daytodaywin.cpp
 * 今日は何の日
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @動作環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 * @参照サイト	Wikipedia，日本記念日協会
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-11-01.shtm
*/

// 初期化処理 ======================================================
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <sstream>
#include <string>
#include <iterator>
#include <list>
#include <regex>
#include <locale>
#include <winsock2.h>
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include <curl/curl.h>
#include <boost/format.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include "pahooCache.hpp"
#include "mystrings.h"
#include "resource.h"

using namespace std;
using namespace boost;
using namespace boost::property_tree;

#define MAKER		"pahoo.org"				// 作成者
#define APPNAME		"daytodaywin"			// アプリケーション名
#define APPNAMEJP	"今日は何の日"			// アプリケーション名（日本語）
#define APPVERSION	"1.5.1"					// バージョン
#define APPYEAR		"2020-25"				// 作成年・更新日
#define REFERENCE	"https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-11-01.shtm"	// 参考サイト

// char*バッファサイズ
#define SIZE_BUFF2		5120

// ListViewItemの最大文字長：変更不可
#define MAX_LISTVIEWITEM	255

// リクエストURL（日本記念日協会）（※変更不可）
#define KINENBI_URL		"https://www.kinenbi.gr.jp/"

// 記念日識別値（※変更不可）
#define KINENBI_YEAR	(-99999)

// キャッシュ・ディレクトリ
#define DIR_CACHE_WIKIPEDIA		"pcache\\"

// キャッシュ保持時間（デフォルト；分）(0：キャッシュしない)
#define LIFE_CACHE_WIKIPEDIA	(7 * 24 * 60)

// 現在のインターフェイス
static HINSTANCE hInst;

// アプリケーション・ウィンドウ
HWND hParent;

// アプリケーション・ウィンドウ位置
unsigned hParent_X, hParent_Y;

// エラー・メッセージ格納用
string ErrorMessage;

// ヘルプ・ファイル
#define HELPFILE	".\\etc\\help.chm"

// デフォルト保存ファイル名
#define SAVEFILE	"daytodaywin.csv"

// UserAgent
string UserAgent;

// 出来事格納用クラス
#define SIZE_EVENTS		500		// 格納上限
class _Events {
public:
	wstring category = L"";		// 区分
	int year = 0;				// 西暦年
	wstring era = L"";			// 元号
	wstring event = L"";		// 出来事
	wstring plus = L"";			// 追加情報
};
unique_ptr<_Events> Events[SIZE_EVENTS] = {};

// ソート用構造体
struct _stEvents {
	wchar_t* category = NULL;	// 区分
	int *year = NULL;			// 西暦年
	wchar_t* event = NULL;		// 出来事
	size_t id = 0;				// Eventsの添字
};
vector<_stEvents> Vevents;

// バージョン情報
string Version = (boost::format(R"(
%1%  バージョン %2%

本アプリケーションはMIT Licenseです．
商用を含む無償利用が可能です．自由に改造できます．
再配布の際は，下記の著作権表記，およびURLと本使用条件を必ず明記してください．

　Copyright by (c)studio pahoo, %3%
　https://www.pahoo.org/

なお，本アプリケーションの利用または改造することによって生じた得失ついては一切関知いたしません．APIの変更・停止によって正常に機能しなくなる場合があります．また，二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません．

本アプリケーションは，下記のライブラリ，フレームワーク，APIを利用しています．
　MinGW(g++), Boost C++ライブラリ, cURL, OpenSSL
)")
% APPNAMEJP
% APPVERSION
% APPYEAR
).str();

// オプション読込／保存 ====================================================
/**
 * オプションの読み込み
 * @param	なし
 * @return	なし
 */
void loadOption(void) {
	hParent_X = 0;
	hParent_Y = 0;
	ptree pt;

	// XMLファイル読み込み
	try {
		xml_parser::read_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt);

		// XML解釈
		try {
			// 形式チェック
			if (optional<string>str = pt.get_optional<string>("parameter")) {
			} else {
				return;
			}
			// パラメータ読み込み
			for (auto it : pt.get_child("parameter")) {
				string type= it.second.get_optional<string>("<xmlattr>.type").value();
				if (type == "wx") {
					hParent_X = (unsigned)stoi(it.second.data());
				} else if (type == "wy") {
					hParent_Y = (unsigned)stoi(it.second.data());
				}
			}
		// 解釈失敗したら初期値設定
		} catch (xml_parser_error& e) {
			hParent_X = 0;
			hParent_Y = 0;
			return;
		}
	// 読み込み失敗したら初期値設定
	} catch (xml_parser_error& e) {
		hParent_X = 0;
		hParent_Y = 0;
		return;
	}

	// アプリケーション・ウィンドウの位置（デスクトップ範囲外なら原点移動）
	HWND hDesktop = GetDesktopWindow();
	WINDOWINFO windowInfo;
	windowInfo.cbSize = sizeof(WINDOWINFO);
	GetWindowInfo(hDesktop, &windowInfo);
	if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
		hParent_X = 0;
	}
	if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
		hParent_Y = 0;
	}
}

/**
 * オプションの保存
 * @param	なし
 * @return	なし
 */
void saveOption(void) {
#ifndef CMDAPP
	// アプリケーション・ウィンドウの位置取得
	WINDOWINFO windowInfo;
	windowInfo.cbSize = sizeof(WINDOWINFO);
	GetWindowInfo(hParent, &windowInfo);
	hParent_X = (unsigned)windowInfo.rcWindow.left;
	hParent_Y = (unsigned)windowInfo.rcWindow.top;
	if (hParent_X >= (unsigned)windowInfo.rcWindow.right) {
		hParent_X = 0;
	}
	if (hParent_Y >= (unsigned)windowInfo.rcWindow.bottom) {
		hParent_Y = 0;
	}
#endif

	// XMLファイルへ書き込む
	ptree pt;
	ptree& child2 = pt.add("parameter.param", (string)to_string(hParent_X));
	child2.add("<xmlattr>.type", "wx");
	ptree& child3 = pt.add("parameter.param", (string)to_string(hParent_Y));
	child3.add("<xmlattr>.type", "wy");

	const int indent = 4;
	write_xml(getMyPath(APPNAME) + APPNAME + ".xml", pt, std::locale(),
		xml_writer_make_settings<std::string>(' ', indent));
}

// バージョン表示ダイアログ =================================================
/**
 * イベントハンドラ：バージョン表示ダイアログ
 * @param	HWND hDlg	ウィンドウ・ハンドラ
 * @paramm	UINT uMsg
 * @param	WPARAM wParam
 * @paramL	PARAM lParam
 * @return	INT_PTR CALLBACK
*/
INT_PTR CALLBACK processHelp(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	string help;

	switch(uMsg){
	// ダイアログ初期化
	case WM_INITDIALOG:
		CenterWindow(hDlg);
	setStrEditBox(hDlg, IDC_TEXT_HELP, Version);
	break;

	// ボタン押下
	case WM_COMMAND:
		 switch (LOWORD(wParam)) {
			// 実行
			case IDC_BUTTON_OK:
				EndDialog(hDlg, 0);
				break;
			default:
				return 1;
		}
		break;
	// プログラム終了
	case WM_CLOSE:
		EndDialog(hDlg, 0);
		break;
	}
    return 0;
}

/**
 * ヘルプ・ダイアログを作成
 * @param  HWND hDlg		親ウィンドウ・ハンドラ
 * @param  DLGPROC handler	イベント・ハンドラ
 * @return なし
*/
void createHelp(HWND hDlg, DLGPROC handler) {
	DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG_VER), hDlg, (DLGPROC)handler);
}

/**
 * 検索結果をCSVファイルに保存する
 * @param	const char *fname 保存ファイル名
 * @return	なし
*/
void __saveCSV(const char *fname) {
	ofstream outputfile(fname);

	// ラベル
	outputfile << "\"" << "区分" << "\",";
	outputfile << "\"" << "西暦年" << "\",";
	outputfile << "\"" << "出来事" << "\",";
	outputfile << "\"" << "リンクURL" << "\"" << endl;

	// データ本体
	for (int i = 0; i < SIZE_EVENTS; i++) {
		if (Events[i] != NULL) {
			outputfile << "\"" << _WS(Events[i]->category) << "\",";
			if (Events[i]->year != KINENBI_YEAR) {
				outputfile << "\"" << Events[i]->year << "\",";
			} else {
				outputfile << "\"\",";
			}
			outputfile << "\"" << _WS(Events[i]->event) << "\"" << endl;
		}
		if (outputfile.bad()) {
			ErrorMessage = "CSVファイル書き込みエラー";
			break;
		}
	}
	outputfile.close();
}

/**
 * 検索結果をCSVファイルに保存する
 * @param	なし
 * @return	なし
*/
void saveCSV(void) {
	static char fname[MAX_PATH + 1];
	strcpy(fname, SAVEFILE);
	OPENFILENAME of;

	// OPENFILENAME構造体のサイズをセット
	memset(&of, 0, sizeof(OPENFILENAME));
	of.lStructSize = sizeof(OPENFILENAME);
	// ダイアログボックスを所有するウィンドウへのハンドル
	of.hwndOwner = hParent;
	of.lpstrFilter = TEXT("CSVファイル(*.csv;*.txt)\0*.csv;*.txt\0すべてのファイル(*.txt)\0*.txt\0\0");
	// ファイル名を格納したバッファのアドレス
	of.lpstrFile = (LPTSTR)fname; 
	// lpstrFileメンバで指定されるバッファのサイズ
	of.nMaxFile = MAX_PATH;
	of.Flags = OFN_OVERWRITEPROMPT;
	// デフォルトの拡張子を格納したバッファのアドレス
	of.lpstrDefExt = TEXT("csv");
	// コモンダイアログの表示
	GetSaveFileName(&of);
	// ファイル保存
	__saveCSV(fname);
}

/**
 * Wikipedia URL取得
 * @param	int month, day	月，日
 * @param	char* url		URLを格納
 * @param	size_t sz		urlの最大長
 * @return	なし
*/
void getURL_Wikipedia(int month, int day, char *url, size_t sz) {
	const string wiki = "https://ja.wikipedia.org/wiki/";
	static char buff1[SIZE_BUFF2 + 1], buff2[SIZE_BUFF2 + 1];

	snprintf(buff1, SIZE_BUFF2, "%d月%d日", month, day);
	string s1 = sjis_utf8(buff1);

	CURL *curl = curl_easy_init();
	strncpy(buff2, curl_easy_escape(curl, (const char *)s1.c_str(), strlen(s1.c_str())), SIZE_BUFF2);


	string ss = wiki + (string)buff2;
	strncpy(url, ss.c_str(), SIZE_BUFF2);

	curl_easy_cleanup(curl);
}

/**
 * 注釈番号を取り除いて返す．
 * @param	wstring wstr	入力テキスト
 * @return	wstring 変換後テキスト
*/
wstring wstripAnnotations(wstring wstr) {
	wregex re1(_SW("\\&\\#[0-9a-fA-F]+\\;[0-9a-fA-F]+\\&\\#[0-9a-fA-F]+\\;"));
	return (wstring)regex_replace(wstr, re1, L"");
}

/**
 * 今日は何の日 取得
 * @param	int  month, day	月，日
 * @param	bool lfn		脚注を残す（省略時＝FALSE）
 * @return	int 情報件数
*/
int getDayToday(int month, int day, bool lfn=FALSE) {
	// 初期化
	for (int i = 0; i < SIZE_EVENTS; i++) {
		Events[i].reset();
		Events[i] = NULL;
	}

	// キャッシュシステム準備
	pahooCache* pCC;
	pCC = new pahooCache(LIFE_CACHE_WIKIPEDIA, getMyPath(APPNAME) + DIR_CACHE_WIKIPEDIA, UserAgent);

	// Wikipediaコンテンツ取得
	char url[SIZE_BUFF2];
	string contents = "";
//	int httpStatus;
	getURL_Wikipedia(month, day, (char *)url, SIZE_BUFF2);
//	bool res = readWebContents(url, UserAgent, &contents, &httpStatus);
	if (pCC->load(url, &contents) == FALSE) {
		ErrorMessage = "Wikipediaにアクセスできません：" + _WS(pCC->getError());
		delete pCC;
		return FALSE;
	}

//	if (res == FALSE) {
//		ErrorMessage = "Wikipediaにアクセスできません．";
//		return (-1);
//	}

	// コンテンツの解釈
	setlocale(LC_ALL, "Japanese");
	int cnt = -1;
	stringstream ss;
	string ss0;
	wstring mode = L"";
	wstring ws, ws2;
	wstringstream wss;
	static wsmatch mt1, mt2, mt3;
	static smatch mt4;
	wregex re1(_SW("<h2[\\s\\S]+id=\"(できごと|出来事)\""));
	wregex re2(_SW("<h2[\\s\\S]+id=\"誕生日\""));
	wregex re3(_SW("<h2[\\s\\S]+id=\"忌日\""));
	wregex re5(_SW("<li>[^\\>]+([0-9]+(年|世紀).+)<\\/li>"));
	wregex re6(_SW("([^0-9]*)([0-9]+)年(\\s*[\(（].[^\\)）]+[）\\)].)?[ \\-]+(.+)"));	// 年 - 出来事
	wregex re7(_SW("([^0-9]*)([0-9]+)年(\\s*[\\(（].[^\\)）]+[）\\)].)?[ \\-]+([^、]+)、?([^（\\(]+)*([^0-9]*)([0-9]*)"));		// 年 - 名前、職業（生没年）
	wregex re8(_SW("<span[\\s\\S]+id=\"フィクションのできごと\">"));
	wregex re9(_SW("^\\（[\\*\\+]."));			// 年 - 名前、職業（生没年）
	wregex re11(_SW("紀元前"));
	wregex re99(_SW("<span[\\s\\S]+class=\"vector-menu-heading-label\">他言語版"));		// 1行読み込み打ち切り

	ss << contents;
	while(ss && getline(ss, ss0)) {
		// 1行をwstring変換
		ws = _UW(ss0);
		// 4000文字以上なら処理しない
		if (ws.length() > 4000) {
			continue;
		}
		// マッチング処理
		if (regex_search(ws, mt1, re99)) {
			break;
		} else if (regex_search(ws, mt1, re1)) {
			mode = _SW("出来事");
		} else if (regex_search(ws, mt1, re8)) {
			mode = _SW("架空");
		} else if (regex_search(ws, mt1, re2)) {
			mode = _SW("誕生");
		} else if (regex_search(ws, mt1, re3)) {
			mode = _SW("死亡");
		} else if ((mode.length() != 0) && (regex_search(ws, mt1, re5) == TRUE)) {
			// HTMLタグ除去
			ws = wstrip_tags(ws);
			// 注釈番号除去
			ws = wstripAnnotations(ws);
			// 出来事
			if ((mode == _SW("出来事")) && (regex_search(ws, mt2, re6) == TRUE)) {
				cnt++;
				Events[cnt] = make_unique<_Events>();
				Events[cnt]->category = mode;
				if (mt2[1].str().length() == 0) {
					Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
				} else {
					Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
				}
				Events[cnt]->era   = mt2[3].str();
				Events[cnt]->event = mt2[4].str();

			// 誕生・死亡
			} else if (regex_search(ws, mt2, re7)) {
				cnt++;
				Events[cnt] = make_unique<_Events>();
				Events[cnt]->category = mode;
				if (mt2[1].str().length() == 0) {
					Events[cnt]->year = atoi(_WS(mt2[2].str()).c_str());
				} else {
					Events[cnt]->year = 0 - atoi(_WS(mt2[2].str()).c_str());
				}
				Events[cnt]->era = mt2[3].str();
				ws2 = mt2[6].str();
				if (regex_search(ws2, mt3, re11) == TRUE) {
					Events[cnt]->plus = L"-" + mt2[7].str();
				} else if (regex_search(ws2, mt3, re9) == TRUE) {
					Events[cnt]->plus = mt2[7].str();
				} else {
					Events[cnt]->plus = L"";
				}
				wss << mt2[4].str() << _SW("（") << mt2[5].str();
				if (mode == _SW("誕生")) {
					wss << _SW("）誕生");
					if (Events[cnt]->plus.length() != 0) {
						wss << _SW("（〜") << Events[cnt]->plus << _SW("年）");
					}
				} else {
					wss << _SW("）死去");
					if (Events[cnt]->plus.length() != 0) {
						wss << _SW("（") << Events[cnt]->plus << _SW("年〜")<< Events[cnt]->year << _SW("年）");
					}
				}
				Events[cnt]->event = wss.str();
				wss.str(L"");
			}
		}
	}
	// インスタンス解放
	delete pCC;

	return cnt;
}

/**
 * 指定月日の記念日を取得する
 * @param	int month, day 月日
 * @param	int cnt 情報を追加する配列の最初の番号
 * @return	int 情報の総数
*/
int getAnniversary(int month, int day, int cnt=0) {
	// コンテンツ読み込み
	static char post[SIZE_BUFF2 + 1];
	snprintf(post, SIZE_BUFF2, "MD=%d&M=%d&D=%d", month, month, day);
	string contents = "";
	int httpStatus;
	bool res = readWebContents(KINENBI_URL, UserAgent, &contents, &httpStatus, post);

	if (res == FALSE) {
		ErrorMessage = "日本記念日協会のサイトにアクセスできません．";
		return (-1);
	}

	// スクレイピング
	wregex re1(_SW("<a\\s+class=\"winDetail\"\\s+href=\"([^\"]+)\"><font[^>]+>([^<]+)</font>"), wregex::icase);
	static wsmatch mt1;

	stringstream ss;
	wstring mode = _SW("記念日");
	string ss0;
	wstring ws;
	ss << contents;
	while(ss && getline(ss, ss0)) {
		// 1行をwstring変換
		ws = _UW(ss0);
		if (regex_search(ws, mt1, re1)) {
			Events[cnt] = make_unique<_Events>();
			Events[cnt]->category = mode;
			Events[cnt]->event = mt1[2].str();
			Events[cnt]->year  = KINENBI_YEAR;
			cnt++;
		}
	}

	return cnt;
}

// 記事ソート用ID
enum {
	SORT_CATEGORY_ASC,
	SORT_CATEGORY_DEC,
	SORT_YEAR_ASC,
	SORT_YEAR_DEC,
	SORT_EVENT_ASC,
	SORT_EVENT_DEC,
	SORT_NONE
};

// 記事ソート用ID
enum {
	SORT_ASC,
	SORT_DEC
};

/**
 * ソート用の比較関数：区分（昇順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpCategoryAsc(const _stEvents& left, const _stEvents& right) {
	return wcscmp(left.category, right.category) < 0;
}

/**
 * ソート用の比較関数：区分（降順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpCategoryDec(const _stEvents& left, const _stEvents& right) {
//	return strcmp(left.category, right.category) >= 0;
	return wcscmp(left.category, right.category) >= 0;
}

/**
 * ソート用の比較関数：西暦（昇順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpYearAsc(const _stEvents& left, const _stEvents& right) {
	return (*left.year < *right.year);
}

/**
 * ソート用の比較関数：西暦（降順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpYearDec(const _stEvents& left, const _stEvents& right) {
	return (*left.year >= *right.year);
}

/**
 * ソート用の比較関数：出来事（昇順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpEventAsc(const _stEvents& left, const _stEvents& right) {
	return wcscmp(left.event, right.event) < 0;
}

/**
 * ソート用の比較関数：出来事（降順）
 * @param	const _stEvents& left, right
 * @return	比較結果
*/
bool __cmpEventDec(const _stEvents& left, const _stEvents& right) {
//	return strcmp(left.event, right.event) >= 0;
	return wcscmp(left.event, right.event) >= 0;
}

/**
 * ソート用変数に代入
 * @param	unique_ptr<_Events> &ptr 	データセット
 * @param	size_t i					データセットのID
 * @return	_stEvents
*/
_stEvents __push_stEvents(unique_ptr<_Events> &ptr, size_t id) {
	_stEvents s;
	s.category	= (wchar_t*)ptr->category.c_str();
	s.year		= (int*)&ptr->year;
	s.event		= (wchar_t*)ptr->event.c_str();
	s.id		= id;
	return s;
}

/**
 * 出来事一覧のソート
 * @param	int c  ソート条件
 * @return	なし
*/
void sortEvents(int c) {
	struct {
		int id;
		bool (*fn)(const _stEvents& left, const _stEvents& right);
} table[] = {
	{ SORT_CATEGORY_ASC,	__cmpCategoryAsc },
	{ SORT_CATEGORY_DEC,	__cmpCategoryDec },
	{ SORT_YEAR_ASC,		__cmpYearAsc },
	{ SORT_YEAR_DEC,		__cmpYearDec },
	{ SORT_EVENT_ASC,		__cmpEventAsc },
	{ SORT_EVENT_DEC,		__cmpEventDec }
};
	size_t i;
	bool (*fn)(const _stEvents& left, const _stEvents& right) = NULL;

	// ソート用配列の初期化
	Vevents.clear();
	Vevents.shrink_to_fit();

	// 出来事情報をソート用配列へ
	for (i = 0; i < SIZE_EVENTS; i++) {
		if (Events[i] != NULL) {
			Vevents.push_back(__push_stEvents(Events[i], i));
		}
	}

	// ソート用関数の選択
	for (i = 0; i < (int)(sizeof(table) / sizeof(*table)); i++) {
		if (c == table[i].id) {
			fn = table[i].fn;
			break;
		}
	}

	// ソート実行
	if (fn != NULL) {
		stable_sort(Vevents.begin(), Vevents.end(), fn);
	}
}

// リストビューの列
enum {
	COL_CATEGORY,
	COL_YEAR,
	COL_EVENT,
	COL_ID
};

/**
 * 出来事一覧のフレーム作成
 * @param	HWND hDlg  ウィンドウハンドル
 * @return	なし
*/
void makeListViewFrame(HWND hWnd) {
	LVCOLUMNA lvcol;
	lvcol.mask = LVCF_TEXT | LVCF_SUBITEM | LVCF_WIDTH | LVCF_FMT;
	lvcol.fmt = LVCFMT_LEFT;

	lvcol.cx = 70;
	lvcol.pszText = (char *)"区分";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_CATEGORY, (WPARAM)&lvcol);
	lvcol.cx = 50;
	lvcol.pszText = (char *)"西暦";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_YEAR, (WPARAM)&lvcol);
	lvcol.cx = 450;
	lvcol.pszText = (char *)"出来事";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_EVENT, (WPARAM)&lvcol);
	lvcol.cx = 0;
	lvcol.pszText = (char *)"ID";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_ID, (WPARAM)&lvcol);

	// 罫線表示＋1行選択
	DWORD dwStyle = ListView_GetExtendedListViewStyle(hWnd);
	dwStyle = dwStyle | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE | LVS_EX_FULLROWSELECT;
	ListView_SetExtendedListViewStyle(hWnd, dwStyle);
}

/**
 * 出来事一覧作成
 * @param	HWND hDlg  ウィンドウハンドル
 * @return	なし
*/
void makeListView(HWND hWnd) {
	LVITEMA item;
	stringstream ss;

	// 初期化
	for (int i = 0; i < SIZE_EVENTS; i++) {
		item.iItem = i;
		SendMessage(hWnd, LVM_DELETEITEM, 0,(WPARAM)&item);
	}

	// データ行
	for (int i = 0; i < SIZE_EVENTS; i++) {
		if (Events[i] == NULL)	break;
		// 区分
		item.mask = LVIF_TEXT;
		item.iItem = i;
		item.iSubItem = COL_CATEGORY;
		item.pszText = (LPSTR)_WS(Vevents[i].category).c_str();
		SendMessage(hWnd, LVM_INSERTITEMA, 0,(WPARAM)&item);
		// 西暦
		item.iSubItem = COL_YEAR;
		ss.str("");
		if (*Vevents[i].year != KINENBI_YEAR) {
			ss << *Vevents[i].year;
		}
		item.pszText = (LPSTR)ss.str().c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// 出来事
		item.iSubItem = COL_EVENT;
		string temp = _WS(Vevents[i].event).substr(0, MAX_LISTVIEWITEM);
		const char* cstr = temp.c_str();
		item.pszText = (LPSTR)cstr;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// ID
		item.iSubItem = COL_ID;
		ss.str("");
		ss << Vevents[i].id;
		item.pszText = (LPSTR)ss.str().c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
	}
}

/**
 * イベントハンドラ：メインウィンドウ
 * @param	HWND hDlg			親ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
*/
INT_PTR CALLBACK processMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	pahooCache* pCC;	// pahooCacheオブジェクト

	HICON hIcon;
	LV_HITTESTINFO lvinfo;
	NM_LISTVIEW *pNMLV;
	TCHAR buff[SIZE_BUFF2 + 1];
	static int subsort[100];
	HWND hDate;
	SYSTEMTIME st;
	LVITEMA item;
	size_t id;
	wstringstream wss;
	int cnt = 0;

	switch(uMsg) {
	// ダイアログ初期化
	case WM_INITDIALOG:
		hParent = hDlg;
		hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
		SendMessage(hParent, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
		makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_EVENTS));
		// オプション読み込み
		loadOption();
		ErrorMessage = "";
		// アプリケーション・ウィンドウ移動
		SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
		break;

	// ボタン押下
	case WM_COMMAND:
		 switch (LOWORD(wParam)) {
		// 検索
		case IDM_EXEC:
		case IDC_BUTTON_EXEC:
			ErrorMessage = "";
			// 年月日取得
			hDate = GetDlgItem(hDlg, IDC_CAL_TODAY);
			DateTime_GetSystemtime(hDate, &st);
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			// Wikipedia
			cnt = getDayToday(st.wMonth, st.wDay, TRUE);
			// 日本記念日協会
			cnt = getAnniversary(st.wMonth, st.wDay, cnt);
			// ソート
			sortEvents(SORT_YEAR_ASC);
			// 一覧表示
			makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_EVENTS));
			setStrEditBox(hDlg, IDC_TEXT_ERROR, ErrorMessage);
			break;
		// 保存
		case IDC_BUTTON_SAVE:
		case IDM_SAVE:
			saveCSV();
			break;
		// ヘルプ
		case IDM_HELP:
			ShellExecute(hParent, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
			break;
		// バージョン表示
		case IDM_VERSION:
			createHelp(hParent, processHelp);
			break;
		// 技術情報
		case IDM_PAHOO:
			ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
			break;
		// コピー
		case IDC_BUTTON_COPY:
		case IDM_COPY:
			setClipboardData(getStrEditBox(hParent, IDC_EDIT_TODAY));
			break;
		// 切り取り
		case IDM_DELETE:
			setStrEditBox(hParent, IDC_EDIT_TODAY, "");
			break;
		// キャッシュ・クリア
		case IDM_CLEAR_CACHE:
			pCC = new pahooCache(LIFE_CACHE_WIKIPEDIA, getMyPath(APPNAME) + DIR_CACHE_WIKIPEDIA, UserAgent);
			pCC->delCache(".+", 0);
			delete pCC;
			break;
		// プログラム終了
		case IDM_QUIT:
			// オプション保存
			saveOption();
			EndDialog(hParent, 0);
			return 0;
		default:
			return 1;
	}
	break;

	// 通知
	case WM_NOTIFY:
		switch(((LPNMHDR)lParam)->idFrom) {
		case IDC_LISTVIEW_EVENTS:
			switch (((LPNMLISTVIEW)lParam)->hdr.code) {
				// 一覧のラベルがクリック
				case LVN_COLUMNCLICK:
					pNMLV = (NM_LISTVIEW *)lParam;
					if (subsort[pNMLV->iSubItem] == SORT_ASC) {
						subsort[pNMLV->iSubItem] = SORT_DEC;
					} else {
						subsort[pNMLV->iSubItem] = SORT_ASC;
					}
					// 出来事一覧の並べ替え
					sortEvents(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
					// 一覧表示
					makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_EVENTS));
					break;
				// 出来事一覧の1行を選択
				case LVN_ITEMCHANGED:
				// 書籍一覧の1行をダブルクリック
				// case NM_DBLCLK:
					GetCursorPos((LPPOINT)&lvinfo.pt);
					ScreenToClient(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo.pt);
					ListView_HitTest(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &lvinfo);
					if ((lvinfo.flags & LVHT_ONITEM) != 0) {
						item.mask = TVIF_HANDLE | TVIF_TEXT;
						item.iItem = lvinfo.iItem;
						item.iSubItem = COL_ID;
						item.pszText = buff;
						item.cchTextMax = SIZE_BUFF2;
						ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
						// データ取得＋表示
						id = atoi(buff);
						wss.str(L"");
						wss << Events[id]->category << _SW("：");
						if (Events[id]->year != KINENBI_YEAR) {
							wss << Events[id]->year << _SW("年：");
						}
						wss << Events[id]->event;
						setStrEditBox(hDlg, IDC_EDIT_TODAY, _WS(wss.str()));
					}
					break;
				default:
					break;
			}
			break;
		default:
			break;
		}
		break;

	// プログラム終了
	case WM_CLOSE:
		// オプション保存
		saveOption();
		EndDialog(hParent, 0);
		return 0;
	}
	return 0;
}

// メイン・プログラム =======================================================
/**
 * Windowsメインプログラム
 * @param	HINSTANCE hInstance			インスタンスハンドル
 * @paramm	HINSTANCE hPrevInstance		未使用(常にNULL)：Win16時代の名残
 * @param	LPSTR lpCmdLine				コマンドライン引数
 * @paramL	int nShowCmd				ウィンドウの表示方法
 * @return	int リターンコード
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
	LoadLibrary("RICHED20.DLL");

	// UserAgent生成
	static OSVERSIONINFOEX os;
	GetVersion2(&os);
	UserAgent = (string)"Mozilla/5.0 (" + APPNAME + "/"
		+ APPVERSION + "/" + MAKER
		+ ", Windows NT " + to_string(os.dwMajorVersion) + "."
		+ to_string(os.dwMinorVersion) + ")";

	hInst = hInstance;
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);

	return 0;
}

/*
** バージョンアップ履歴 =====================================================
 *
 * @version 1.5.1  2026/02/21 使用ライブラリ更新
 * @version 1.5.0  2025/09/11 getDayToday() キャッシュシステムを利用するよう変更，使用ライブラリ更新
 * @version 1.4.6  2025/09/11 getDayToday() 「できごと」取得パターン変更
 * @version 1.4.5  2025/07/17 使用ライブラリ更新, readWebContents引数不足修正
 * @version 1.4.4  2025/03/08 使用ライブラリ更新
 * @version 1.4.3  2024/11/23 makeListView()--debug, 使用ライブラリ更新
 * @version 1.4.2  2024/07/27 getDayToday()取得パターンを変更, getDay使用ライブラリ更新
 * @version 1.4.1  2024/03/16 使用ライブラリ更新
 * @version 1.4.0  2023/11/04 注釈番号を除去，使用ライブラリ更新
 * @version 1.3.4  2023/06/18 使用ライブラリを更新
 * @version 1.3.3  2023/03/11 使用ライブラリを更新
 * @version 1.3.2  2023/01/08 getDayToday() -- の取得パターンを変更した
 * @version 1.3.1  2022/10/16 紀元前パターンを追加
 * @version 1.3.0  2022/09/03 ウィンドウ位置保存
 * @version 1.2.1  2022/09/03 getDayToday() -- 1行4000文字以上なら読み飛ばし
 * @version 1.2    2022/03/26 getDayToday() -- 1行読み込み打ち切りを追加
 * @version 1.1    2021/01/31 記念日を表示，ソース分割
 * @version 1.0    2020/09/12 初版
*/
