/** cpuid.cpp
 * CPUの情報を表示する
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @動作環境	MinGW C++ + Boost C++ Libraries
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-06-01.shtm
*/

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

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

#define MAKER		"pahoo.org"			// 作成者
#define APPNAME		"cpuid"				// アプリケーション名
#define APPNAMEJP	"CPU情報"			// アプリケーション名（日本語）
#define APPVERSION	"1.3.12"			// バージョン
#define APPYEAR		"2020-2026"			// 作成年
#define REFERENCE	"https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-06-01.shtm"	// 参考サイト

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

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

// 親ウィンドウ
static HWND hParent;

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

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

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

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

// ProcessorListファイル名
#define FNAME	"processorList.xml"

#define SIZE_PROCESSORINFO	500		// 格納上限

// ProcessorInfoクラス
class ProcessorInfo {
public:
	unsigned int level = 0;
	unsigned int type = 0;
	unsigned int number = 0;
	unsigned int model = 0;
	unsigned int family = 0;
	unsigned int stepping = 0;
	string vendor = "";
	string architecture = "";
};

// 検索結果格納用
unique_ptr<ProcessorInfo> ProcessorList[SIZE_PROCESSORINFO] = {};

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

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

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

なお，本アプリケーションの利用または改造することによって生じた得失ついては一切関知いたしません．また，二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません．

本アプリケーションは，下記のライブラリを利用しています．
　MinGW（g++）／Boost C++ライブラリ
)")
% APPNAMEJP
% APPVERSION
% APPYEAR
).str();

// クリップボード関連 ======================================================
/**
 * クリップボードに文字列をセット
 * @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  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);
}

// オプション読込／保存 ====================================================
/**
 * 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 loadAppPosition(void) {
	hParent_X = 0;
	hParent_Y = 0;
	ptree pt;

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

		// XML解釈
		try {
			// 形式チェック
			if (boost::optional<std::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;
	}

	return;
}

/**
 * アプリケーション・ウィンドウ位置を保存する．
 * @param	なし
 * @return	なし
 */
void saveAppPosition(void) {
	// アプリケーション・ウィンドウの位置取得
	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;
	}

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

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

// GUI処理 =================================================================
/**
 * ダイアログを親ウィンドウの中央に配置
 * @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);
}

// バージョン表示ダイアログ =================================================
/**
 * イベントハンドラ：バージョン表示ダイアログ
 * @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) {
	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);
}

// テキスト・ファイルへ出力 =================================================
/**
 * CPU情報をTXTファイルに保存する
 * @param	const char* fname	保存ファイル名
 * @param	char* cpu			CPU情報
 * @return	なし
*/
void __saveTXT(const char* fname, char* cpu) {
	ofstream outputfile(fname);

	outputfile << cpu << endl;

	if (outputfile.bad()) {
		ErrorMessage = "テキストファイル書き込みエラー";
	}
	outputfile.close();
}

/**
 * CPU情報をTXTファイルに保存する
 * @param	char* cpu  CPU情報
 * @return	なし
*/
void saveTXT(char* cpu) {
	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("テキストファイル(*.txt)\0*.txt\0すべてのファイル(*.*)\0*.*\0\0");
	// ファイル名を格納したバッファのアドレス
	of.lpstrFile = (LPTSTR)fname; 
	// lpstrFileメンバで指定されるバッファのサイズ
	of.nMaxFile = MAX_PATH;
	of.Flags = OFN_OVERWRITEPROMPT;
	// デフォルトの拡張子を格納したバッファのアドレス
	of.lpstrDefExt = TEXT("txt");
	// コモンダイアログの表示
	GetSaveFileName(&of);
	// ファイル保存
	__saveTXT(fname, cpu);
}

// CPU情報リスト取得・マッチング ===========================================
/**
 * CPU情報リストを取得する
 * @param  char *fname CPU情報リストファイル名
 * @return int 取得した情報数／(-1)：取得失敗
*/
int getProcessorList(const char *fname, char *date) {
	int cnt = -1;

	try {
		// XMLファイル読み込み
		boost::property_tree::ptree pt;
		boost::property_tree::xml_parser::read_xml(fname, pt);

		// XML解釈
		boost::optional<std::string> str = pt.get_optional<std::string>("processorList.date");
		strncpy(date, str->c_str(), SIZE_BUFF);

		for (auto it : pt.get_child("processorList")) {
			cnt++;
			ProcessorList[cnt] = make_unique<ProcessorInfo>();
			// ベンダ
			if (optional<string> vendor = it.second.get_optional<string>("vendor")) {
				ProcessorList[cnt]->vendor = vendor.value();
			}
			// ファミリー
			if (optional<string> family = it.second.get_optional<string>("family")) {
				ProcessorList[cnt]->family = strtol(family->c_str(), NULL, 16);
			}
			// モデル
			if (optional<string> model = it.second.get_optional<string>("model")) {
				ProcessorList[cnt]->model = strtol(model->c_str(), NULL, 16);
			}
			// アーキテクチャ
			if (optional<string> architecture = it.second.get_optional<string>("architecture")) {
				ProcessorList[cnt]->architecture = architecture.value();
			}
		}
	// エラー処理
	} catch (boost::property_tree::xml_parser_error& e) {
		ErrorMessage = "エラー：" + (string)e.what();
	}

	return cnt;
}

/**
 * SystemInfoの結果とCPU情報リストをマッチングしCPU情報を取得
 * @param   ProcessorInfo cpuinfo 結果を格納
 * @return  bool TRUE：取得成功／FALSE：取得失敗
*/
bool getProcessorInfo(ProcessorInfo &cpuinfo) {
	// CPU情報取得
	SYSTEM_INFO systemInfo;
	GetSystemInfo(&systemInfo);
	cpuinfo.level  = (unsigned int)systemInfo.wProcessorLevel;
	cpuinfo.type   = (unsigned int)systemInfo.dwProcessorType;
	cpuinfo.number = (unsigned int)systemInfo.dwNumberOfProcessors;
	cpuinfo.model  = (unsigned int)(systemInfo.wProcessorRevision >> 8);
	cpuinfo.stepping = (unsigned int)(systemInfo.wProcessorRevision & 0x00FF);
	cpuinfo.vendor = "unknown";
	cpuinfo.architecture = "unknown";

	// CPU情報リストとマッチング
	bool res = false;
	for (int i = 0; i < SIZE_PROCESSORINFO; i++) {
		if (ProcessorList[i] == nullptr)	break;
		if ((ProcessorList[i]->family == cpuinfo.level) && (ProcessorList[i]->model == cpuinfo.model)) {
			cpuinfo.vendor = ProcessorList[i]->vendor;
			cpuinfo.architecture = ProcessorList[i]->architecture;
			res = true;
			break;
		}
	}
	return res;
}

// メインウィンドウ ==========================================================
/**
 * イベントハンドラ：メインウィンドウ
 * @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;
	ProcessorInfo cpuinfo;
	static char cpu[SIZE_BUFF + 1], date[SIZE_BUFF + 1];

	switch(uMsg){
	// ダイアログ初期化
	case WM_INITDIALOG:
		hParent = hDlg;
		hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
		SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
		// アプリケーション・ウィンドウの位置を読み込む
		loadAppPosition();
		// アプリケーション・ウィンドウ移動
		SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
		// CPU情報取得
		getProcessorList(FNAME, date);
		if (ErrorMessage != "") {
			setStrEditBox(hDlg, IDC_TEXT_CPUID, ErrorMessage);
		} else {
			getProcessorInfo(cpuinfo);
			// CPU情報（テキスト）生成
			snprintf(cpu, SIZE_BUFF, "%s %s, %d processor(s)\nlevel %d  model %d  stepping %d", cpuinfo.vendor.c_str(), cpuinfo.architecture.c_str(), cpuinfo.number, cpuinfo.level, cpuinfo.model, cpuinfo.stepping);
			setStrEditBox(hDlg, IDC_TEXT_CPUID, cpu);
			setStrEditBox(hDlg, IDC_TEXT_ONLINE, date);
		}
		break;

	// ボタン押下
	case WM_COMMAND:
		 switch (LOWORD(wParam)) {
		// 保存
		case IDC_BUTTON_SAVE:
		case IDM_SAVE:
			saveTXT(cpu);
			if (ErrorMessage != "") {
				setStrEditBox(hDlg, IDC_TEXT_CPUID, ErrorMessage);
			}
			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 IDM_COPY:
		case IDC_BUTTON_COPY:
			setClipboardData(cpu);
			break;
		// プログラム終了
		case IDM_QUIT:
			// アプリケーション・ウィンドウの位置を保存する
			saveAppPosition();
			EndDialog(hParent, 0);
			return 0;
		default:
			return 1;
		}
		break;
	// プログラム終了
	case WM_CLOSE:
		// アプリケーション・ウィンドウの位置を保存する
		saveAppPosition();
		EndDialog(hDlg, 0);
		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) {
	hInst = hInstance;
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
	return 0;
}

/*
** バージョンアップ履歴 =====================================================
 *
 * @version 1.3.12 2026/05/16 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.11 2026/02/07 CPUリスト更新，使用ライブラリ更新, optional名前衝突回避
 * @version 1.3.10 2025/10/11 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.9  2025/06/21 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.8  2025/03/01 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.7  2024/07/14 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.6  2024/07/14 CPUリスト更新，使用ライブラリ更新
 * @version 1.3.5  2024/02/17 CPUリストを更新，使用ライブラリ等を更新
 * @version 1.3.4  2023/10/08 使用ライブラリをバージョンアップ，アプリケーション・ウィンドウの位置を保存
 * @version 1.3.3  2023/05/28 使用ライブラリをバージョンアップ
 * @version 1.3.2  2023/02/26 使用ライブラリをバージョンアップ
 * @version 1.3.1  2022/10/31 使用ライブラリをバージョンアップ，CPUリストを更新
 * @version 1.3    2022/07/14 使用ライブラリをバージョンアップ，CPUリストを更新
 * @version 1.2    2020/09/05 インストーラー，ヘルプファイル，メニュー追加
 * @version 1.1    2020/08/24 XML処理を Boost C++ Librariesに変更
 * @version 1.0    2020/08/01 初版
*/
