/** searchndl.cpp
 * 国立国会図書館検索
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @動作環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-09-01.shtm
 * @使用API		国立国会図書館サーチAPI https://iss.ndl.go.jp/information/api/
*/

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

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

#define MAKER		"pahoo.org"				// 作成者
#define APPNAME		"gsearchndl"			// アプリケーション名
#define APPNAMEJP	"国立国会図書館検索"	// アプリケーション名（日本語）
#define APPVERSION	"1.3.7"					// バージョン
#define APPYEAR		"2020-2026"				// 作成年
#define REFERENCE	"https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-09-01.shtm"	// 参考サイト
#define REFERENCE_NDL	"https://iss.ndl.go.jp/information/api/"	// 国立国会図書館APIについて

// 国立国会図書館API
#define URL_NDLAPI	"https://ndlsearch.ndl.go.jp/api/opensearch?";

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

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

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

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

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

// 検索キー格納用
string Query;

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

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

// UserAgent
string UserAgent;

// 書籍情報格納用クラス
#define SIZE_BOOKS		500		// 格納上限
class _Books {
public:
	bool flag = false;
	char title[SIZE_BUFF + 1] = {};			// 書籍名
	char author[SIZE_BUFF + 1] = {};		// 作者
	char publisher[SIZE_BUFF + 1] = {};		// 出版社
	char pubdate[SIZE_BUFF + 1] = {};		// 出版日
	char link[SIZE_BUFF + 1] = {};			// リンクURL
	char isbn[SIZE_BUFF + 1] = {};			// ISBN番号
	char ndc9[SIZE_BUFF + 1] = {};			// NDC番号
};
unique_ptr<_Books> Books[SIZE_BOOKS] = {};

// ソート用構造体
struct _stBooks {
	char *title;		// 書籍名
	char *author;		// 作者
	char *publisher;	// 出版社
	char *pubdate;		// 出版日
	char *isbn;			// ISBN番号
	char *ndc9;			// NDC番号
	char *link;			// リンクURL
};
vector<_stBooks> Vbooks;

// バージョン情報
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();

// サブ・プログラム =====================================================
typedef void (WINAPI *RtlGetVersion_FUNC)(OSVERSIONINFOEXW*);

/**
 * Windowsバージョン取得
 * @param	OSVERSIONINFOEX* os バージョン情報を格納
 * @return	bool TRUE/FALSEなし
 */
BOOL GetVersion2(OSVERSIONINFOEX* os) {
	HMODULE hMod;
	RtlGetVersion_FUNC func;
#ifdef UNICODE
	OSVERSIONINFOEXW* osw = os;
#else
	OSVERSIONINFOEXW o;
	OSVERSIONINFOEXW* osw = &o;
#endif

	hMod = LoadLibrary(TEXT("ntdll.dll"));
	if (hMod) {
		func = (RtlGetVersion_FUNC)GetProcAddress(hMod, "RtlGetVersion");
		if (func == 0) {
			FreeLibrary(hMod);
			return FALSE;
		}
		ZeroMemory(osw, sizeof(*osw));
		osw->dwOSVersionInfoSize = sizeof(*osw);
		func(osw);
#ifndef UNICODE
		os->dwBuildNumber = osw->dwBuildNumber;
		os->dwMajorVersion = osw->dwMajorVersion;
		os->dwMinorVersion = osw->dwMinorVersion;
		os->dwPlatformId = osw->dwPlatformId;
		os->dwOSVersionInfoSize = sizeof(*os);
//		DWORD sz = sizeof(os->szCSDVersion);
		WCHAR* src = osw->szCSDVersion;
		unsigned char* dtc = (unsigned char*)os->szCSDVersion;
		while (*src) {
			*dtc++ = (unsigned char)*src++;
		}
		*dtc = '\0';
#endif

	} else {
		return FALSE;
	}
	FreeLibrary(hMod);

return TRUE;
}

// クリップボード関連 ======================================================
/**
 * クリップボードに文字列をセット
 * @param	string str 文字列
 * @return	なし
*/
void setClipboardData(string str) {
	char buff[str.size() + 1];
	char_traits<char>::copy(buff, str.c_str(), str.size() + 1);

	OpenClipboard(NULL);
	EmptyClipboard();

	HGLOBAL hText = GlobalAlloc(GHND | GMEM_SHARE, SIZE_BUFF);
	PTSTR pText = (PTSTR)GlobalLock(hText);
	lstrcpy(pText, buff);
	GlobalUnlock(hText);
	SetClipboardData(CF_TEXT, hText);
	CloseClipboard();
}

/**
 * クリップボードから文字列を取得
 * @param	なし
 * @return	文字列／NULL＝取得失敗
*/
char* getClipboardData(void) {
	static char buff[SIZE_BUFF + 1];

	OpenClipboard(NULL);
	HGLOBAL hText = GetClipboardData(CF_TEXT);

	if (hText == NULL) {
		return NULL;
	} else {
		PTSTR pText = (PTSTR)GlobalLock(hText);
		lstrcpy(buff, pText);
		GlobalUnlock(hText);
	}
	CloseClipboard();

	return buff;
}

// GUI処理 =================================================================
/**
 * テキストボックスに文字列を代入
 * @param  HWND		hDlg  ダイアログID
 * @param  unsigned	id    テキストボックスID
 * @param  string	s     設定値
 * @return なし
*/
void setStrEditBox(HWND hDlg, unsigned id, string s) {
	char buff[SIZE_BUFF + 1];
	char_traits<char>::copy(buff, s.c_str(), s.size() + 1);
	SetDlgItemText(hDlg, id, buff);
}

/**
 * テキストボックスから文字列を取得
 * @param  HWND		hDlg  ダイアログID
 * @param  unsigned	id    テキストボックスID
 * @return string 取得文字列
*/
string getStrEditBox(HWND hDlg, unsigned id) {
	char buff[SIZE_BUFF];
	GetDlgItemText(hDlg, id, buff, sizeof(buff));
	return (string)buff;
}

/**
 * テキスト・コード変換：SJIS→wstring（Windows API使用）
 * @param  string src SJISテキスト
 * @return string 変換後テキスト
*/
std::wstring __sjis_wstring(std::string src) {
	auto const dest_size = ::MultiByteToWideChar(CP_ACP, 0U, src.data(), -1, nullptr, 0U);
	std::vector<wchar_t> dest(dest_size, L'\0');

	if (::MultiByteToWideChar(CP_ACP, 0U, src.data(), -1, dest.data(), dest.size()) == 0) {
		throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
	}
	dest.resize(std::char_traits<wchar_t>::length(dest.data()));
	dest.shrink_to_fit();

	return std::wstring(dest.begin(), dest.end());
}

/**
 * テキスト・コード変換：wstring→SJIS（Windows API使用）
 * @param  wstring src テキスト
 * @return string 変換後テキスト（SJIS）
*/
std::string __wstring_sjis(std::wstring src) {
	auto const dest_size = ::WideCharToMultiByte(CP_ACP, 0U, src.data(), -1, nullptr, 0, nullptr, nullptr);
	std::vector<char> dest(dest_size, '\0');
	if (::WideCharToMultiByte(CP_ACP, 0U, src.data(), -1, dest.data(), dest.size(), nullptr, nullptr) == 0) {
		throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
	}
	dest.resize(std::char_traits<char>::length(dest.data()));
	dest.shrink_to_fit();

	return std::string(dest.begin(), dest.end());
}

/**
 * テキスト・コード変換：UTF-8→wstring（Windows API使用）
 * @param  string src UTF-8テキスト
 * @return string 変換後テキスト
*/
std::wstring __utf8_wstring(std::string src) {
	auto const dest_size = ::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, nullptr, 0U);
	std::vector<wchar_t> dest(dest_size, L'\0');
	if (::MultiByteToWideChar(CP_UTF8, 0U, src.data(), -1, dest.data(), dest.size()) == 0) {
		throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
	}
	dest.resize(std::char_traits<wchar_t>::length(dest.data()));
	dest.shrink_to_fit();

	return std::wstring(dest.begin(), dest.end());
}

/**
 * テキスト・コード変換：wstring→UTF-8（Windows API使用）
 * @param  wstring src テキスト
 * @return string 変換後テキスト（UTF-8）
*/
std::string __wstring_utf8(std::wstring src) {
	auto const dest_size = ::WideCharToMultiByte(CP_UTF8, 0U, src.data(), -1, nullptr, 0, nullptr, nullptr);
	std::vector<char> dest(dest_size, '\0');

	if (::WideCharToMultiByte(CP_UTF8, 0U, src.data(), -1, dest.data(), dest.size(), nullptr, nullptr) == 0) {
		throw std::system_error{static_cast<int>(::GetLastError()), std::system_category()};
	}
	dest.resize(std::char_traits<char>::length(dest.data()));
	dest.shrink_to_fit();

	return std::string(dest.begin(), dest.end());
}

/**
 * テキスト・コード変換：SJIS→UTF-8（Windows API使用）
 * @param  wstring src SJISテキスト
 * @return string 変換後テキスト（UTF-8）
*/
std::string sjis_utf8(std::string src) {
	wstring const wide = __sjis_wstring(src);
	return __wstring_utf8(wide);
}

/**
 * テキスト・コード変換：UTF-8→SJIS（Windows API使用）
 * @param  wstring src UTF-8テキスト
 * @return string 変換後テキスト（UTF-8）
*/
std::string utf8_sjis(std::string src) {
	wstring const wide = __utf8_wstring(src);
	return __wstring_sjis(wide);
}
/**
 * ダイアログを親ウィンドウの中央に配置
 * @param	HWND hDlg		自分のハンドラ
 * @global	HWND hParent	親ウィンドウのハンドラ
 * @return	なし
*/
void CenterWindow(HWND hDlg) {
	int  x, y;
	RECT rc;

	GetWindowRect(hParent, &rc);
	x = rc.left + (rc.right - rc.left) / 2;
	y = rc.top + (rc.bottom - rc.top) / 2;

	SetWindowPos(hDlg, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}

// オプション読込／保存 ====================================================
/**
 * AppDataのパスを取得
 * @param	char* appname アプリケーション名
 * @return	string パス
 */
string getMyPath(const char* appname) {
	static TCHAR myPath[MAX_PATH] = "";

	if (strlen(myPath) == 0) {
		if (SHGetSpecialFolderPath(NULL, myPath, CSIDL_APPDATA, 0)) {
			TCHAR *ptmp = _tcsrchr(myPath, _T('\\'));
			if (ptmp != NULL) {
				ptmp = _tcsinc(ptmp);
				*ptmp = _T('\0');
			}
			strcat(myPath, _T("Roaming"));
			CreateDirectory((LPCTSTR)myPath, NULL);
			strcat(myPath, _T("\\pahoo.org"));
			CreateDirectory((LPCTSTR)myPath, NULL);
			strcat(myPath, _T("\\"));
			strcat(myPath, _T(appname));
			CreateDirectory((LPCTSTR)myPath, NULL);
			strcat(myPath, _T("\\"));
		} else {
		}
	}
	return (string)myPath;
}

/**
 * パラメータの初期化
 * @param	なし
 * @return	なし
 */
void initParameter(void) {
	hParent_X = 0;
	hParent_Y = 0;
	Query     = "";
}

/**
 * オプションの読み込み
 * @param	なし
 * @return	なし
 */
void loadParameter(void) {
	ptree pt;

	// 初期値設定
	initParameter();

	// 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 == "query") {
					Query = utf8_sjis((string)it.second.data());
				} else 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;
			Query = "";
			return;
		}
	// 読み込み失敗したら初期値設定
	} catch (xml_parser_error& e) {
		hParent_X = 0;
		hParent_Y = 0;
		Query = "";
		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 saveParameter(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& child1 = pt.add("parameter.param", sjis_utf8(Query));
	child1.add("<xmlattr>.type", "query");
	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	なし
 * @return	なし
 */
void delParameter(void) {
	remove((getMyPath(APPNAME) + APPNAME + ".xml").c_str());

	// 初期値設定
	initParameter();
}

// バージョン表示ダイアログ =================================================
/**
 * イベントハンドラ：バージョン表示ダイアログ
 * @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 << "\"" << "出版日" << "\",";
	outputfile << "\"" << "ISBN" << "\",";
	outputfile << "\"" << "NDC9" << "\",";
	outputfile << "\"" << "リンクURL" << "\"" << endl;

	// データ本体
	for (int i = 0; i < SIZE_BOOKS; i++) {
		if (Books[i] != NULL) {
			outputfile << "\"" << Books[i]->title << "\",";
			outputfile << "\"" << Books[i]->author << "\",";
			outputfile << "\"" << Books[i]->publisher << "\",";
			outputfile << "\"" << Books[i]->pubdate << "\",";
			outputfile << "\"" << Books[i]->isbn << "\",";
			outputfile << "\"" << Books[i]->ndc9 << "\",";
			outputfile << "\"" << Books[i]->link << "\"" << 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);
}

// 国立国会図書館サーチ処理 ================================================
/**
 * 国立国会図書館サーチAPI のURLを取得する
 * @param	char* query		タイトル（SJIS；部分一致）
 *                         またはISBN（10桁または13桁；完全一致または前方一致）
 *                         【省略不可】
 * @param	char* creater	作成者（SJIS；部分一致）
 * @param	char* url		URLを格納
 * @param	size_t sz		urlの最大長
 * @return	なし
*/
void getURL_searchNDL(char *query, char *creater, char *url, size_t sz) {
	const string ndl = URL_NDLAPI;
	static char buff1[SIZE_BUFF + 1], buff2[SIZE_BUFF + 1];

	strncpy(buff1, sjis_utf8((string)query).c_str(), SIZE_BUFF);

	CURL *curl = curl_easy_init();
	strncpy(buff2, curl_easy_escape(curl, (const char *)buff1, strlen(buff1)), SIZE_BUFF);

	// ISBNパターン
	regex re("^[0-9]+$");
	smatch mt;
	string ss;
	ss = (string)buff2;
	if (regex_match(ss, mt, re)) {
		ss = ndl + "isbn=" + (string)buff2;
	// 書名
	} else {
		ss = ndl + "title=" + (string)buff2;
	}
	strncpy(url, ss.c_str(), SIZE_BUFF);

	curl_easy_cleanup(curl);
}

size_t callBackFunk(char* ptr, size_t size, size_t nmemb, string* stream) {
	int realsize = size * nmemb;
	stream->append(ptr, realsize);
	return realsize;
}

/**
 * 検索結果を配列に格納する
 * @param	array $item 配列
 * @param	int   $i    カウンタ
 * @return	int 検索結果の件数
*/
int searchBooks(char *title) {
	// 初期化
	for (int i = 0; i < SIZE_BOOKS; i++) {
		Books[i].reset();
		Books[i] = NULL;
	}

	// cURLによる結果取得
	CURL *curl;
	CURLcode res = (CURLcode)0;
	curl = curl_easy_init();
	string chunk;
	char url[SIZE_BUFF];
	getURL_searchNDL(title, (char *)"", (char *)url, SIZE_BUFF);

	// cURLによる国立国会図書館サーチAPI
	if (curl) {
		curl_easy_setopt(curl, CURLOPT_URL, url);
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callBackFunk);
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, (string*)&chunk);
		res = curl_easy_perform(curl);
		curl_easy_cleanup(curl);
	}
    if (res != CURLE_OK) {
		ErrorMessage = "国立国会図書館サーチAPIのエラー";
		return (-1);
	}

	// XML読み込み
	std::stringstream ss;
	ss << chunk;
	ptree pt;
	xml_parser::read_xml(ss, pt);

	// XML解釈
	ptree tree;
	struct tm dt;
	char buff[SIZE_BUFF + 1];
	int cnt = -1;
	for (auto it : pt.get_child("rss.channel")) {
		// item以外は読み飛ばし
		if (it.first != "item")		continue;
		cnt++;
		Books[cnt] = make_unique<_Books>();

		// 書籍名
		if (optional<string>title = it.second.get_optional<string>("title")) {
			strncpy(Books[cnt]->title, utf8_sjis((string)(title->c_str())).c_str(), SIZE_BUFF);
		}
		// 巻数
		if (optional<string>volume = it.second.get_optional<string>("dcndl:volume")) {
			strncpy(buff, utf8_sjis((string)(volume->c_str())).c_str(), SIZE_BUFF);
			strcat(Books[cnt]->title, " ");
			strcat(Books[cnt]->title, buff);
			strncat(Books[cnt]->title, (char *)"巻", SIZE_BUFF);
		}

		// 作者
		if (optional<string>creator = it.second.get_optional<string>("dc:creator")) {
			strncpy(Books[cnt]->author, utf8_sjis((string)(creator->c_str())).c_str(), SIZE_BUFF);
		}
		if (optional<string>author = it.second.get_optional<string>("author")) {
			if (*Books[cnt]->author == 0) {
				strncpy(Books[cnt]->author, utf8_sjis((string)(author->c_str())).c_str(), SIZE_BUFF);
			}
		}

		// 出版社
		if (optional<string>publisher = it.second.get_optional<string>("dc:publisher")) {
			strncpy(Books[cnt]->publisher, utf8_sjis((string)(publisher->c_str())).c_str(), SIZE_BUFF);
		}

		// 出版日
		if (optional<string>pubdate = it.second.get_optional<string>("pubDate")) {
			{
			istringstream ss(pubdate->c_str());
			ss >> get_time(&dt, "%a, %d %b %Y");
			strftime(buff, SIZE_BUFF, "%Y-%m-%d", &dt);
			}
			strcpy(Books[cnt]->pubdate, buff);
		}
		if (optional<string>dcterms = it.second.get_optional<string>("dcterms:issued")) {
			if (*Books[cnt]->pubdate == 0) {
				if (it.second.get_optional<string>("dcterms:issued.<xmlattr>.xsi:type") == (string)"dcterms:W3CDTF") {
					strncpy(Books[cnt]->pubdate, dcterms->c_str(), SIZE_BUFF);
				}
			}
		}

		// リンクURL
		if (optional<string>link = it.second.get_optional<string>("link")) {
			strncpy(Books[cnt]->link, link->c_str(), SIZE_BUFF);
		}

		// ISBN
		if (optional<string>isbn = it.second.get_optional<string>("dc:identifier")) {
			if (it.second.get_optional<string>("dc:identifier.<xmlattr>.xsi:type") == (string)"dcndl:ISBN") {
				strncpy(Books[cnt]->isbn, isbn->c_str(), SIZE_BUFF);
			}
		}

		// NDC9
		if (optional<string>ndc9 = it.second.get_optional<string>("dc:subject")) {
			if (it.second.get_optional<string>("dc:subject.<xmlattr>.xsi:type") == (string)"dcndl:NDC9") {
				strncpy(Books[cnt]->ndc9, ndc9->c_str(), SIZE_BUFF);
			}
		}
	}
	chunk.clear();

	return cnt;
}

// 記事ソート用ID
enum {
	SORT_TITLE_ASC,
	SORT_TITLE_DEC,
	SORT_AUTHOR_ASC,
	SORT_AUTHOR_DEC,
	SORT_PUBLISHER_ASC,
	SORT_PUBLISHER_DEC,
	SORT_PUBDATE_ASC,
	SORT_PUBDATE_DEC,
	SORT_NDC9_ASC,
	SORT_NDC9_DEC,
	SORT_ISBN_ASC,
	SORT_ISBN_DEC,
	SORT_NONE
};

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

/**
 * ソート用の比較関数：書籍名（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpTitleAsc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.title, right.title) < 0;
}

/**
 * ソート用の比較関数：書籍名（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpTitleDec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.title, right.title) >= 0;
}

/**
 * ソート用の比較関数：作者（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpAuthorAsc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.author, right.author) < 0;
}

/**
 * ソート用の比較関数：作者（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpAuthorDec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.author, right.author) >= 0;
}

/**
 * ソート用の比較関数：出版社（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpPublisherAsc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.publisher, right.publisher) < 0;
}

/**
 * ソート用の比較関数：出版社（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpPublisherDec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.publisher, right.publisher) >= 0;
}

/**
 * ソート用の比較関数：出版日（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpPubDateAsc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.pubdate, right.pubdate) < 0;
}

/**
 * ソート用の比較関数：出版日（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpPubDateDec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.pubdate, right.pubdate) >= 0;
}

/**
 * ソート用の比較関数：ISBN（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpISBNAsc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.isbn, right.isbn) < 0;
}

/**
 * ソート用の比較関数：ISBN（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpISBNDec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.isbn, right.isbn) >= 0;
}

/**
 * ソート用の比較関数：NDC9（昇順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpNDC9Asc(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.ndc9, right.ndc9) < 0;
}

/**
 * ソート用の比較関数：NDC9（降順）
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
bool __cmpNDC9Dec(const _stBooks& left, const _stBooks& right) {
	return strcmp(left.ndc9, right.ndc9) >= 0;
}

/**
 * ソート用変数に代入
 * @param	const _stBooks& left, right
 * @return	比較結果
*/
_stBooks __push_stBooks(unique_ptr<_Books> &ptr) {
	_stBooks s;
	s.title		= ptr->title;
	s.author	= ptr->author;
	s.publisher	= ptr->publisher;
	s.pubdate	= ptr->pubdate;
	s.link		= ptr->link;
	s.isbn		= ptr->isbn;
	s.ndc9		= ptr->ndc9;
	return s;
}

/**
 * 書籍一覧のソート
 * @param	int c  ソート条件
 * @return	なし
*/
void sortBooks(int c) {
	struct {
		int id;
		bool (*fn)(const _stBooks& left, const _stBooks& right);
} table[] = {
	{ SORT_TITLE_ASC,		__cmpTitleAsc },
	{ SORT_TITLE_DEC,		__cmpTitleDec },
	{ SORT_AUTHOR_ASC,		__cmpAuthorAsc },
	{ SORT_AUTHOR_DEC,		__cmpAuthorDec },
	{ SORT_PUBLISHER_ASC,	__cmpPublisherAsc },
	{ SORT_PUBLISHER_DEC,	__cmpPublisherDec },
	{ SORT_PUBDATE_ASC,		__cmpPubDateAsc },
	{ SORT_PUBDATE_DEC,		__cmpPubDateDec },
	{ SORT_ISBN_ASC,		__cmpISBNAsc },
	{ SORT_ISBN_DEC,		__cmpISBNDec },
	{ SORT_NDC9_ASC,		__cmpNDC9Asc },
	{ SORT_NDC9_DEC,		__cmpNDC9Dec }
};
	int i;
	bool (*fn)(const _stBooks& left, const _stBooks& right) = NULL;

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

	// 書籍情報をソート用配列へ
	for (i = 0; i < SIZE_BOOKS; i++) {
		if (Books[i] != NULL) {
			Vbooks.push_back(__push_stBooks(Books[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(Vbooks.begin(), Vbooks.end(), fn);
	}
}

// リストビューの列
enum {
	COL_TITLE,
	COL_AUTHOR,
	COL_PUBLISHER,
	COL_PUBDATE,
	COL_NDC,
	COL_ISBN,
	COL_LINK
};

/**
 * 書籍一覧のフレーム作成
 * @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 = 250;
	lvcol.pszText = (char *)"書籍名";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_TITLE, (WPARAM)&lvcol);
	lvcol.cx = 100;
	lvcol.pszText = (char *)"作者";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_AUTHOR, (WPARAM)&lvcol);
	lvcol.cx = 80;
	lvcol.pszText = (char *)"出版社";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_PUBLISHER, (WPARAM)&lvcol);
	lvcol.cx = 100;
	lvcol.pszText = (char *)"出版日";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_PUBDATE, (WPARAM)&lvcol);
	lvcol.cx = 50;
	lvcol.pszText = (char *)"NDC";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_NDC, (WPARAM)&lvcol);
	lvcol.cx = 120;
	lvcol.pszText = (char *)"ISBN";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_ISBN, (WPARAM)&lvcol);
	lvcol.cx = 0;
	lvcol.pszText = (char *)"URL";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_LINK, (WPARAM)&lvcol);

	// 罫線表示
	DWORD dwStyle = ListView_GetExtendedListViewStyle(hWnd);
	dwStyle = dwStyle | LVS_EX_GRIDLINES | LVS_EX_ONECLICKACTIVATE;
	ListView_SetExtendedListViewStyle(hWnd, dwStyle);
}

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

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

	// データ行
	for (int i = 0; i < SIZE_BOOKS; i++) {
		if (Books[i] == NULL)	break;
		item.mask = LVIF_TEXT;
		item.iItem = i;
		item.iSubItem = COL_TITLE;
		item.pszText = (LPSTR)Vbooks[i].title;
		SendMessage(hWnd, LVM_INSERTITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_AUTHOR;
		item.pszText = (LPSTR)Vbooks[i].author;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_PUBLISHER;
		item.pszText = (LPSTR)Vbooks[i].publisher;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_PUBDATE;
		item.pszText = (LPSTR)Vbooks[i].pubdate;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_NDC;
		item.pszText = (LPSTR)Vbooks[i].ndc9;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_ISBN;
		item.pszText = (LPSTR)Vbooks[i].isbn;
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		item.iSubItem = COL_LINK;
		item.pszText = (LPSTR)Vbooks[i].link;
		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) {
	HICON hIcon;
	char *str;
	LVITEM item;
	LV_HITTESTINFO lvinfo;
	NM_LISTVIEW *pNMLV;
	TCHAR buff[SIZE_BUFF + 1];
	static int subsort[100];

	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);
		ErrorMessage = "";
		// オプション読み込み
		loadParameter();
		setStrEditBox(hDlg, IDC_EDIT_QUERY, Query);
		// アプリケーション・ウィンドウ移動
		SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
		makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
		break;

	// ボタン押下
	case WM_COMMAND:
		 switch (LOWORD(wParam)) {
		// 実行
		case IDM_EXEC:
		case IDC_BUTTON_SEARCH:
			ErrorMessage = "";
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
			// 書籍検索
			searchBooks((char *)Query.c_str());
			sortBooks(SORT_NONE);
			// 一覧表示
			makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
			setStrEditBox(hDlg, IDC_TEXT_ERROR, ErrorMessage);
			break;
		// 保存
		case IDC_BUTTON_SAVE:
		case IDM_SAVE:
			saveCSV();
			break;
		// 設定クリア＋アプリ終了
		case IDM_CLEAR_PARAMETER:
			delParameter();
			EndDialog(hParent, 0);
			return 0;
			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;
		// 国立国会図書館APIについて
		case IDM_NDL:
			ShellExecute(NULL, _T("open"), _T(REFERENCE_NDL), NULL, NULL, SW_RESTORE);
			break;
		// コピー
		case IDM_COPY:
			setClipboardData(getStrEditBox(hParent, IDC_EDIT_QUERY));
			break;
		// 貼り付け
		case IDM_PASTE:
			str = getClipboardData();
			if (str != NULL) {
				setStrEditBox(hParent, IDC_EDIT_QUERY, str);
			}
			break;
		// 切り取り
		case IDM_DELETE:
			setStrEditBox(hParent, IDC_EDIT_QUERY, "");
			break;
		// プログラム終了
		case IDM_QUIT:
			// オプション保存
			saveParameter();
			EndDialog(hParent, 0);
			return FALSE;
		default:
			return 1;
		}
		break;

	// 通知
	case WM_NOTIFY:
		switch(((LPNMHDR)lParam)->idFrom) {
		case IDC_LISTVIEW_BOOKS:
			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;
				}
				// 書籍一覧の並べ替え
				sortBooks(pNMLV->iSubItem * 2 + subsort[pNMLV->iSubItem]);
				// 一覧表示
				makeListView(GetDlgItem(hDlg, IDC_LISTVIEW_BOOKS));
				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_LINK;
					item.pszText = buff;
					item.cchTextMax = SIZE_BUFF;
					ListView_GetItem(((LPNMLISTVIEW)lParam)->hdr.hwndFrom, &item);
					// ブラウザ起動
					ShellExecute(hParent, _T("open"), _T(buff), NULL, NULL, SW_RESTORE);
				}
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		break;

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

// メイン・プログラム =======================================================
/**
 * 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.3.7  2026/05/24 使用ライブラリ更新
 * @version 1.3.6  2026/02/14 使用ライブラリ更新, optional名前衝突回避
 * @version 1.3.5  2025/10/18 使用ライブラリ更新
 * @version 1.3.4  2025/07/05 使用ライブラリ更新
 * @version 1.3.3  2025/03/08 使用ライブラリ更新
 * @version 1.3.2  2024/11/16 使用ライブラリ更新
 * @version 1.3.1  2024/07/27 使用ライブラリ更新
 * @version 1.3.0  2024/03/09 API 1.1版に対応，使用ライブラリ更新
 * @version 1.2.4  2023/10/14 使用ライブラリ更新
 * @version 1.2.3  2023/06/10 使用ライブラリ更新，ISBN検索できない不具合修正
 * @version 1.2.2  2023/03/05 使用ライブラリ更新
 * @version 1.2.1  2022/07/31 使用ライブラリ更新
 * @version 1.2    2022/07/31 ウィンドウ位置・検索キー保存
 * @version 1.1    2022/07/31 UserAgent追加，使用ライブラリ更新
 * @version 1.0    2020/08/29 初版
*/
