//static
void Dlg_GameLibrary::AddTitle(const std::string& sTitle, const std::string& sFilename, GameID nGameID)
{
	LV_ITEM item;
	ZeroMemory(&item, sizeof(item));
	item.mask = LVIF_TEXT;
	item.cchTextMax = 255;
	item.iItem = m_vGameEntries.size();

	HWND hList = GetDlgItem(m_hDialogBox, IDC_RA_LBX_GAMELIST);

	//	id:
	item.iSubItem = 0;
	tstring sID = std::to_string(nGameID);	//scoped cache!
	item.pszText = const_cast<LPTSTR>( sID.c_str() );
	item.iItem = ListView_InsertItem(hList, &item);

	item.iSubItem = 1;
	ListView_SetItemText(hList, item.iItem, 1, const_cast<LPTSTR>(NativeStr(sTitle).c_str()));

	item.iSubItem = 2;
	ListView_SetItemText(hList, item.iItem, 2, const_cast<LPTSTR>(NativeStr(m_ProgressLibrary[nGameID]).c_str()));

	item.iSubItem = 3;
	ListView_SetItemText(hList, item.iItem, 3, const_cast<LPTSTR>(NativeStr(sFilename).c_str()));

	m_vGameEntries.push_back(GameEntry(sTitle, sFilename, nGameID));
}
void Dlg_GameLibrary::RefreshList()
{
	std::map<std::string, std::string>::iterator iter = Results.begin();
	while (iter != Results.end())
	{
		const std::string& filepath = iter->first;
		const std::string& md5 = iter->second;

		if (VisibleResults.find(filepath) == VisibleResults.end())
		{
			//	Not yet added,
			if (m_GameHashLibrary.find(md5) != m_GameHashLibrary.end())
			{
				//	Found in our hash library!
				const GameID nGameID = m_GameHashLibrary[md5];
				RA_LOG("Found one! Game ID %d (%s)", nGameID, m_GameTitlesLibrary[nGameID].c_str());

				const std::string& sGameTitle = m_GameTitlesLibrary[nGameID];
				AddTitle(sGameTitle, filepath, nGameID);

				SetDlgItemText(m_hDialogBox, IDC_RA_SCANNERFOUNDINFO, NativeStr(sGameTitle).c_str());
				VisibleResults[filepath] = md5;	//	Copy to VisibleResults
			}
		}
		iter++;
	}
}
void ParseGameTitlesFromFile(std::map<GameID, std::string>& GameTitlesListOut)
{
	SetCurrentDirectory(NativeStr(g_sHomeDir).c_str());
	FILE* pf = nullptr;
	fopen_s(&pf, RA_TITLES_FILENAME, "rb");
	if (pf != nullptr)
	{
		Document doc;
		doc.ParseStream(FileStream(pf));

		if (!doc.HasParseError() && doc.HasMember("Success") && doc["Success"].GetBool() && doc.HasMember("Response"))
		{
			const Value& List = doc["Response"];
			for (Value::ConstMemberIterator iter = List.MemberBegin(); iter != List.MemberEnd(); ++iter)
			{
				if (iter->name.IsNull() || iter->value.IsNull())
					continue;

				GameID nID = static_cast<GameID>(std::strtoul(iter->name.GetString(), nullptr, 10));	//	KEYS ARE STRINGS, must convert afterwards!
				const std::string sTitle = iter->value.GetString();
				GameTitlesListOut[nID] = sTitle;
			}
		}

		fclose(pf);
	}
}
void ParseGameHashLibraryFromFile(std::map<std::string, GameID>& GameHashLibraryOut)
{
	SetCurrentDirectory(NativeStr(g_sHomeDir).c_str());
	FILE* pf = NULL;
	fopen_s(&pf, RA_GAME_HASH_FILENAME, "rb");
	if (pf != NULL)
	{
		Document doc;
		doc.ParseStream(FileStream(pf));

		if (!doc.HasParseError() && doc.HasMember("Success") && doc["Success"].GetBool() && doc.HasMember("MD5List"))
		{
			const Value& List = doc["MD5List"];
			for (Value::ConstMemberIterator iter = List.MemberBegin(); iter != List.MemberEnd(); ++iter)
			{
				if (iter->name.IsNull() || iter->value.IsNull())
					continue;

				const std::string sMD5 = iter->name.GetString();
				//GameID nID = static_cast<GameID>( std::strtoul( iter->value.GetString(), NULL, 10 ) );	//	MUST BE STRING, then converted to uint. Keys are strings ONLY
				GameID nID = static_cast<GameID>(iter->value.GetUint());
				GameHashLibraryOut[sMD5] = nID;
			}
		}

		fclose(pf);
	}
}
void Dlg_MemBookmark::SetupColumns( HWND hList )
{
	//	Remove all columns,
	while ( ListView_DeleteColumn( hList, 0 ) ) {}

	//	Remove all data.
	ListView_DeleteAllItems( hList );

	LV_COLUMN col;
	ZeroMemory( &col, sizeof( col ) );

	for ( size_t i = 0; i < NumColumns; ++i )
	{
		col.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_FMT;
		col.cx = COLUMN_WIDTH[ i ];
		tstring colTitle = NativeStr( COLUMN_TITLE[ i ] ).c_str();
		col.pszText = const_cast<LPTSTR>( colTitle.c_str() );
		col.cchTextMax = 255;
		col.iSubItem = i;

		col.fmt = LVCFMT_CENTER | LVCFMT_FIXED_WIDTH;
		if ( i == NumColumns - 1 )
			col.fmt |= LVCFMT_FILL;

		ListView_InsertColumn( hList, i, (LPARAM)&col );
	}

	m_nNumOccupiedRows = 0;

	BOOL bSuccess = ListView_SetExtendedListViewStyle( hList, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER );
	bSuccess = ListView_EnableGroupView( hList, FALSE );

}
BOOL Dlg_MemBookmark::EditLabel ( int nItem, int nSubItem )
{
	HWND hList = GetDlgItem( g_MemBookmarkDialog.GetHWND(), IDC_RA_LBX_ADDRESSES );
	RECT rcSubItem;
	ListView_GetSubItemRect( hList, nItem, nSubItem, LVIR_BOUNDS, &rcSubItem );

	RECT rcOffset;
	GetWindowRect ( hList, &rcOffset );
	rcSubItem.left += rcOffset.left;
	rcSubItem.right += rcOffset.left;
	rcSubItem.top += rcOffset.top;
	rcSubItem.bottom += rcOffset.top;

	int nHeight = rcSubItem.bottom - rcSubItem.top;
	int nWidth = rcSubItem.right - rcSubItem.left;

	ASSERT ( g_hIPEEditBM == nullptr );
	if ( g_hIPEEditBM ) return FALSE;

	g_hIPEEditBM = CreateWindowEx(
		WS_EX_CLIENTEDGE,
		_T("EDIT"),
		_T(""),
		WS_CHILD | WS_VISIBLE | WS_POPUPWINDOW | WS_BORDER | ES_WANTRETURN,
		rcSubItem.left, rcSubItem.top, nWidth, (int)( 1.5f*nHeight ),
		g_MemBookmarkDialog.GetHWND(),
		0,
		GetModuleHandle( NULL ),
		NULL );

	if ( g_hIPEEditBM == NULL )
	{
		ASSERT( !"Could not create edit box!" );
		MessageBox( nullptr, _T("Could not create edit box."), _T("Error"), MB_OK | MB_ICONERROR );
		return FALSE;
	};

	SendMessage( g_hIPEEditBM, WM_SETFONT, (WPARAM)GetStockObject( DEFAULT_GUI_FONT ), TRUE );
	SetWindowText( g_hIPEEditBM, NativeStr( m_vBookmarks[ nItem ]->Description() ).c_str() );

	SendMessage( g_hIPEEditBM, EM_SETSEL, 0, -1 );
	SetFocus( g_hIPEEditBM );
	EOldProcBM = (WNDPROC)SetWindowLong( g_hIPEEditBM, GWL_WNDPROC, (LONG)EditProcBM );

	return TRUE;
}
bool ListFiles(std::string path, std::string mask, std::deque<std::string>& rFileListOut)
{
	std::stack<std::string> directories;
	directories.push(path);

	while (!directories.empty())
	{
		path = directories.top();
		std::string spec = path + "\\" + mask;
		directories.pop();

		WIN32_FIND_DATA ffd;
		HANDLE hFind = FindFirstFile(NativeStr(spec).c_str(), &ffd);
		if (hFind == INVALID_HANDLE_VALUE)
			return false;

		do
		{
			std::string sFilename = Narrow(ffd.cFileName);
			if ((strcmp(sFilename.c_str(), ".") == 0) ||
				(strcmp(sFilename.c_str(), "..") == 0))
				continue;

			if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				directories.push(path + "\\" + sFilename);
			else
				rFileListOut.push_front(path + "\\" + sFilename);
		} while (FindNextFile(hFind, &ffd) != 0);

		if (GetLastError() != ERROR_NO_MORE_FILES)
		{
			FindClose(hFind);
			return false;
		}

		FindClose(hFind);
		hFind = INVALID_HANDLE_VALUE;
	}

	return true;
}
INT_PTR CALLBACK Dlg_GameLibrary::GameLibraryProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_INITDIALOG:
	{
		HWND hList = GetDlgItem(hDlg, IDC_RA_LBX_GAMELIST);
		SetupColumns(hList);

		ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);

		SetDlgItemText(hDlg, IDC_RA_ROMDIR, NativeStr(g_sROMDirLocation).c_str());
		SetDlgItemText(hDlg, IDC_RA_GLIB_NAME, TEXT(""));

		m_GameHashLibrary.clear();
		m_GameTitlesLibrary.clear();
		m_ProgressLibrary.clear();
		ParseGameHashLibraryFromFile(m_GameHashLibrary);
		ParseGameTitlesFromFile(m_GameTitlesLibrary);
		ParseMyProgressFromFile(m_ProgressLibrary);

		//int msBetweenRefresh = 1000;	//	auto?
		//SetTimer( hDlg, 1, msBetweenRefresh, (TIMERPROC)g_GameLibrary.s_GameLibraryProc );
		RefreshList();

		return FALSE;
	}

	case WM_TIMER:
		if ((g_GameLibrary.GetHWND() != NULL) && (IsWindowVisible(g_GameLibrary.GetHWND())))
			RefreshList();
		//ReloadGameListData();
		return FALSE;

	case WM_NOTIFY:
		switch (LOWORD(wParam))
		{
		case IDC_RA_LBX_GAMELIST:
		{
			switch (((LPNMHDR)lParam)->code)
			{
			case LVN_ITEMCHANGED:
			{
				//RA_LOG( "Item Changed\n" );
				HWND hList = GetDlgItem(hDlg, IDC_RA_LBX_GAMELIST);
				const int nSel = ListView_GetSelectionMark(hList);
				if (nSel != -1)
				{
					TCHAR buffer[1024];
					ListView_GetItemText(hList, nSel, 1, buffer, 1024);
					SetWindowText(GetDlgItem(hDlg, IDC_RA_GLIB_NAME), buffer);
				}
			}
			break;

			case NM_CLICK:
				//RA_LOG( "Click\n" );
				break;

			case NM_DBLCLK:
				if (LaunchSelected())
				{
					EndDialog(hDlg, TRUE);
					return TRUE;
				}
				break;

			default:
				break;
			}
		}
		return FALSE;

		default:
			RA_LOG("%08x, %08x\n", wParam, lParam);
			return FALSE;
		}

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			if (LaunchSelected())
			{
				EndDialog(hDlg, TRUE);
				return TRUE;
			}
			else
			{
				return FALSE;
			}

		case IDC_RA_RESCAN:
			ReloadGameListData();

			mtx.lock();	//?
			SetDlgItemText(m_hDialogBox, IDC_RA_SCANNERFOUNDINFO, TEXT("Scanning..."));
			mtx.unlock();
			return FALSE;

		case IDC_RA_PICKROMDIR:
			g_sROMDirLocation = GetFolderFromDialog();
			RA_LOG("Selected Folder: %s\n", g_sROMDirLocation.c_str());
			SetDlgItemText(hDlg, IDC_RA_ROMDIR, NativeStr(g_sROMDirLocation).c_str());
			return FALSE;

		case IDC_RA_LBX_GAMELIST:
		{
			HWND hList = GetDlgItem(hDlg, IDC_RA_LBX_GAMELIST);
			const int nSel = ListView_GetSelectionMark(hList);
			if (nSel != -1)
			{
				TCHAR sGameTitle[1024];
				ListView_GetItemText(hList, nSel, 1, sGameTitle, 1024);
				SetWindowText(GetDlgItem(hDlg, IDC_RA_GLIB_NAME), sGameTitle);
			}
		}
		return FALSE;

		case IDC_RA_REFRESH:
			RefreshList();
			return FALSE;

		default:
			return FALSE;
		}

	case WM_PAINT:
		if (nNumParsed != Results.size())
			nNumParsed = Results.size();
		return FALSE;

	case WM_CLOSE:
		EndDialog(hDlg, FALSE);
		return TRUE;

	case WM_USER:
		return FALSE;

	default:
		return FALSE;
	}
}
void Dlg_GameLibrary::ScanAndAddRomsRecursive(const std::string& sBaseDir)
{
	char sSearchDir[2048];
	sprintf_s(sSearchDir, 2048, "%s\\*.*", sBaseDir.c_str());

	WIN32_FIND_DATA ffd;
	HANDLE hFind = FindFirstFile(NativeStr(sSearchDir).c_str(), &ffd);
	if (hFind != INVALID_HANDLE_VALUE)
	{
		unsigned int ROM_MAX_SIZE = 6 * 1024 * 1024;
		unsigned char* sROMRawData = new unsigned char[ROM_MAX_SIZE];

		do
		{
			if (KEYDOWN(VK_ESCAPE))
				break;

			memset(sROMRawData, 0, ROM_MAX_SIZE);	//?!??

			const std::string sFilename = Narrow(ffd.cFileName);
			if (strcmp(sFilename.c_str(), ".") == 0 ||
				strcmp(sFilename.c_str(), "..") == 0)
			{
				//	Ignore 'this'
			}
			else if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				RA_LOG("Directory found: %s\n", ffd.cFileName);
				std::string sRecurseDir = sBaseDir + "\\" + sFilename.c_str();
				ScanAndAddRomsRecursive(sRecurseDir);
			}
			else
			{
				LARGE_INTEGER filesize;
				filesize.LowPart = ffd.nFileSizeLow;
				filesize.HighPart = ffd.nFileSizeHigh;
				if (filesize.QuadPart < 2048 || filesize.QuadPart > ROM_MAX_SIZE)
				{
					//	Ignore: wrong size
					RA_LOG("Ignoring %s, wrong size\n", sFilename.c_str());
				}
				else
				{
					//	Parse as ROM!
					RA_LOG("%s looks good: parsing!\n", sFilename.c_str());

					char sAbsFileDir[2048];
					sprintf_s(sAbsFileDir, 2048, "%s\\%s", sBaseDir.c_str(), sFilename.c_str());

					HANDLE hROMReader = CreateFile(NativeStr(sAbsFileDir).c_str(), GENERIC_READ, FILE_SHARE_READ,
						NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

					if (hROMReader != INVALID_HANDLE_VALUE)
					{
						BY_HANDLE_FILE_INFORMATION File_Inf;
						int nSize = 0;
						if (GetFileInformationByHandle(hROMReader, &File_Inf))
							nSize = (File_Inf.nFileSizeHigh << 16) + File_Inf.nFileSizeLow;

						DWORD nBytes = 0;
						BOOL bResult = ReadFile(hROMReader, sROMRawData, nSize, &nBytes, NULL);
						const std::string sHashOut = RAGenerateMD5(sROMRawData, nSize);

						if (m_GameHashLibrary.find(sHashOut) != m_GameHashLibrary.end())
						{
							const unsigned int nGameID = m_GameHashLibrary[std::string(sHashOut)];
							RA_LOG("Found one! Game ID %d (%s)", nGameID, m_GameTitlesLibrary[nGameID].c_str());

							const std::string& sGameTitle = m_GameTitlesLibrary[nGameID];
							AddTitle(sGameTitle, sAbsFileDir, nGameID);
							SetDlgItemText(m_hDialogBox, IDC_RA_GLIB_NAME, NativeStr(sGameTitle).c_str());
							InvalidateRect(m_hDialogBox, nullptr, TRUE);
						}

						CloseHandle(hROMReader);
					}
				}
			}
		} while (FindNextFile(hFind, &ffd) != 0);

		delete[](sROMRawData);
		sROMRawData = NULL;

		FindClose(hFind);
	}

	SetDlgItemText(m_hDialogBox, IDC_RA_SCANNERFOUNDINFO, TEXT("Scanning complete"));
}
void Dlg_AchievementsReporter::OnOK(HWND hwnd)
{

	m_hProblem2 = GetDlgItem(hwnd, IDC_RA_PROBLEMTYPE2);





	const auto bProblem1Sel{ Button_GetCheck(m_hProblem1) };
	const auto bProblem2Sel{ Button_GetCheck(m_hProblem2) };

	if ((bProblem1Sel == false) && (bProblem2Sel == false))
	{
		MessageBox(nullptr, TEXT("Please select a problem type."),
			TEXT("Warning"), MB_ICONWARNING);
		return;
	}
	// 0==?
	auto nProblemType{ bProblem1Sel ? 1 : bProblem2Sel ? 2 : 0 };
	auto sProblemTypeNice{ PROBLEM_STR.at(to_unsigned(nProblemType)) };

	std::string sBuggedIDs;
	sBuggedIDs.reserve(1024);

	int nReportCount = 0;

	const size_t nListSize = to_unsigned(ListView_GetItemCount(m_hList));
	for (size_t i = 0; i < nListSize; ++i)
	{
		if (ListView_GetCheckState(m_hList, i) != 0)
		{
			//	NASTY big assumption here...
			auto buffer{
				tfm::format("%d,",
				g_pActiveAchievements->GetAchievement(i).ID())
			};
			sBuggedIDs+=buffer;

			//ListView_GetItem( hList );	
			nReportCount++;
		}
	}

	// Needs another check
	if (sBuggedIDs == "")
	{
		// even with this it might be strange, there has to be a better way to do this...
		// The close button will still close it even though this warning will show up
		MessageBox(GetActiveWindow(),
			_T("You need to to select at least one achievement"),
			_T("Warning"), MB_OK);
		return;
	}

	if (nReportCount > 5)
	{
		if (MessageBox(nullptr,
			TEXT("You have over 5 achievements selected. Is this OK?"),
			TEXT("Warning"), MB_YESNO) == IDNO)
			return;
	}


	m_hComment = GetDlgItem(hwnd, IDC_RA_BROKENACHIEVEMENTREPORTCOMMENT);

	// Now I remember
	auto len{ GetTextLength(m_hComment)};
	std::string sBugReportComment;

	// This ones is extremly important or the capacity will change
	sBugReportComment.reserve(static_cast<std::size_t>(len));
	GetText(m_hComment, len, sBugReportComment.data());


	//	Intentionally MBCS
	auto sBugReportInFull{ tfm::format(
		"--New Bug Report--\n"
		"\n"
		"Game: %s\n"
		"Achievement IDs: %s\n"
		"Problem: %s\n"
		"Reporter: %s\n"
		"ROM Checksum: %s\n"
		"\n"
		"Comment: %s\n"
		"\n"
		"Is this OK?",
		g_pCurrentGameData->GameTitle(),
		sBuggedIDs,
		sProblemTypeNice,
		username(),
		g_sCurrentROMMD5,
		sBugReportComment.c_str()) // strange, it won't show itself as a regular string
	};

	if (MessageBox(nullptr, NativeStr(sBugReportInFull).c_str(),
		TEXT("Summary"), MB_YESNO) == IDNO)
		return;

	PostArgs args
	{
		{ 'u', cusername() },
		{ 't', user_token()},
		{ 'i', sBuggedIDs.c_str() },
		{ 'p', std::to_string(nProblemType) },
		{ 'n', sBugReportComment.c_str() },
		{ 'm', g_sCurrentROMMD5.c_str() }
	};

	Document doc;
	// Something is wrong with this function...
	if (RAWeb::DoBlockingRequest(RequestSubmitTicket, args, doc))
	{

		// really weird, success is in there but it's not
		// really bizzare need to check the contents of JSON and do a new approach

		//for (auto& i : doc.GetObjectA())
		//{
		//	RA_LOG("Type of member %s is %s\n", i.name.GetString(),
		//		i.value.GetType());
		//}




		if (doc["Success"].GetBool())
		{
			auto msg{
				"Submitted OK!\n"
				"\n"
				"Thank you for reporting that bug(s), and sorry it hasn't worked correctly.\n"
				"\n"
				"The development team will investigate this bug as soon as possible\n"
				"and we will send you a message on RetroAchievements.org\n"
				"as soon as we have a solution.\n"
				"\n"
				"Thanks again!"
			};

			MessageBox(hwnd, NativeStr(msg).c_str(), TEXT("Success!"), MB_OK);
			// this is so strange, the achievements get sent over but says it's failing
			IRA_Dialog::OnOK(hwnd);
			return;
		}
		else
		{
			auto buffer{ tfm::format(
				"Failed!\n"
				"\n"
				"Response From Server:\n"
				"\n"
				"Error code: %d", doc.GetParseError())
			};
			MessageBox(hwnd, NativeStr(buffer).c_str(), TEXT("Error from server!"), MB_OK);
			return;
		}
	}
	else
	{
		MessageBox(hwnd,
			TEXT("Failed!\n")
			TEXT("\n")
			TEXT("Cannot reach server... are you online?\n")
			TEXT("\n"),
			TEXT("Error!"), MB_OK);
		return;
	}
}