#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <functional>
#include <regex>
#include <random>
#include <filesystem>
#include <cstdio>
#include <cassert>
#include <cmath>
#include <ctime>

#define NOMINMAX
#include "../../dxlib_vc/DxLib.h"

// Constants
static const int LIMITX = ::GetSystemMetrics(SM_CXSCREEN);
static const int LIMITY = ::GetSystemMetrics(SM_CYSCREEN);

// Structs
struct Piece{
	int pos, graph;
	int dstx, dsty; // 目標座標
	int x, y; // 現在座標
};

// Prototypes
int NaturalPow(size_t num, size_t n); // 自然数の自然数乗を計算する

// Grobal Variables
static int wndWidth = 800;
static int wndHeight = 600;

// Record名前空間
namespace Record
{
	size_t fastestTime = 999999;
	size_t leastMoveCount = 999999;
	size_t positions[16] = {}; // note: modify this when init game

	void Set(const int time, const int count)
	{
		fastestTime = time;
		leastMoveCount = count;
	}

	void Reset()
	{
		fastestTime = leastMoveCount = 999999;
	}

	void UpdateWindowText()
	{
		std::stringstream ss;
		ss << "fastest time: " << Record::fastestTime / 1000 << '.' << Record::fastestTime % 1000 << " best move count: " << Record::leastMoveCount;
		SetWindowText(std::string(ss.str()).c_str());
	}

	void InitWindowText()
	{
		SetWindowText("15Puzzle");
	}
}

// 15パズルクラス
class Puzzle15 final{
private:
	// Singletonなので実行時に直ちに初期化される
	Puzzle15()
	{
		// 目標座標とposをスワップするラムダを定義
		int& hole = mPiece[LAST_PIECE].pos;
		mPieceSwap[0] = [&]()->bool{ if (!IsLeftBound(hole)) { std::swap(mPiece[LAST_PIECE].dstx, mPiece[SearchPiece(hole - 1)].dstx); std::swap(hole, mPiece[SearchPiece(hole - 1)].pos); return true; } else return false; };
		mPieceSwap[1] = [&]()->bool{ if (!IsRightBound(hole)) { std::swap(mPiece[LAST_PIECE].dstx, mPiece[SearchPiece(hole + 1)].dstx); std::swap(hole, mPiece[SearchPiece(hole + 1)].pos); return true; } else return false; };
		mPieceSwap[2] = [&]()->bool{ if (!IsTopBound(hole)) { std::swap(mPiece[LAST_PIECE].dsty, mPiece[SearchPiece(hole - XDIV)].dsty); std::swap(hole, mPiece[SearchPiece(hole - XDIV)].pos); return true; } else return false; };
		mPieceSwap[3] = [&]()->bool{ if (!IsBottonBound(hole)) { std::swap(mPiece[LAST_PIECE].dsty, mPiece[SearchPiece(hole + XDIV)].dsty); std::swap(hole, mPiece[SearchPiece(hole + XDIV)].pos); return true; } else return false; };

		// リソース取得
		if ((mSoundClear = LoadSoundMem("sound/clear.wav")) == -1){
			::MessageBox(nullptr, "clear soundが見つかりませんでした", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		if ((mSoundShuffle = LoadSoundMem("sound/shuffle.wav")) == -1){
			::MessageBox(nullptr, "shuffle soundが見つかりませんでした", "Error", MB_OK | MB_ICONEXCLAMATION);
		}
		// 画像・音声情報を読み込み
		this->LoadSystemData();
		this->GameInit(true, true);
	}

public:
	~Puzzle15() = default;
	Puzzle15(const Puzzle15&) = delete;
	Puzzle15(const Puzzle15&&) = delete;
	Puzzle15& operator=(const Puzzle15&) = delete;
	Puzzle15& operator=(const Puzzle15&&) = delete;

	void Run();
	static Puzzle15* get()
	{
		static Puzzle15 inst;
		return &inst;
	}

private:
	// ゲームに必要な初期化
	void GameInit(const bool loadNewImg, const bool startWithNewPos)
	{
		if (loadNewImg){
			this->LoadImages();
		}

		if (startWithNewPos){
			for (int i = 0; i < PIECE_MAX; ++i){
				mPiece[i].graph = i;
				mPiece[i].pos = i;
				mPiece[i].dstx = mPieceX * (mPiece[i].pos % XDIV);
				mPiece[i].dsty = mPieceY * (mPiece[i].pos / YDIV);
			}
			// 一致率が1/4以下になるまでかき混ぜる
			do{
				for (int i = 0; i < 100; ++i){
					mPieceSwap[GetRand(3)]();
				}
			} while (this->AccordanceCount() > PIECE_MAX / 4);

			// データ保存用にposを記録しておく
			for (int i = 0; i < PIECE_MAX; ++i){
				Record::positions[i] = mPiece[i].pos;
			}
			// 最高記録はないので元のテキストに戻しておく
			Record::InitWindowText();
		}
		else{
			// posデータから読み込む
			for (int i = 0; i < PIECE_MAX; ++i){
				mPiece[i].pos = Record::positions[i];
				mPiece[i].dstx = mPieceX * (mPiece[i].pos % XDIV);
				mPiece[i].dsty = mPieceY * (mPiece[i].pos / YDIV);
			}
		}

		this->InitParams();
		// 画像の情報を保存する
		this->SaveSystemData();
		PlaySoundMem(mSoundShuffle, DX_PLAYTYPE_BACK);
	}

	// パラメータ初期化
	void InitParams()
	{
		mMoveCount = 0;
		mIsCleared = false;
		mClearElapsed = 0;
		mExtRate = 1.0;
		mDefaultExt = std::min(wndWidth, wndHeight) / 2.0 / LIMITX;
		mStartTIme = GetNowCount();
	}

	// 画像読み込み(SetGraphModeで破棄されるため全体のセッティングを含める)
	// 使用個所：最初、リストア（画像・ディレクトリ）
	void LoadImages()
	{
		// 画像読み込み（SetGraphMode()で画像がロストするのですべて読み込む）
		InitGraph();
		this->ChangeWindowSize(mImgPaths[mImgNum]);
		if (LoadDivGraph(mImgPaths[mImgNum++ % mImgPaths.size()].c_str(), PIECE_MAX, XDIV, YDIV, mPicWidth / XDIV, mPicHeight / YDIV, mImgPieces) == -1){
			std::stringstream ss;
			ss << "画像の読み込みに失敗しました\n\"" << mImgPaths[mImgNum % mImgPaths.size()] << '\"';
			::MessageBox(nullptr, std::string(ss.str()).c_str(), "Error", MB_OK | MB_ICONEXCLAMATION);
			return;
		}
		// 1巡したらシャッフルする
		if (mImgNum % mImgPaths.size() == 0){
			this->PathShuffle();
		}

		// 環境設定
		SetTransColor(255, 255, 255);
		SetDrawScreen(DX_SCREEN_BACK);

		// 再読み込み
		mImgClear = LoadGraph("img/gameclearb.png");
		mImgDot = LoadGraph("img/dot.png");
		mImgGlaw = LoadGraph("img/glaw.png");
		LoadDivGraph("img/countb.png", 10, 10, 1, 160, 200, mImgNums);
		LoadDivGraph("img/timeb.png", 10, 10, 1, 96, 144, mImgTimes);
	}

	// ドラッグアンドドロップされたかチェック
	void CheckDD()
	{
		static const std::regex regSoundFile(R"(.+\.(wav|ogg|mp3))", std::regex::icase | std::regex::ECMAScript);
		static const std::regex regImgFile(R"(.+\.(bmp|jpeg|jpg|png|dds|argb|tga))", std::regex::icase | std::regex::ECMAScript);
		static const std::regex reg15pzlFile(R"(.+\.(15pzl|txt))", std::regex::icase | std::regex::ECMAScript);

		static const std::vector<std::string> soundExt = {".wav", ".mp3", ".ogg"};
		static const std::vector<std::string> imgExt = {".bmp", ".jpeg", ".jpg", ".png", ".dds", ".argb", ".tga"};
		static const std::vector<std::string> pzl15Ext = {".15pzl", ".txt"};

		// D&Dされればその処理
		if (GetDragFileNum()){
			char buf[MAX_PATH] = {};
			GetDragFilePath(buf);
			DragFileInfoClear();
			
			// ディレクトリなら専用処理
			if (std::tr2::sys::is_directory(std::tr2::sys::path(buf))){
				// そのディレクトリ内のファイルを再帰的に列挙
				std::vector<std::string> fileList;
				for (std::tr2::sys::recursive_directory_iterator it(buf), end; it != end; ++it){
					if (!std::tr2::sys::is_directory(it->path())){
						fileList.push_back(it->path());
					}
				}

				// 全て音声だったら
				if (std::all_of(fileList.begin(), fileList.end(), [&](const std::string& elem){
					auto ext = std::tr2::sys::extension(std::tr2::sys::path(elem));
					return std::find(soundExt.begin(), soundExt.end(), ext) != soundExt.end();
				})){
					mSoundPaths = std::move(fileList);
					// 初期ディレクトリ更新
					mSoundDirectory = buf;
					// gameinit を通らないためここで更新
					this->SaveSystemData();
				}
				// 全て画像だったら
				else if (std::all_of(fileList.begin(), fileList.end(), [&](const std::string& elem){
					auto ext = std::tr2::sys::extension(std::tr2::sys::path(elem));
					return std::find(imgExt.begin(), imgExt.end(), ext) != imgExt.end();
				})){
					mImgPaths = std::move(fileList);
					// 初期ディレクトリ更新
					mImgDirectory = buf;
					this->PathShuffle();
					this->GameInit(true, true);
				}
				else{
					::MessageBox(nullptr, "ディレクトリ内のファイルは\n画像(bmp|jpeg|jpg|png|dds|argb|tga)か\n音声(wav|ogg|mp3)\nに統一されている必要があります", "Error", MB_OK | MB_ICONEXCLAMATION);
				}
			}
			// 音声ファイルなら音声切り替え処理
			else if (std::regex_match(buf, regSoundFile)){
				mSoundPaths.clear();
				mSoundPaths.push_back(buf);
				// gameinit を通らないためここで更新
				this->SaveSystemData();
				::MessageBox(nullptr, "効果音を切り替えました", "Success", MB_OK);
			}
			// 画像なら画像を切り替えてnewGame
			else if (std::regex_match(buf, regImgFile)){
				// パス情報再構築
				mImgPaths.clear();
				mImgPaths.push_back(buf);
				mImgNum = 0;
				mImgDirectory = buf;

				this->GameInit(true, true);
			}
			// .15pzlファイルなら読み込む
			else if (std::regex_match(buf, reg15pzlFile)){
				this->LoadPuzzleData(buf);
				this->GameInit(false, false);
			}
			else{
				::MessageBox(nullptr, "対応していないファイル形式です", "Error", MB_OK | MB_ICONASTERISK);
			}
		}
	}

	// パスリストをシャッフル
	void PathShuffle()
	{
		static std::random_device seed_gen;
		static std::mt19937 mt(seed_gen());

		std::shuffle(mImgPaths.begin(), mImgPaths.end(), mt);
		mImgNum = 0;
	}

	// ウインドウサイズ変更
	void ChangeWindowSize(const std::string& path)
	{
		// 画像サイズ取得
		const int imgtmp = LoadSoftImage(path.c_str());
		GetSoftImageSize(imgtmp, &mPicWidth, &mPicHeight);
		DeleteSoftImage(imgtmp);
		if (mPicWidth < 128 || mPicHeight < 128){
			::MessageBox(nullptr, "画像のサイズが小さすぎます", "Error", MB_OK | MB_ICONEXCLAMATION);
			return;
		}
		// 画像がディスプレイより大きければ縮小
		double reductionRateX = 1;
		double reductionRateY = 1;
		if (mPicHeight > LIMITY - 80){
			// 多少余裕を持たせる
			reductionRateY = std::min((LIMITY - 80) / double(mPicHeight), 1.0);
		}
		if (mPicWidth > LIMITX - 40){
			// 多少余裕を持たせる
			reductionRateX = std::min((LIMITX - 40) / double(mPicWidth), 1.0);
		}
		const double requiredRate = std::min(reductionRateX, reductionRateY);

		// ウインドウサイズ変更
		wndWidth = mPicWidth * requiredRate;
		wndHeight = mPicHeight * requiredRate;
		// 1ピースのサイズを決定
		mPieceX = wndWidth / XDIV;
		mPieceY = wndHeight / YDIV;
		// 発光画像の倍率も決定
		mGlawExtRate = std::max(wndHeight / 1500.0, wndWidth / 2000.0) * 1.5;

		if (SetGraphMode(wndWidth, wndHeight, 32) != DX_CHANGESCREEN_OK){
			::MessageBox(nullptr, "画面サイズの変更に失敗しました", "Error", MB_OK | MB_ICONEXCLAMATION);
			return;
		}
	}

	// マウス関係の処理
	void MouseEvent()
	{
		static int mouseX, mouseY;

		// クリアしてれば処理しない
		if (mIsCleared)
			return;

		mMouseState = GetMouseInput() ? ++mMouseState : 0;
		// マウスを押した瞬間の処理
		if (mMouseState == 1){
			// 動かせるか調査
			GetMousePoint(&mouseX, &mouseY);
			const int hole = mPiece[LAST_PIECE].pos;
			const int mousePos = this->GetPos(mouseX, mouseY);

			// 空白部分と隣接していれば動かす
			if (mousePos == hole - 1){
				this->PieceSwap(DIR_LEFT);
			}
			else if (mousePos == hole + 1){
				this->PieceSwap(DIR_RIGHT);
			}
			else if (mousePos == hole - XDIV){
				this->PieceSwap(DIR_UP);
			}
			else if (mousePos == hole + XDIV){
				this->PieceSwap(DIR_DOWN);
			}
		}
	}

	// ピースの情報を変更
	void PieceSwap(const int direction)
	{
		if (mPieceSwap[direction]()){
			++mMoveCount;
			// サウンドの中からランダムに
			const int index = GetRand(mSoundPaths.size() - 1);
			PlaySoundFile(mSoundPaths[index].c_str(), DX_PLAYTYPE_BACK); // 再生できなくても気にせず続行する
		}
	}

	// キーボード入力処理
	void KeyEvent()
	{
		static char keystateBuf[KEYMAX] = {};
		// keystate準備
		GetHitKeyStateAll(keystateBuf);
		for (int i = 0; i < KEYMAX; ++i){
			mKeyState[i] = keystateBuf[i] ? ++mKeyState[i] : 0;
		}

		// Rが押されたら同じ配置でもう一度
		if (mKeyState[KEY_INPUT_R] == 1){
			this->GameInit(false, false);
		}
		// Nが押されたら、別の配置でもう一度
		else if (mKeyState[KEY_INPUT_N] == 1){
			this->GameInit(true, true);
			Record::Reset();
		}
		// ゲームクリア後にSキーが押されたらセーブする
		else if (mIsCleared && mKeyState[KEY_INPUT_S] == 1){
			this->SavePuzzleData();
		}
		// 0が押されたら画像・音声情報を初期化
		else if (mKeyState[KEY_INPUT_0] == 1){
			// 現在のリストを初期化
			mImgPaths.clear();
			mImgPaths.push_back(mDefaultImgPath);
			mImgNum = 0;
			mSoundPaths.clear();
			mSoundPaths.push_back(mDefaultSoundPath);
			// ディレクトリ情報も初期化
			mImgDirectory = mDefaultImgPath;
			mSoundDirectory = mDefaultSoundPath;
			::MessageBox(nullptr, "音声・画像情報が初期化されました。\n新しいゲームを開始すると初期画像でプレーできます。", "Succees", MB_OK | MB_ICONINFORMATION);
		}
		// Iが押されたらposインフォを表示
		else if (mKeyState[KEY_INPUT_I] == 1){
			mDispPosInfo = ++mDispPosInfo % 3;
		}
		// 矢印キーでも操作
		else if (!mIsCleared){
			if (mKeyState[KEY_INPUT_LEFT] == 1){
				this->PieceSwap(DIR_LEFT);
			}
			else if (mKeyState[KEY_INPUT_RIGHT] == 1){
				this->PieceSwap(DIR_RIGHT);
			}
			else if (mKeyState[KEY_INPUT_UP] == 1){
				this->PieceSwap(DIR_UP);
			}
			else if (mKeyState[KEY_INPUT_DOWN] == 1){
				this->PieceSwap(DIR_DOWN);
			}
		}
	}

	// ピースを滑らかに動かす
	void PieceMove()
	{
		// 滑らかに移動
		for (auto&& elem : mPiece){
			if (elem.x != elem.dstx){
				elem.x += (elem.dstx - elem.x) / 3;
				if (std::abs(elem.dstx - elem.x) < 10){
					elem.x = elem.dstx;
				}
			}
			if (elem.y != elem.dsty){
				elem.y += (elem.dsty - elem.y) / 3;
				if (std::abs(elem.dsty - elem.y) < 10){
					elem.y = elem.dsty;
				}
			}
		}
	}

	// 描画処理
	void Draw() const
	{
		const double netExt = mExtRate * mDefaultExt; // トータルでの拡縮率

		// ピース（クリアしたら最後のピースも描画）
		for (int i = 0; i < (mIsCleared ? PIECE_MAX : LAST_PIECE); ++i){
			DrawExtendGraph(mPiece[i].x, mPiece[i].y, mPiece[i].x + mPieceX, mPiece[i].y + mPieceY, mImgPieces[mPiece[i].graph], false);
		}

		// mElapsedMs
		for (int i = 0; i < this->Digits(mElapsedMs); ++i){
			// 4桁目以降は秒なのでドットが入る位置を開ける
			const int fix = i < 3 ? 0 : 60;
			DrawRotaGraph(wndWidth * 0.95 - (i * 96 + fix) * netExt, 96 * netExt, netExt, 0, mImgTimes[mElapsedMs / NaturalPow(10, i) % 10], true);
		}
		// dot
		DrawRotaGraph(wndWidth * 0.95 - (2 * 118 + 32) * netExt, 160 * netExt, netExt, 0, mImgDot, true);
		// mMoveCount
		for (int i = 0; i < this->Digits(mMoveCount); ++i){
			DrawRotaGraph(wndWidth * 0.9 - i * 138 * netExt, 300 * netExt, netExt, 0, mImgNums[mMoveCount / NaturalPow(10, i) % 10], true);
		}

		// pos番号情報表示
		if (mDispPosInfo){
			for (int i = 0; i < LAST_PIECE; ++i){
				DrawFormatString(mPiece[i].x + mPieceX - 20, mPiece[i].y + mPieceY - 20, mDispPosInfo == 1 ? GetColor(0, 0, 0) : GetColor(255, 255, 255), "%d", mPiece[i].graph + 1);
			}
		}
	}

	// クリア時の描画処理
	void ClearDraw()
	{
		// クリア時の処理
		if (this->CheckClear()){
			mClearElapsed += 3;
			const double extRate = std::min(wndHeight, wndWidth) / 2000.0;
			// GAME CLEAR画像
			if (mClearElapsed < ZOOMIN_COUNT){
				SetDrawBlendMode(DX_BLENDMODE_ALPHA, mClearElapsed);
				DrawRotaGraph(wndWidth / 2, wndHeight / 2, extRate + ((ZOOMIN_COUNT - mClearElapsed) / (double)ZOOMIN_COUNT * 3), 0, mImgClear, true);
			}
			else{
				static int alpha = 0;
				// 発光画像
				static const int peak = 35;
				if (mClearElapsed < ZOOMIN_COUNT + peak){
					alpha += 18;
				}
				else if (ZOOMIN_COUNT + peak < mClearElapsed){
					alpha = std::max(0, --alpha);
				}
				SetDrawBlendMode(DX_BLENDMODE_PMA_ADD_X4, alpha);
				DrawRotaGraph(wndWidth / 2, wndHeight / 2, mGlawExtRate, 0, mImgGlaw, true); // 発光画像
				ResetBlendMode();
				DrawRotaGraph(wndWidth / 2, wndHeight / 2, extRate, 0, mImgClear, true); // クリア画像
			}
			// ズームRatioを増やす
			if (ZOOMIN_COUNT + 500 < mClearElapsed && mClearElapsed < ZOOMIN_COUNT + 700){
				mExtRate += 1.0 / 50;
			}
			ResetBlendMode();
		}
	}

	// クリアしたかどうかチェック
	bool CheckClear()
	{
		if (mIsCleared){
			return true;
		}
		else if (this->AccordanceCount() == PIECE_MAX){
			// クリアした瞬間にのみする処理
			mIsCleared = true;
			mDispPosInfo = 0;
			PlaySoundMem(mSoundClear, DX_PLAYTYPE_BACK);

			// 記録更新していればRecordを更新(移動回数が基準)
			if (mMoveCount < Record::leastMoveCount){
				Record::Set(mElapsedMs, mMoveCount);
				Record::UpdateWindowText();
			}
			return true;
		}
		return false;
	}

	// パネルの中で一致している枚数を返す
	int AccordanceCount() const
	{
		return std::count_if(mPiece, mPiece + PIECE_MAX, [](const Piece& elem){return elem.graph == elem.pos; });
	}

	// posにあるパネルに対応するオブジェクトを検索する
	int SearchPiece(const int pos) const
	{
		return std::find_if(mPiece, mPiece + PIECE_MAX, [&pos](const Piece& elem){return elem.pos == pos; }) - mPiece;
	}

	// セーブ
	void SavePuzzleData()
	{
		// savedata/ディレクトリが存在しなければ作成する
		static std::tr2::sys::path savedataPath = std::tr2::sys::current_path<std::tr2::sys::path>() / std::tr2::sys::path("savedata");
		if (!std::tr2::sys::exists(savedataPath)){
			std::tr2::sys::create_directory(std::tr2::sys::path(savedataPath));
		}
		
		// 現在時間を取得してそれをファイル名にする
		std::time_t now = std::time(nullptr);
		std::tm date;
		localtime_s(&date, &now);
		std::stringstream ss;
		ss << std::setfill('0');
		ss << "savedata/" << date.tm_year + 1900 << std::setw(2) << date.tm_mon + 1 << std::setw(2) << date.tm_mday << '_' << 
			std::setw(2) << date.tm_hour << std::setw(2) << date.tm_min << std::setw(2) << date.tm_sec << ".txt";

		// txtファイルとしてセーブ
		std::ofstream fileout(std::string(ss.str()).c_str());
		
		fileout << Record::fastestTime << '\n' << Record::leastMoveCount << '\n' << PIECE_MAX << '\n';
		for (int i = 0; i < PIECE_MAX; ++i){
			fileout << Record::positions[i] << '\n';
		}
		std::stringstream ss2;
		ss2 << "データのセーブに成功しました\n" << '\"' << ss.str() << '\"';
		::MessageBox(nullptr, std::string(ss2.str()).c_str(), "Success", MB_OK | MB_ICONINFORMATION);
	}

	// ロード
	void LoadPuzzleData(const char* const path)
	{
		std::ifstream filein(path);
		if (!filein){
			std::stringstream ss;
			ss << "データのロードに失敗しました\n\"" << path << '\"';
			::MessageBox(nullptr, std::string(ss.str()).c_str(), "Error", MB_OK | MB_ICONEXCLAMATION);
			return;
		}
		std::string tmp;

		// データ読み込み
		std::getline(filein, tmp);
		Record::fastestTime = std::stoi(tmp);
		std::getline(filein, tmp);
		Record::leastMoveCount = std::stoi(tmp);
		std::getline(filein, tmp);
		int pieceMax = std::stoi(tmp);
		for (int i = 0; i < pieceMax; ++i){
			std::getline(filein, tmp);
			Record::positions[i] = std::stoi(tmp);
		}

		Record::UpdateWindowText();
	}

	// リソースパス情報保存
	void SaveSystemData()
	{
		std::ofstream fileout("gamesystem.dat", std::ios::binary | std::ios::out);
		fileout << mImgDirectory << '\0' << mSoundDirectory << '\0';
	}

	// リソースパス情報取得
	void LoadSystemData()
	{
		std::ifstream filein("gamesystem.dat");
		if (!filein){
			::MessageBox(nullptr, "gamesystemファイルを開くことができませんでした。\n初期設定を適用します", "Error", MB_OK | MB_ICONINFORMATION);
			mImgPaths.push_back(mDefaultImgPath);
			mSoundPaths.push_back(mDefaultSoundPath);
			return;
		}

		// 取得したディレクトリからリストを構築する
		char buf[MAX_PATH] = {};
		filein.getline(buf, sizeof(buf), '\0');
		mImgDirectory = *buf ? buf : mDefaultImgPath;
		if (std::tr2::sys::is_directory(std::tr2::sys::path(mImgDirectory))){
			for (std::tr2::sys::recursive_directory_iterator it(buf), end; it != end; ++it){
				if (!std::tr2::sys::is_directory(it->path())){
					mImgPaths.push_back(it->path());
				}
			}
		}
		else{
			mImgPaths.push_back(mImgDirectory);
		}
		this->PathShuffle();

		filein.getline(buf, sizeof(buf), '\0');
		mSoundDirectory = *buf ? buf : mDefaultSoundPath;
		if (std::tr2::sys::is_directory(std::tr2::sys::path(mSoundDirectory))){
			for (std::tr2::sys::recursive_directory_iterator it(buf), end; it != end; ++it){
				if (!std::tr2::sys::is_directory(it->path())){
					mSoundPaths.push_back(it->path());
				}
			}
		}
		else{
			mSoundPaths.push_back(mSoundDirectory);
		}
	}

	void ResetBlendMode() const { SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0); }
	int Digits(const int num) const { return std::printf("%d", num); } // 桁数を返す
	int GetPos(const int x, const int y) const { return y / mPieceY * XDIV + x / mPieceX; } // X,Y座標から対応する位置番号を返す

	bool IsRightBound(const int pos) const { return pos % XDIV == XDIV - 1; }
	bool IsLeftBound(const int pos) const { return pos % XDIV == 0; }
	bool IsTopBound(const int pos) const { return pos / XDIV == 0; }
	bool IsBottonBound(const int pos) const { return pos / XDIV == YDIV - 1; }

private:
	// Constants
	enum{
		XDIV = 4,
		YDIV = 4,
		LAST_PIECE = XDIV * YDIV - 1,
		PIECE_MAX = XDIV * YDIV,

		ZOOMIN_COUNT = 200,

		KEYMAX = 256,
	};

	enum{
		DIR_LEFT,
		DIR_RIGHT,
		DIR_UP,
		DIR_DOWN,
	};

	// Local Variables
	Piece mPiece[PIECE_MAX];
	std::function<bool()> mPieceSwap[4];

	int mPieceX = wndWidth / XDIV;
	int mPieceY = wndHeight / YDIV;

	int mPicWidth = 800;
	int mPicHeight = 600;
	int mClearElapsed = 0;
	size_t mMoveCount = 0;
	size_t mElapsedMs = 0;
	int mMouseState = 0;
	char mKeyState[KEYMAX];
	int mStartTIme = 0;
	double mExtRate = 1.0;
	double mGlawExtRate = std::max(wndHeight / 1500.0, wndWidth / 2000.0) * 1.5;
	double mDefaultExt = std::min(wndWidth, wndHeight) / 2.0 / LIMITX;
	bool mIsCleared = false;
	int mDispPosInfo = 0; // pos番号を表示するかどうかのフラグ

	using ImageHandle = int;
	using SoundHandle = int;
	// images
	std::string mImgDirectory;
	std::vector<std::string> mImgPaths;
	std::string mDefaultImgPath = std::string("img/a.png");
	size_t mImgNum = 0;
	ImageHandle mImgPieces[PIECE_MAX];
	ImageHandle mImgNums[10];
	ImageHandle mImgTimes[10];
	ImageHandle mImgClear;
	ImageHandle mImgDot;
	ImageHandle mImgGlaw;

	// Sounds
	std::string mSoundDirectory;
	std::vector<std::string> mSoundPaths;
	std::string mDefaultSoundPath = std::string("sound/move.wav");
	SoundHandle mSoundMove;
	SoundHandle mSoundShuffle;
	SoundHandle mSoundClear;
};

void Puzzle15::Run()
{
	// クリアしてなければタイムを進める
	if (!mIsCleared)
		mElapsedMs = GetNowCount() - mStartTIme;

	this->CheckDD();
	this->MouseEvent();
	this->KeyEvent();
	this->PieceMove();
	this->Draw();
	this->ClearDraw();
}

// 自然数の自然数乗を計算する
int NaturalPow(size_t num, size_t n)
{
	switch (n){
	case 0: return 1;
	case 1: return num;
	}

	const int base = num;
	while (--n){
		num *= base;
	}
	return num;
}

// 毎フレームの処理
bool LoopProc()
{
	return !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !CheckHitKey(KEY_INPUT_ESCAPE);
}

// エントリポイント
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	ChangeWindowMode(true);
	SetGraphMode(wndWidth, wndHeight, 32);
	SetWindowText("15Puzzle");
	SetOutApplicationLogValidFlag(false);
	if (DxLib_Init()) return -1;
	SetDragFileValidFlag(true);

	while (LoopProc()){
		Puzzle15::get()->Run();
	}

	DxLib_End();

	return 0;
}
