///////////////////////////////////
// Maintenance Class for MENACE DB
///////////////////////////////////
#include	<vector>	//vector使用の為
#include	<string>	//string使用の為
#include	<windows.h>	//Win32(WriteFile, ReadFile)使用の為
#include	<algorithm>	//sort使用の為

using namespace std;

typedef struct _board {
	char m_Pos[9];
	//	-------		盤面はm_Pos[0]-[8]のchar配列を使う
	//	|0|1|2|		これがMENACEのビーズに相当
	//	-------
	//	|3|4|5|
	//	-------
	//	|6|7|8|
	//	-------
	int m_Outcome;	//試合後DB登録用評価データ（先手勝負(1, -1)、後手勝負(2, -2)）
} BOARD, *PBOARD;

class CMENACEDB {
public:
	//メナスデータベース用変数
	vector <BOARD> m_BoardDB;
	vector <BOARD>::iterator m_Itr1, m_Itr2;
	//メナスメンテナンス用関数
	int BoardCmp(char*, char*);	//盤面比較（戻り値はstrcmp(char*, char*)と同じ）
	void Rotate(char*);			//盤を右に90度回転させる
	void Mirror(char*);			//盤を左右反転させる
	int DBSize();				//DBのデータ数を返す
	bool CheckDup();			//登録データの重複チェック
	void Count(int&, int&, int&, int&);	//勝敗結果の集計
	void DBSort();				//DBのデータをm_Posをキーに昇順でソートする
	void DBShow(int);			//DBのデータを表示する
	void DBList();				//DBのデータをリスト表示する
	bool BoardWrite(char*);		//BOARDの書き込み
	bool BoardRead(char*);		//BOARDの読み込み
};

//盤面比較（戻り値はstrcmp(char*, char*)と同じ）
int CMENACEDB::BoardCmp(char* bd1, char* bd2) {

	int i;
	for(i = 0; i < 9; i++) {
		if(bd1[i] != bd2[i])
			break;
	}
	return (bd1[i] - bd2[i]);
}

void CMENACEDB::Rotate(char* bd) {

	char c = bd[0];
	bd[0] = bd[6];
	bd[6] = bd[8];
	bd[8] = bd[2];
	bd[2] = c;
	c = bd[3];
	bd[3] = bd[7];
	bd[7] = bd[5];
	bd[5] = bd[1];
	bd[1] = c;
}

//盤を左右反転させる
//	-------		-------
//	|0|1|2|		|2|1|0|
//	-------		-------
//	|3|4|5|→	|5|4|3|
//	-------		-------
//	|6|7|8|		|8|7|6|
//	-------		-------
void CMENACEDB::Mirror(char* bd) {

	swap(bd[0], bd[2]);
	swap(bd[3], bd[5]);
	swap(bd[6], bd[8]);
}

//DBのデータ数を返す
int CMENACEDB::DBSize() {

	return m_BoardDB.size();
}

//登録データの重複チェック
bool CMENACEDB::CheckDup() {
	
	int i, j;
	bool res = TRUE;
	m_Itr1 = m_BoardDB.begin();
	for(i = 0; m_Itr1 != m_BoardDB.end(); m_Itr1++, i++) {
		for(m_Itr2 = m_Itr1 + 1, j = 1; m_Itr2 != m_BoardDB.end(); m_Itr2++, j++) {
			for(int k = 0; k < 4; k++) {						//4回回して元に戻す
				if(!BoardCmp(m_Itr1->m_Pos, m_Itr2->m_Pos)) {	//重複があった
					cout << "DB第" << i << "番要素の重複データが第" << j << "番目のデータの第" << k << "回転中に見つかりました。" << endl;
					//必要に応じて削除処理
					res = FALSE;
				}
			}
			Rotate(m_Itr1->m_Pos);								//4回回転するので元に戻る
		}
		Mirror(m_Itr1->m_Pos);									//盤面を反転する
		for(m_Itr2 = m_Itr1 + 1, j = 1; m_Itr2 != m_BoardDB.end(); m_Itr2++, j++) {
			for(int k = 0; k < 4; k++) {//4回回して元に戻す
				if(!BoardCmp(m_Itr1->m_Pos, m_Itr2->m_Pos)) {	//重複があった
					cout << "反転したDB第" << i << "番要素の重複データが第" << j << "番目のデータの第" << k << "回転中に見つかりました。" << endl;
					//必要に応じて削除処理
					res = FALSE;
				}
			}
			Rotate(m_Itr1->m_Pos);								//4回回転するので元に戻る
		}
		Mirror(m_Itr1->m_Pos);									//盤面を反転して元に戻す
	}
	if(res)
		cout << "重複データは見つかりませんでした。" << endl;
	return res;
}

//勝敗結果の集計
void CMENACEDB::Count(int& fwin, int& swin, int& flose, int& slose) {

	for(m_Itr1 = m_BoardDB.begin(); m_Itr1 != m_BoardDB.end(); m_Itr1++) {
		switch(m_Itr1->m_Outcome) {
		case 1:
			fwin++;
			break;
		case 2:
			swin++;
			break;
		case -1:
			flose++;
			break;
		case -2:
			slose++;
			break;
		}
	}
}

//DBのデータをm_Posをキーに昇順でソートする
//BDCmpは昇順用比較関数（x < y)でTRUEとなる
bool BDCmp(const BOARD& x, const BOARD& y) {

	int i;
	for(i = 0; i < 9; i++) {
		if(x.m_Pos[i] != y.m_Pos[i])
			break;
	}
	return ((x.m_Pos[i] - y.m_Pos[i]) < 0);
}
//BDCmpを使ってsort関数を使う
void CMENACEDB::DBSort() {

	sort(m_BoardDB.begin(), m_BoardDB.end(), BDCmp);
}

//DBのデータを表示する
void CMENACEDB::DBShow(int n) {

	m_Itr1 = m_BoardDB.begin() + n;
	cout << "-------" << endl;
	for(int i = 0; i < 9; i++) {
		char c;
		switch(m_Itr1->m_Pos[i]) {
		case 0:
			c = ' ';
			break;
		case 1:
			c = 'O';
			break;
		case 2:
			c = 'X';
			break;
		}
		cout << "|" << c;
		if((i % 3) == 2)
			cout << "|" << endl << "-------" << endl;
	}
}

//DBのデータをリスト表示する（1行4列）
void CMENACEDB::DBList() {

	string num, str[4];		//num→レコード番号、str[0]→"------- "、他3行はデータ表示用
	char nums[9];			//レコード番号8文字＋NULL
	int i;
	for(m_Itr1 = m_BoardDB.begin(), i = 0; m_Itr1 != m_BoardDB.end(); m_Itr1++, i++) {
		//盤表示一行毎にstr配列の初期化
		if(!(i % 4)) {
			num = "";
			for(int j = 0; j < 4; j++)
				str[j] = "";
		}
		//レコード番号行
		sprintf(nums, "No.%4d ", i);
		num = num + nums;
		//横線
		str[0] = str[0] + "------- ";
		//データ
		char c;
		for(int j = 0; j < 3; j++) {
			//0 - 2の行
			switch(m_Itr1->m_Pos[j]) {
			case 0:
				c = ' ';
				break;
			case 1:
				c = 'O';
				break;
			case 2:
				c = 'X';
				break;
			}
			str[1] = str[1] + "|" + c;
			//3 - 5の行
			switch(m_Itr1->m_Pos[j + 3]) {
			case 0:
				c = ' ';
				break;
			case 1:
				c = 'O';
				break;
			case 2:
				c = 'X';
				break;
			}
			str[2] = str[2] + "|" + c;
			//6 - 8の行
			switch(m_Itr1->m_Pos[j + 6]) {
			case 0:
				c = ' ';
				break;
			case 1:
				c = 'O';
				break;
			case 2:
				c = 'X';
				break;
			}
			str[3] = str[3] + "|" + c;
		}
		str[1] = str[1] + "| ";
		str[2] = str[2] + "| ";
		str[3] = str[3] + "| ";
		if((i % 4) == 3) {
			cout << num << endl;
			cout << str[0] << endl;
			for(int j = 1; j < 4; j++) {
				cout << str[j] << endl;
				cout << str[0] << endl;
			}
		}
		if(i && !(i % 5)) {	//5行毎に一旦ストップする（最初は実行しない）
			cout << "何かキーを押してください．．．（SPC→中止）" << endl;
			//Pause
			if(getch() == ' ')
				return;;
		}
	}
	if(!(i % 4)) {	//上記の表示改行処理が行われなかった場合の補完
		cout << num << endl;
		cout << str[0] << endl;
		for(int j = 1; j < 4; j++) {
			cout << str[j] << endl;
			cout << str[0] << endl;
		}
	}
}

//BOARDの書き込み
bool CMENACEDB::BoardWrite(char* fn) {

	BOARD board;
	int num = m_BoardDB.size();
	HANDLE hFile = CreateFile(fn, GENERIC_WRITE,
				FILE_SHARE_WRITE, NULL,
				OPEN_ALWAYS, NULL, NULL);	//ファイルを書込でオープン
	if(hFile != INVALID_HANDLE_VALUE) {		//オープンできたか
		DWORD dwWrite;
		//先ずintでデータ数を書き込む
		WriteFile(hFile, &num, sizeof(int), &dwWrite, NULL);
		m_Itr1 = m_BoardDB.begin();
		//次にBOARDの配列を書き込む
		for(int i = 0; i < num; i++, m_Itr1++) {
			board = *m_Itr1;
			WriteFile(hFile, &board, sizeof(board), &dwWrite, NULL);
		}
		CloseHandle(hFile);				//ファイルのクローズ
		return TRUE;
	}
	else
		return FALSE;
}

//BOARDの読み込み
bool CMENACEDB::BoardRead(char* fn) {

	BOARD board;
	int num;
	HANDLE hFile = CreateFile(fn, GENERIC_READ,
				FILE_SHARE_READ, NULL,
				OPEN_EXISTING, NULL, NULL);	//ファイルを読込でオープン
	if(hFile != INVALID_HANDLE_VALUE) {		//オープンできたか
		m_BoardDB.clear();					//m_BoardDBのデータを空にする
		DWORD dwRead;
		//先ずint numにデータ数を読み込む
		ReadFile(hFile, &num, sizeof(int), &dwRead, NULL);
		for(int i = 0; i < num; i++) {
			ReadFile(hFile, &board, sizeof(board), &dwRead, NULL);
			m_BoardDB.push_back(board);
		}
		CloseHandle(hFile);				//ファイルのクローズ
		return TRUE;
	}
	else
		return FALSE;
}
