/** pahooCache.cpp
 * キャッシュ処理に関わるクラス：C++ソース
 *
 * @copyright	(c)studio pahoo
 * @author		パパぱふぅ
 * @動作環境	MinGW C++ + cURL + OpenSSL + Boost C++ Libraries
 * @参考URL		https://www.pahoo.org/e-soul/webtech/cpp01-16-01.shtm
 */
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <time.h>
#include <direct.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sstream>
#include <string>
#include <regex>
#include <locale>
#include <math.h>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/hex.hpp>
#include <boost/uuid/detail/md5.hpp>
#include "mystrings.h"
#include "pahooCache.hpp"

using namespace std;
using namespace boost;
using namespace boost::property_tree;
using namespace boost::uuids::detail;

/**
 * コンストラクタ
 * @param	unsigned int life キャッシュ保持時間（分）
 * @param	string dir        キャッシュ・ディレクトリ
 * @param   string ua         UserAgent
 * @return	なし
 */
pahooCache::pahooCache(unsigned int life, string dir, const string ua) {
	// dirの最後にディレクトリ区切り文字がなければ追加
	smatch mt;
	regex re("\\\\$");
	if (regex_search(dir, mt, re) == FALSE) {
		dir = dir + "\\";
	}
	this->errmsg = L"";
	this->lifeCache = life;
	this->dirCache  = dir;
	this->userAgent = ua;

	// キャッシュ・ディレクトリが無ければ作成
	struct stat statBuf;
	if (stat(this->dirCache.c_str(), &statBuf) != 0) {
		if (_mkdir(this->dirCache.c_str()) != 0) {
			errmsg = _SW("キャッシュ・ディレクトリ \"") + _SW(this->dirCache) + _SW("\" の作成に失敗しました");
		}
	}
}

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

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

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

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

/**
 * キャッシュ・ディレクトリ取得
 * @param	なし
 * @return	string キャッシュ・ディレクトリ
*/
string pahooCache::getDirCache(void) {
	return this->dirCache;
}

/**
 * キャッシュの削除
 * @param	string pat 削除ファイル名（正規表現指定可能）
 * @param	unsigned int life  キャッシュ保持時間（分）
 * @return	unsigned int 削除したファイル数
*/
unsigned int pahooCache::delCache(string pat, unsigned int life) {
	int cnt = 0;
	string fullname;
	struct stat st;
	smatch mt;
	regex re(pat);

	// 現在時刻(UTC)
	time_t t;
	time(&t);
	std::tm* localTime = std::localtime(&t); 
	// 現在時刻(ローカル時間)
	std::time_t localEpoch = std::mktime(localTime);

	// ディレクトリ検索
	DIR *dp;
	dp = opendir(this->dirCache.c_str());
	if (dp == NULL) {
		return 0;
	}
	dirent* entry = readdir(dp);
	while (entry != NULL){
		if (entry != NULL) {
			string fname = (string)entry->d_name;
			// patに一致するファイル
			if (regex_search(fname, mt, re)) {
				fullname = this->dirCache + fname;
				if (stat(fullname.c_str(), &st) == 0) {
					// 保持時間を過ぎたキャッシュ・ファイルは削除
					if ((long unsigned int)localEpoch - (long unsigned int)st.st_mtime > (life * 60)) {
						unlink(fullname.c_str());
						cnt++;
					}
				}
			}
		}
		entry = readdir(dp);
	}
	closedir(dp);

	return cnt;
}

/**
 * cURLによるコンテンツ取得
 * @param	string url URL
 * @param	string* contents コンテンツを格納
 * @return	bool TRUE：読み込み成功／FALSE：失敗
*/
bool pahooCache::cLoad(string url, string* contents) {
	int httpStatus = 0;
	bool res = readWebContents(url, (const string)this->userAgent, contents, &httpStatus);
	if (res == FALSE) {
		this->errmsg  = _SW("\"") + _SW(url) + _SW("\" が取得できません");
		return FALSE;
	} else if ((httpStatus < 200) || (httpStatus > 299)) {
		this->errmsg = _SW("国土地理院ジオコーディングAPIの接続エラーが発生しました（HTTPステータスコード" + to_string(httpStatus) + "）");
		return FALSE;
	}
	return TRUE;
}

/**
 * MD5ハッシュ値を返す
 * @param	string str 文字列
 * @return	string MD5ハッシュ値
*/
string pahooCache::md5(string str) {
	boost::uuids::detail::md5 hash;
	md5::digest_type digest;
	hash.process_bytes(str.data(), str.size());
	hash.get_digest(digest);

	const auto intDigest = reinterpret_cast<const int *>(&digest);
	std::string result;
	boost::algorithm::hex(intDigest, intDigest + (sizeof(md5::digest_type) / sizeof(int)), std::back_inserter(result));

	return result;
}

/**
 * ネットからコンテンツを読み込む
 * @param	string url   URL
 * @param	string* contents コンテンツを格納
 * @return	bool TRUE：読み込み成功／FALSE：失敗
*/
bool pahooCache::forceLoad(string url, string* contents) {
	// キャッシュするファイル名
	string fname = this->md5(url);
	string fullname = this->dirCache + fname;

	bool ret = this->cLoad(url, contents);
	ofstream fout;
	fout.open(fullname, ios::out);
	fout << *contents;

	if (ret == FALSE) {
		return FALSE;
	}
	this->debug += url + "\n";

	return TRUE;
}

/**
 * コンテンツを読み込む
 * @param	string url   URL
 * @param	string* contents コンテンツを格納
 * @return	bool TRUE：読み込み成功／FALSE：失敗
*/
bool pahooCache::load(string url, string* contents) {
	bool res = FALSE;
	string fname, fullname;
	string buff;
	ifstream fin;

	// キャッシュ有効
	if (this->lifeCache > 0) {
		this->delCache("[0-9a-f]+", this->lifeCache);	// 古いキャッシュを削除
		fname = this->md5(url);						// キャッシュするファイル名
		fullname = this->dirCache + fname;
		// キャッシュが存在する
		*contents = "";
		fin.open(fullname, ios::binary);
		if (fin) {
//			cout << "from file: " << fname << endl;
			getline(fin, *contents, '\0');
			if (contents->length() <= 0) {
				this->errmsg = _SW("キャッシュファイル \"") + _SW(fullname) + _SW("\" の読み込みに失敗しました");
//				cout << _WS(this->errmsg) << endl;
				return FALSE;
			}
			this->debug += fname + "\n";
			res = TRUE;
		// ネットから取得
		} else {
//			cout << "from URL: " << url << endl;
			res = this->forceLoad(url, contents);
		}

	// キャッシュ無効
	} else {
//		cout << "uncache from URL: " << url << endl;
		res = this->forceLoad(url, contents);
	}

	return res;
}

/*
 ** バージョンアップ履歴 =====================================================
 *
 * @version 1.1.0 2025/03/16 不具合修正
 * @version 1.0   2021/04/14 初版
 */
