/** searchoutletwin.cpp
 * 電源・Wi-Fi利用可能店舗を検索
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @開発環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 *					+ WebView2Loader.dll + Leaflet + OSM (+ GoogleMap)
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-17-01.shtm
 */
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <sstream>
#include <string>
#include <locale>
#include <regex>
#include <winsock2.h>
#include <windows.h>
#include <shlobj.h>
#include <commctrl.h>
#include <objbase.h>
#include <wrl.h>
#include <richedit.h>

#include <curl/curl.h>
#include <boost/format.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string.hpp>
#include "mystrings.h"
#include "apikey.h"
#include "WebView2.h"
#include "pahooWebView2.hpp"
#include "pahooGeocode.hpp"
#include "resource.h"

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

// 定数など ==================================================================
#define MAKER		"pahoo.org"			// 作成者
#define APPNAME		"searchoutletwin"	// アプリケーション名
#define APPNAMEJP	"電源・Wi-Fi利用可能\店舗を検索"	// アプリケーション名（日本語）
#define APPVERSION	"2.2.4"				// バージョン
#define APPYEAR		"2020-26"			// 作成年
#define REFERENCE	"https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-17-01.shtm"	// 参考サイト

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

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

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

// マップID
#define MAP_ID			"map_id"
// 地図の大きさ
#define MAP_WIDTH		550		// 地図の幅（ピクセル）
#define MAP_HEIGHT		320		// 地図の高さ（ピクセル）
// 経度・緯度（初期値）
#define DEF_LONGITUDE	139.766667
double Longitude = DEF_LONGITUDE;
#define DEF_LATITUDE	35.681111
double Latitude  = DEF_LATITUDE;
// 地図拡大率（初期値）
#define DEF_ZOOM	13
int Zoom = DEF_ZOOM;
// 地図の種類（初期値）
#define DEF_MAPTYPE		"GSISTD"
string Maptype = DEF_MAPTYPE;

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

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

// WebView2ウィンドウ
HWND hWebView2;

// ICoreWebView2：pahooWebView2.cpp で定義する．
extern ICoreWebView2* webView2;

// テンポラリファイル名
char tmpFname[MAX_PATH + 1];
wchar_t wUri[MAX_PATH * 2 + 1];

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

// 検索キー格納変数
string Query;

// エラー・メッセージ格納用【変更不可】
string ErrorMessage;

// UserAgent
string UserAgent;

// pahooGeocodeオブジェクト
pahooGeocode* pGC;

// WebView2ハンドラの動作を指定する．【変更不可】
enum class eAction {
	GetLatLong,				// 緯度・経度取得してブラウザ表示
	GetZoomType,			// 拡大率・マップタイプ取得してブラウザ表示
	GetLatLongZoomType,		// 緯度・経度・拡大率・マップタイプ取得してブラウザ表示
	Finish					// プログラム終了
};
eAction selectAction;

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

本アプリケーションはMIT Licenseです．
個人での無償利用が可能です．自由に改造できます．
再配布の際は，下記の著作権表記，およびURLと本使用条件を必ず明記してください．
ただし，モバイラーズオアシスAPIの規定により商用利用はできません．

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

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

本アプリケーションは，下記のライブラリ，フレームワーク，APIを利用しています．
　MinGW（g++）／Boost C++ライブラリ／libcurl／OpenSSL+／
　WebView2Loader.dll／Leaflet／OpenStreetMap／Googleマップ／
　モバイラーズオアシスAPI／Google Geocoding API／
　YOLPコンテンツジオコーダAPI／HeartRails GeoAPI
)")
% APPNAMEJP
% APPVERSION
% APPYEAR
).str();

// サブ・プログラム =====================================================
/**
 * パラメータの初期化
 * @param	なし
 * @return	なし
 */
void initParameter(void) {
	Query     = "";
	Longitude = DEF_LONGITUDE;
	Latitude  = DEF_LATITUDE;
	Zoom      = DEF_ZOOM;
	Maptype   = DEF_MAPTYPE;
	hParent_X = 0;
	hParent_Y = 0;
}

/**
 * パラメータの読み込み
 * @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 == "latitude") {
					Latitude = stod(it.second.data());
				} else if (type == "longitude") {
					Longitude = stod(it.second.data());
				} else if (type == "zoom") {
					Zoom = stoi(it.second.data());
				} else if (type == "maptype") {
					Maptype = (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());
				} else if (type == "query") {
					Query = utf8_sjis((string)it.second.data());
				}
			}
		// 解釈失敗したら初期値設定
		} catch (xml_parser_error& e) {
			initParameter();
			return;
		}
	// 読み込み失敗したら初期値設定
	} catch (xml_parser_error& e) {
		initParameter();
		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

	char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
	snprintf(lng, SIZE_BUFF, "%.5f", Longitude);
	snprintf(lat, SIZE_BUFF, "%.5f", Latitude);

	// XMLファイルへ書き込む
	ptree pt;
	ptree& child1 = pt.add("parameter.param", lat);
	child1.add("<xmlattr>.type", "latitude");
	ptree& child2 = pt.add("parameter.param", lng);
	child2.add("<xmlattr>.type", "longitude");
	ptree& child3 = pt.add("parameter.param", to_string(Zoom));
	child3.add("<xmlattr>.type", "zoom");
	ptree& child4 = pt.add("parameter.param", Maptype);
	child4.add("<xmlattr>.type", "maptype");
	ptree& child5 = pt.add("parameter.param", (string)to_string(hParent_X));
	child5.add("<xmlattr>.type", "wx");
	ptree& child6 = pt.add("parameter.param", (string)to_string(hParent_Y));
	child6.add("<xmlattr>.type", "wy");
	ptree& child7 = pt.add("parameter.param", sjis_utf8(Query));
	child7.add("<xmlattr>.type", "query");

	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();
}

// WebAPI「モバイラーズオアシス電源情報API」 ================================

// 電源・Wi-Fi利用可能店舗
typedef struct _poutlets {
	char id;					// 識別子
	std::wstring title;			// 店名
	std::wstring address;		// 住所
	std::wstring phone;			// 電話番号
	double latitude;			// 緯度
	double longitude;			// 経度
	std::string url;			// 店舗公式サイトのURL
	std::string mo_url;			// モバイラーズオアシスでの情報ページURL
	std::wstring charge;		// 充電情報
	std::wstring powersupply;	// 電源情報
	std::wstring wireless;		// Wi-Fi情報

} poutlets_t;

// クラス定義
class pahooOasisMogya {

public:
// コンストラクタ
pahooOasisMogya();
~pahooOasisMogya();

std::wstring errmsg = L"";		// エラーメッセージ
std::string webapi = "";		// 直近で使用したWebAPIのURL

// 電源・Wi-Fi利用可能店舗情報
poutlets_t Poutlets[__SIZE_PPOINTS];

bool isError(void);
std::wstring getError(void);
void resetError(void);
void setError(std::wstring msg);
std::string getwebapi(void);

int getResults_OasisMogya(double latitude, double longitude, double distance);

};

// pahooOasisMogya:オブジェクト
pahooOasisMogya* pOM;

/**
 * コンストラクタ
 */
pahooOasisMogya::pahooOasisMogya() {
}

// デストラクタ
pahooOasisMogya::~pahooOasisMogya() {
}

/**
 * エラー状況
 * @param	なし
 * @return	bool TRUE:異常／FALSE:正常
*/
bool pahooOasisMogya::isError(void) {
	return this->errmsg.length() > 0;
}

/**
 * エラーメッセージ取得
 * @param	なし
 * @return	wstring 現在発生しているエラーメッセージ
*/
wstring pahooOasisMogya::getError(void) {
	return this->errmsg;
}

/**
 * エラーをリセットする
 * @param	なし
 * @return	なし
*/
void pahooOasisMogya::resetError(void) {
	errmsg = L"";
}

/**
 * エラーをセットする
 * @param	wstring msg エラーメッセージ
 * @return	なし
*/
void pahooOasisMogya::setError(wstring msg) {
	errmsg = msg;
}

/**
 * 直近で使用したWebAPIのURL取得
 * @param	なし
 * @return	string URL
*/
string pahooOasisMogya::getwebapi(void) {
	return this->webapi;
}

/**
 * モバイラーズオアシス電源情報API から必要な情報を配列に格納する
 * @param	double latitude  緯度（世界測地系）
 * @param	double longitude 経度（世界測地系）
 * @param	double distance  範囲（メートル）
 * @return	int ヒット数／(-1)：エラー発生
*/
int pahooOasisMogya::getResults_OasisMogya(double latitude, double longitude, double distance) {
	// モバイラーズオアシス電源情報API呼び出し
	char sn[SIZE_BUFF + 1], ss[SIZE_BUFF + 1];
	char se[SIZE_BUFF + 1], sw[SIZE_BUFF + 1];
	double n, w, s, e;

	pGC->getPointDistance(longitude, latitude, distance, 0 - distance, &w, &n);
	pGC->getPointDistance(longitude, latitude, 0 - distance, distance, &e, &s);

	snprintf(sn, SIZE_BUFF, "%.5f", n);
	snprintf(ss, SIZE_BUFF, "%.5f", s);
	snprintf(se, SIZE_BUFF, "%.5f", e);
	snprintf(sw, SIZE_BUFF, "%.5f", w);
	this->webapi = "https://oasis.mogya.com/api/v1/spots.json?n=" + (string)sn + "&w=" + (string)sw + "&s=" + (string)ss + "&e=" + (string)se;

	// モバイラーズオアシス電源情報API応答
	static string contents = "";
	static wstring ucontents = L"";

	int httpStatus = 0;
	bool res = readWebContents(this->webapi, UserAgent, &contents, &httpStatus);
	cout << httpStatus << endl;
	if (res == FALSE) {
		this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
		return (-1);
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		ErrorMessage = "モバイラーズオアシス電源情報APIの接続エラーが発生しました";
		return (-1);
	}

	// 配列の初期化
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		this->Poutlets[i].id = 0;
		this->Poutlets[i].title = this->Poutlets[i].address = L"";
		this->Poutlets[i].phone = L"";
		this->Poutlets[i].url = this->Poutlets[i].mo_url = "";
		this->Poutlets[i].latitude = this->Poutlets[i].longitude = 0.0;
	}

	wregex re1(_SW("用途\\:充電"));
	wregex re2(_SW("電源\\:"));
	wregex re3(_SW("用途\\:"));
	wregex re4(_SW("NG|ng"));
	wsmatch mt1;

	// JSON読み込み
	int cnt = 0;
	try {
		std::stringstream ss;
		ss << contents;
		ptree pt;
		json_parser::read_json(ss, pt);

		// 応答チェック
		if (optional<string>str = pt.get_optional<string>("status")) {
			if (str.get() == "OK") {
			} else {
				this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
				return (-1);
			}
		} else {
			this->errmsg = _SW("モバイラーズオアシス電源情報APIの応答エラーが発生しました");
			return (-1);
		}

		// JSON解釈
		try {
			for (auto it : pt.get_child("results")) {
				if (cnt >= __SIZE_PPOINTS) {
					break;
				}
				// 店名
				if (optional<string>title = it.second.get_optional<string>("title")) {
					this->Poutlets[cnt].title = _UW(title.get());
				}
				// 住所
				if (optional<string>address = it.second.get_optional<string>("address")) {
					this->Poutlets[cnt].address = _UW(address.get());
				}
				// 緯度
				if (optional<string>lat = it.second.get_optional<string>("latitude")) {
					this->Poutlets[cnt].latitude = stod(lat.get());
				}
				// 経度
				if (optional<string>lng = it.second.get_optional<string>("longitude")) {
					this->Poutlets[cnt].longitude = stod(lng.get());
				}
				// URL
				if (optional<string>url = it.second.get_optional<string>("url")) {
					this->Poutlets[cnt].url = url.get();
				}
				// moURL
				if (optional<string>mo_url = it.second.get_optional<string>("mo_url")) {
					this->Poutlets[cnt].mo_url = mo_url.get();
				}
				// 充電情報
				this->Poutlets[cnt].charge = L"";
				for (auto it2 : it.second.get_child("tags")) {
					if (optional<string>name = it2.second.get_optional<string>("name")) {
						wstring ws = _UW(name.get());
						if (regex_search(ws, mt1, re1)) {
							this->Poutlets[cnt].charge = _SW("可能\");
						}
					}
				}
				// 電源情報
				this->Poutlets[cnt].powersupply = L"";
				int key = 0;
				for (auto it2 : it.second.get_child("tags")) {
					if (optional<string>name = it2.second.get_optional<string>("name")) {
						wstring ws = _UW(name.get());
						if (regex_search(ws, mt1, re4)) {
							this->Poutlets[cnt].powersupply = L"";
							break;
						}
						if (regex_search(ws, mt1, re2)) {
							wstring wstr;
							if (key > 0) {
								wstr = _SW(",");
							} else {
								wstr = L"";
							}
							wstr += regex_replace(ws, re2, L"");
							this->Poutlets[cnt].powersupply += wstr;
							key++;
						}
					}
				}
				// Wi-Fi情報
				this->Poutlets[cnt].wireless = L"";
				for (auto it2 : it.second.get_child("wireless")) {
					if (optional<string>name = it2.second.get_optional<string>("name")) {
						wstring ws = _UW(name.get());
						wstring wstr;
						if (key > 0) {
							wstr = _SW(",");
						} else {
							wstr = L"";
						}
						wstr += regex_replace(ws, re3, L"");
						this->Poutlets[cnt].wireless += wstr;
						key++;
					}
				}
				// 識別子
				this->Poutlets[cnt].id = {(char)(65 + cnt)};
				cnt++;
			}
		// JSON解釈エラー
		} catch(ptree_bad_path& e) {
			this->errmsg = _SW("モバイラーズオアシス電源情報APIの検索エラーが発生しました");
			return (-1);
		}

	// 読み込みエラー
	} catch(json_parser_error& e) {
		this->errmsg = _SW("モバイラーズオアシス電源情報APIの接続エラーが発生しました");
		return (-1);
	}
	contents.clear();

	return cnt;
}

/**
 * 地図に表示するインフォメーション作成
 * @param	int cnt 表示するインフォメーション数
 * @return	重複削除後のインフォメーション数
 */
int makeInformation(int cnt) {
	// 初期化
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		pGC->Ppoints[i].latitude = pGC->Ppoints[i].longitude = 0.0;
		pGC->Ppoints[i].title = pGC->Ppoints[i].address = pGC->Ppoints[i].description = L"";
		pGC->Ppoints[i].icon = "";
	}

	// インフォメーション作成
	int j = -1;
	for (int i = 0; i < cnt; i++) {
		// 記述内容準備
		string site;
		if (pOM->Poutlets[i].url == "") {
			site = "";
		} else {
			site = "<a href=\"" + pOM->Poutlets[i].url + "\" target=\"_blank\">⇒公式サイト</a><br />";
		}
		string charge;
		if (pOM->Poutlets[i].charge == L"") {
			charge = "なし";
		} else {
			charge = _WS(pOM->Poutlets[i].charge);
		}
		string powersupply;
		if (pOM->Poutlets[i].powersupply == L"") {
			powersupply = "なし";
		} else {
			powersupply = _WS(pOM->Poutlets[i].powersupply);
		}
		string wireless;
		if (pOM->Poutlets[i].wireless == L"") {
			wireless = "なし";
		} else {
			wireless = _WS(pOM->Poutlets[i].wireless);
		}
		string str =(boost::format(R"(<span class="small"><a href="%1%" target="_blank">%2%</a><br />%3%住所：%4%<br />充電：%5%<br />電源：%6%<br />Wi-Fi：%7%</span>)")
% pOM->Poutlets[i].mo_url
% _WS(htmlspecialchars(pOM->Poutlets[i].title))
% site
% _WS(htmlspecialchars(pOM->Poutlets[i].address))
% charge
% powersupply
% wireless
).str();
		// 直前の地点と緯度・経度が同じ
		if ((i > 0)
			&& (pOM->Poutlets[i].latitude == pOM->Poutlets[i - 1].latitude)
			&& (pOM->Poutlets[i].longitude == pOM->Poutlets[i - 1].longitude)) {
			pGC->Ppoints[j].latitude  = pOM->Poutlets[i].latitude;
			pGC->Ppoints[j].longitude = pOM->Poutlets[i].longitude;
			pGC->Ppoints[j].description += L"<br />" + _SW(str);
		// 緯度・経度が異なる
		} else {
			j++;
			pGC->Ppoints[j].latitude  = pOM->Poutlets[i].latitude;
			pGC->Ppoints[j].longitude = pOM->Poutlets[i].longitude;
			pGC->Ppoints[j].description = _SW(str);
		}
		// 識別子の振り直し
		pOM->Poutlets[i].id = {(char)(65 + j)};
	}

	// 重複の削除
	for (int i = j + 1; i < __SIZE_PPOINTS; i++) {
		pGC->Ppoints[i].latitude = pGC->Ppoints[i].longitude = 0.0;
		pGC->Ppoints[i].title = pGC->Ppoints[i].address = pGC->Ppoints[i].description = L"";
		pGC->Ppoints[i].icon = "";
	}

	return j + 1;
}

// 地図表示（ブラウザ） ======================================================
/**
 * 地図表示用HTML生成
 * @param	int cnt 表示するインフォメーション数
 * @return	string 生成したHTML文
 */
string makeMapHTML(int cnt=0) {
	string script = pGC->makeMapLeaflet(MAP_ID, Longitude, Latitude, Zoom, Maptype, pGC->Ppoints, cnt);
	string html = (boost::format(R"(<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="SJIS">
<title>電源・Wi-Fi利用可能店舗を検索</title>
<meta name="author" content="studio pahoo" />
<meta name="copyright" content="studio pahoo" />
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
%8%
</head>
<body>
<div id="%1%" style="width:%2%px; height:%3%px;"></div>
<form>
<input id="latitude"  type="hidden" value="%4%" />
<input id="longitude" type="hidden" value="%5%" />
<input id="zoom"      type="hidden" value="%6%" />
<input id="maptype"   type="hidden" value="%7%" />
</form>
</body>
</html>
)")
% MAP_ID						// 地図ID
% MAP_WIDTH						// 地図の幅
% MAP_HEIGHT					// 地図の高さ
% Latitude						// 緯度
% Longitude						// 経度
% Zoom							// 地図拡大率
% Maptype						// 地図タイプ
% script						// 地図描画スクリプト
).str();

	return html;
}

/**
 * エラーメッセージ表示用HTML生成
 * @param	なし
 * @return	string 生成したHTML文
 */
string makeErrorHTML(void) {
	string msg;
	if (pGC->isError()) {
		msg = _WS(pGC->getError());
	} else {
		msg = ErrorMessage;
	}
	return (boost::format(R"(<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="SJIS">
<title>エラー発生</title>
<meta name="author" content="studio pahoo" />
<meta name="copyright" content="studio pahoo" />
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
</head>
<body>
<p style="color:red; font-size:large;">%1%</p>
</body>
</html>
)")
% msg
).str();
}

/**
 * ブラウザ・コントロールを表示
 * @param	char* tmpname 読み込むHTMLファイル名
 * @param	int cnt 表示するインフォメーション数
 * @return	なし
 */
void viewBrowser(const char *tmpname, int cnt=0) {
	string html;
	string fname;
	ofstream ofs;

	// 表示用HTMLファイル作成
	if ((ErrorMessage == "") && (! pGC->isError())) {
		html = makeMapHTML(cnt);
	} else {
		html = makeErrorHTML();
	}
	ofs.open(tmpname);
	ofs << html;
	ofs.close();

	fname = (string) tmpname;
	std::replace(fname.begin(), fname.end(), '\\', '/');

	if (isWeb2Ready()) {
		webView2->Reload();
	}
}

// 店舗一覧作成（リストビュー） =============================================
// リストビューの列
enum {
	COL_ID,
	COL_TITLE,
	COL_ADDRESS,
	COL_CHARGE,
	COL_POWERSUPPLY,
	COL_WIRELESS,
	COL_LINK
};

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

	lvcol.cx = 25;
	lvcol.fmt = LVCFMT_CENTER;
	lvcol.pszText = (char *)"ID";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_ID, (WPARAM)&lvcol);
	lvcol.cx = 150;
	lvcol.fmt = LVCFMT_LEFT;
	lvcol.pszText = (char *)"店名";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_TITLE, (WPARAM)&lvcol);
	lvcol.cx = 250;
	lvcol.fmt = LVCFMT_LEFT;
	lvcol.pszText = (char *)"住所";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_ADDRESS, (WPARAM)&lvcol);
	lvcol.cx = 25;
	lvcol.fmt = LVCFMT_CENTER;
	lvcol.pszText = (char *)"充電";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_CHARGE, (WPARAM)&lvcol);
	lvcol.cx = 25;
	lvcol.fmt = LVCFMT_CENTER;
	lvcol.pszText = (char *)"電源";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_POWERSUPPLY, (WPARAM)&lvcol);
	lvcol.cx = 25;
	lvcol.fmt = LVCFMT_CENTER;
	lvcol.pszText = (char *)"Wi-Fi";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_WIRELESS, (WPARAM)&lvcol);
	lvcol.cx = 0;
	lvcol.fmt = LVCFMT_CENTER;
	lvcol.pszText = (char *)"LINK";
	SendMessage(hWnd, LVM_INSERTCOLUMNA, COL_LINK, (WPARAM)&lvcol);

	// 罫線表示
	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;

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

	// データ行
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		if (pOM->Poutlets[i].id == 0) {
			break;
		}
		// ID
		item.mask = LVIF_TEXT;
		item.iItem = i;
		item.iSubItem = COL_ID;
		string s = {pOM->Poutlets[i].id};
		item.pszText = (LPSTR)(s.c_str());
		SendMessage(hWnd, LVM_INSERTITEMA, 0,(WPARAM)&item);
		// 店名
		item.iSubItem = COL_TITLE;
		item.pszText = (LPSTR)_WS(pOM->Poutlets[i].title).substr(0, MAX_LISTVIEWITEM).c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// 住所
		item.iSubItem = COL_ADDRESS;
		item.pszText = (LPSTR)_WS(pOM->Poutlets[i].address).substr(0, MAX_LISTVIEWITEM).c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// 充電
		item.iSubItem = COL_CHARGE;
		if (pOM->Poutlets[i].charge != L"") {
			item.pszText = (LPSTR)"○";
		} else {
			item.pszText = (LPSTR)"";
		}
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// 電源
		item.iSubItem = COL_POWERSUPPLY;
		if (pOM->Poutlets[i].powersupply != L"") {
			item.pszText = (LPSTR)"○";
		} else {
			item.pszText = (LPSTR)"";
		}
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// Wi-Fi
		item.iSubItem = COL_WIRELESS;
		if (pOM->Poutlets[i].wireless != L"") {
			item.pszText = (LPSTR)"○";
		} else {
			item.pszText = (LPSTR)"";
		}
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
		// リンク
		item.iSubItem = COL_LINK;
		item.pszText = (LPSTR)pOM->Poutlets[i].mo_url.c_str();
		SendMessage(hWnd, LVM_SETITEMA, 0,(WPARAM)&item);
	}
}

/**
 * 電源・Wi-Fi利用可能店舗を検索し，ブラウザ表示と一覧表示を行う．
 * @param	HWND hDlg  ウィンドウハンドル
 * @return	なし
*/
void viewMapList(void) {
	int cnt = 0;

	// 電源・Wi-Fi利用可能店舗検索
	cnt = pOM->getResults_OasisMogya(Latitude, Longitude, 100);
	if (cnt > 0) {
		cnt = makeInformation(cnt);
	}

	// ブラウザ・コントロールに表示
	viewBrowser(tmpFname, cnt);

	// 電源・Wi-Fi利用可能店舗の一覧表示
	makeListView(GetDlgItem(hParent, IDC_LISTVIEW_OUTLETS));
}

// 保存・コピー =============================================================
/**
 * 一覧情報をCSVファイルに保存する：保存処理
 * @param	const char* fname 保存ファイル名
 * @return	なし
 */
void __saveCSV(const char *fname) {
	char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
	string str;
	ofstream outputfile(fname);

	// ラベル
	outputfile << "\"" << "ID" << "\",";
	outputfile << "\"" << "店名" << "\",";
	outputfile << "\"" << "住所" << "\",";
	outputfile << "\"" << "緯度" << "\",";
	outputfile << "\"" << "経度" << "\",";
	outputfile << "\"" << "充電" << "\",";
	outputfile << "\"" << "電源" << "\",";
	outputfile << "\"" << "Wi-Fi" << "\"";
	outputfile << endl;

	// データ本体
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		if (pOM->Poutlets[i].id != 0) {
			snprintf(lng, SIZE_BUFF, "%.5f", pOM->Poutlets[i].longitude);
			snprintf(lat, SIZE_BUFF, "%.5f", pOM->Poutlets[i].latitude);
			outputfile << "\"" << pOM->Poutlets[i].id << "\",";
			outputfile << "\"" << _WS(pOM->Poutlets[i].title) << "\",";
			outputfile << "\"" << _WS(pOM->Poutlets[i].address) << "\",";
			outputfile << "\"" << lat << "\",";
			outputfile << "\"" << lng << "\",";
			if (pOM->Poutlets[i].charge == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].charge);
			}
			outputfile << "\"" << str << "\",";
			if (pOM->Poutlets[i].powersupply == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].powersupply);
			}
			outputfile << "\"" << str << "\",";
			if (pOM->Poutlets[i].wireless == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].wireless);
			}
			outputfile << "\"" << str << "\"";
			outputfile << 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);
}

/**
 * クリップボードにコピーする文字列を生成
 * @param	なし
 * @return	string コピーする文字列
 */
string copyText(void) {
	char lng[SIZE_BUFF + 1], lat[SIZE_BUFF + 1];
	string str;
	ostringstream oss;

	// ラベル
	oss << "ID" << "\t";
	oss << "店名" << "\t";
	oss << "住所" << "\t";
	oss << "緯度" << "\t";
	oss << "経度" << "\t";
	oss << "充電" << "\t";
	oss << "電源" << "\t";
	oss << "Wi-Fi" << endl;

	// データ本体
	for (int i = 0; i < __SIZE_PPOINTS; i++) {
		if (pOM->Poutlets[i].id != 0) {
			snprintf(lng, SIZE_BUFF, "%.5f", pOM->Poutlets[i].longitude);
			snprintf(lat, SIZE_BUFF, "%.5f", pOM->Poutlets[i].latitude);
			oss << pOM->Poutlets[i].id << "\t";
			oss << _WS(pOM->Poutlets[i].title) << "\t";
			oss << _WS(pOM->Poutlets[i].address) << "\t";
			oss << lat << "\t";
			oss << lng << "\t";
			if (pOM->Poutlets[i].charge == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].charge);
			}
			oss << str << "\t";
			if (pOM->Poutlets[i].powersupply == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].powersupply);
			}
			oss << str << "\t";
			if (pOM->Poutlets[i].wireless == L"") {
				str = "なし";
			} else {
				str = _WS(pOM->Poutlets[i].wireless);
			}
			oss << str << endl;
		}
	}
	return oss.str();
}


// バージョン表示ダイアログ =================================================
/**
 * イベントハンドラ：バージョン表示ダイアログ
 * @param	HWND hDlg			ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
 */
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);
}

// WebView2制御用関数群 ======================================================
/**
 * WebView2：ダイアログを初期化する
 * @param	HWND hDlg	親ウィンドウ・ハンドラ
 * @return	なし
*/
void initDialog(HWND hDlg) {
	HICON hIcon;
	hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
	SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
	ErrorMessage = "";

	// カーソルを砂時計に
	SetCursor(LoadCursor(NULL, IDC_WAIT));
	// オプション読み込み
	loadParameter();
	// アプリケーション・ウィンドウ移動
	SetWindowPos(hDlg, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));

	// テンポラリ・ファイル名取得
	getTempFname(tmpFname);
	size_t wLen = 0;
	mbstowcs_s(&wLen, wUri, MAX_PATH * 2, tmpFname, MAX_PATH);
	// WebView2生成
	hWebView2 = createWebView2(hInst, hDlg, 10, 50, MAP_WIDTH + 20, MAP_HEIGHT + 20, wUri);
	// 電源・Wi-Fi利用可能店舗の一覧表フレーム作成
	makeListViewFrame(GetDlgItem(hDlg, IDC_LISTVIEW_OUTLETS));

	// メッセージ・ループ
	MSG msg;
	static bool flagWebView2 = false;
	while (GetMessage(&msg, nullptr, 0, 0)) {
		if (msg.message == WM_QUIT) {
			// オプション保存
			// saveOption();
			DestroyWindow(hDlg);
		}
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		if (!flagWebView2 && isWeb2Ready()) {
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			// ブラウザ表示と一覧表示
			viewMapList();
			flagWebView2 = true;
		}
	}
}

/**
 * WebView2ハンドラ処理．
 * selectActionの値によって動作を変える。
 * @param	char* result	JavaScript終了ハンドラから渡るデータ
 * @return	なし
*/
void execScriptCompleted(string result) {
	double lat = 0, lng = 0;
	int zm = 0;
	string mt;

	// パラメータを読み込む
	vector<string> tokens;
	split(tokens, result, is_any_of(",\""));
	int cnt = 0;
	for (string ss: tokens) {
		switch (cnt) {
			case 1:
				lng = stod(ss);
				break;
			case 2:
				lat = stod(ss);
				break;
			case 3:
				zm = stoi(ss);
				break;
			case 4:
				mt = ss;
				break;
		}
		cnt++;
	}

	switch (selectAction) {
		// 緯度・経度を取得してブラウザに表示
		case eAction::GetLatLong:
			Longitude = lng;
			Latitude  = lat;
			// ブラウザ表示と一覧表示
			viewMapList();
			break;
		// 拡大率・マップタイプを取得してブラウザに表示
		case eAction::GetZoomType:
			Zoom = zm;
			Maptype = mt;
			// ブラウザ表示と一覧表示
			viewMapList();
			break;
		// 緯度・経度・拡大率・マップタイプを取得してブラウザに表示
		case eAction::GetLatLongZoomType:
			Longitude = lng;
			Latitude  = lat;
			Zoom = zm;
			Maptype = mt;
			// ブラウザ表示と一覧表示
			viewMapList();
			break;
		// プログラムを終了する．
		default:
			Longitude = lng;
			Latitude  = lat;
			Zoom = zm;
			Maptype = mt;
			// テンポラリ・ファイルを削除する．
			remove(tmpFname);
			// パラメータを保存する．
			saveParameter();
			// プログラムを終了する
			EndDialog(hParent, 0);
			DestroyWindow(hParent);
			break;
	}
//	cout << "Longitude: " << Longitude << endl;
//	cout << "Latitude: " << Latitude << endl;
//	cout << "Zoom: " << Zoom << endl;
//	cout << "Maptype: " << Maptype << endl;
}

/**
 * WebView2：JavaScript終了ハンドラ
 * @param	HWND hDlg			親ウィンドウ・ハンドラ
 * @paramm	UINT uMsg			メッセージ識別子
 * @param	WPARAM wParam		メッセージの最初のパラメータ
 * @paramL	PARAM lParam		メッセージの2番目のパラメータ
 * @return	INT_PTR CALLBACK	TRUE：メッセージ処理完了／FALSE：未完了
*/
class MyScriptCompletedHandler : public ICoreWebView2ExecuteScriptCompletedHandler {
public:
	HRESULT STDMETHODCALLTYPE Invoke(HRESULT error, PCWSTR result) override {
		if (SUCCEEDED(error)) {
			// PCWSTR → char* 変換
			int requiredSize = WideCharToMultiByte(CP_UTF8, 0, result, -1, nullptr, 0, nullptr, nullptr);
			char* buffer = new char[requiredSize];
			WideCharToMultiByte(CP_UTF8, 0, result, -1, buffer, requiredSize, nullptr, nullptr);
			std::string res(buffer);
			// JavaScript終了時に実行する関数
			execScriptCompleted(res);
		}
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
		return E_NOINTERFACE;
	}

	ULONG STDMETHODCALLTYPE AddRef(void) override {
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release(void) override {
		return 1;
	}
};

// WebView2のJavaScriptを実行してDOM要素を取得
MyScriptCompletedHandler* scriptCompletedHandler = new MyScriptCompletedHandler();

/**
 * WebView2でスクリプトを実行した後，selectActionで指定したアクションを実行する．
 * @param	なし
 * @return	なし
*/
void execScriptAndAction(void) {
	// プログラム終了時に実行するJavaScript
	LPCWSTR js = L"document.getElementById('longitude').value + ',' + document.getElementById('latitude').value + ',' + document.getElementById('zoom').value + ',' + document.getElementById('maptype').value;";

	webView2->ExecuteScript(js, scriptCompletedHandler);
}

// メインウィンドウ ==========================================================
/**
 * イベントハンドラ：メインウィンドウ
 * @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) {
	TCHAR buff[SIZE_BUFF + 1];
	LVITEM item;
	LV_HITTESTINFO lvinfo;
	wstring val;
	wstring wid;

	switch (uMsg) {
	// ダイアログ初期化
	case WM_INITDIALOG:
		break;

	// ボタン押下
	case WM_COMMAND:
		if ((ErrorMessage != "") | pGC->isError()) {
			break;
		}
		switch (LOWORD(wParam)) {
		// 検索
		case IDM_EXEC:
		case IDC_BUTTON_EXEC:
			// カーソルを砂時計に
			SetCursor(LoadCursor(NULL, IDC_WAIT));
			// 検索キーがあればジオコードAPI呼び出し
			Query = getStrEditBox(hDlg, IDC_EDIT_QUERY);
			if (Query.length() > 0) {
				if (pGC->searchPoints(_SW(Query), UserAgent, 0) > 0) {
					Longitude = pGC->Ppoints[0].longitude;
					Latitude  = pGC->Ppoints[0].latitude;
				}
				// ブラウザ表示と一覧表示
				selectAction = eAction::GetZoomType;
				execScriptAndAction();
			} else {
				// ブラウザ表示と一覧表示
				selectAction = eAction::GetLatLongZoomType;
				execScriptAndAction();
			}
			// エラー・リセット
			ErrorMessage = "";
			pGC->resetError();
			break;
		// リセット
		case IDC_BUTTON_RESET:
		case IDM_RESET:
			setStrEditBox(hDlg, IDC_EDIT_QUERY, "");
			ErrorMessage = "";
			pGC->resetError();
			break;
		// 保存
		case IDC_BUTTON_SAVE:
		case IDM_SAVE:
			saveCSV();
			break;
		// GoogleAPIキー保存
		case IDM_GOOGLE_APIKEY:
			createSetAPIkey(hDlg, LABEL_GOOGLE_API, FNAME_GOOGLE_API, URL_GOOGLE_API, &pGC->GoogleAPIkey);
			pGC->readGoogleApiKey();
			// エラー・リセット
			ErrorMessage = "";
			pGC->resetError();
			// GoogleAPIキー未登録ならデフォルトのマップタイプに戻す．
			Maptype = pGC->isGoogleApiKey() ? "GMRD" : DEF_MAPTYPE;
			// ブラウザ・コントロールに表示
			viewBrowser(tmpFname);
			break;
		// Yahoo!アプリケーションID保存
		case IDM_YAHOO_APIKEY:
			createSetAPIkey(hDlg, LABEL_YAHOO_API, FNAME_YAHOO_API, URL_YAHOO_API, &pGC->YahooAPIkey);
			// エラー・リセット
			ErrorMessage = "";
			pGC->resetError();
			// ブラウザ・コントロールに表示
			viewBrowser(tmpFname);
			break;
		// 設定クリア＋アプリ終了
		case IDM_CLEAR_PARAMETER:
			delParameter();
			// テンポラリ・ファイル削除
			remove(tmpFname);
			EndDialog(hDlg, 0);
			return 0;
			break;
		// ヘルプ
		case IDM_HELP:
			ShellExecute(hDlg, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
			break;
		// バージョン表示
		case IDM_VERSION:
			createHelp(hDlg, processHelp);
			break;
		// 解説サイト
		case IDM_PAHOO:
			ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
			break;
		// コピー
		case IDM_COPY:
		case IDC_BUTTON_COPY:
			setClipboardData(copyText());
			break;
		// 切り取り
		case IDM_DELETE:
			setStrEditBox(hDlg, IDC_EDIT_QUERY, "");
			break;
		// プログラム終了
		case IDM_QUIT:
			selectAction = eAction::Finish;
			execScriptAndAction();
			break;
		default:
			return 1;
		}
		break;

	// 通知
	case WM_NOTIFY:
		switch(((LPNMHDR)lParam)->idFrom) {
		case IDC_LISTVIEW_OUTLETS:
			switch (((LPNMLISTVIEW)lParam)->hdr.code) {
			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(hDlg, _T("open"), _T(buff), NULL, NULL, SW_RESTORE);
				}
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
		break;

	// プログラム終了
	case WM_CLOSE:
		selectAction = eAction::Finish;
		execScriptAndAction();
		break;
	}
	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) + ")";

	// pahooGeocodeオブジェクト
	pGC = new pahooGeocode(APPNAME);
	// pahooOasisMogyaオブジェクト
	pOM = new pahooOasisMogya();

	// メイン・ダイアログ
	HWND hDlg = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
	hInst = hInstance;
	hParent = hDlg;
	// メイン・ダイアログを初期化する．
	initDialog(hDlg);

	// オブジェクト解放
	delete pGC;
	delete pOM;

	return 0;
}

/*
 ** バージョンアップ履歴 =====================================================
 *
 * @version 2.2.4  2026/06/06 使用ライブラリ更新
 * @version 2.2.3  2026/03/15 使用ライブラリ更新
 * @version 2.2.2  2025/11/23 使用ライブラリ更新
 * @version 2.2.1  2025/07/26 使用ライブラリ更新
 * @version 2.2.0  2025/03/22 Leafletアクセス可否チェック追加,使用ライブラリ更新
 * @version 2.1.2  2024/11/30 使用ライブラリ更新
 * @version 2.1.1  2024/08/24 使用ライブラリ更新
 * @version 2.1.0  2024/05/03 API入力処理を改良
 * @version 2.0.0  2024/04/28 Edgeブラウザ対応，64ビット対応
 * @version 1.1.4  2024/04/07 使用ライブラリ更新
 * @version 1.1.3  2023/12/17 使用ライブラリ更新
 * @version 1.1.2  2023/07/23 使用ライブラリ更新
 * @version 1.1.1  2023/04/01 使用ライブラリ更新
 * @version 1.1.0  2022/09/23 ウィンドウ位置保存，ライブラリ更新
 * @version 1.0    2020/10/17 初版
 */
