// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <algorithm>
#include <cinttypes>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <wx/app.h>
#include <wx/bitmap.h>
#include <wx/buffer.h>
#include <wx/colour.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/filefn.h>
#include <wx/filename.h>
#include <wx/gdicmn.h>
#include <wx/imaglist.h>
#include <wx/listctrl.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/settings.h>
#include <wx/tipwin.h>
#include <wx/wxcrt.h>

#include "Common/CDUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/MathUtil.h"
#include "Common/StringUtil.h"
#include "Common/SysConf.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/Movie.h"
#include "Core/Boot/Boot.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/WiiSaveCrypted.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
#include "DolphinWX/Frame.h"
#include "DolphinWX/GameListCtrl.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/ISOProperties.h"
#include "DolphinWX/Main.h"
#include "DolphinWX/WxUtils.h"

struct CompressionProgress final
{
public:
	CompressionProgress(int items_done_, int items_total_, const std::string& current_filename_, wxProgressDialog* dialog_)
		: items_done(items_done_), items_total(items_total_), current_filename(current_filename_), dialog(dialog_)
	{ }

	int items_done;
	int items_total;
	std::string current_filename;
	wxProgressDialog* dialog;
};

static bool sorted = false;

static int CompareGameListItems(const GameListItem* iso1, const GameListItem* iso2,
                                long sortData = CGameListCtrl::COLUMN_TITLE)
{
	int t = 1;

	if (sortData < 0)
	{
		t = -1;
		sortData = -sortData;
	}

	switch (sortData)
	{
		case CGameListCtrl::COLUMN_TITLE:
			if (!strcasecmp(iso1->GetName().c_str(), iso2->GetName().c_str()))
			{
				if (iso1->GetUniqueID() != iso2->GetUniqueID())
					return t * (iso1->GetUniqueID() > iso2->GetUniqueID() ? 1 : -1);
				if (iso1->GetRevision() != iso2->GetRevision())
					return t * (iso1->GetRevision() > iso2->GetRevision() ? 1 : -1);
				if (iso1->GetDiscNumber() != iso2->GetDiscNumber())
					return t * (iso1->GetDiscNumber() > iso2->GetDiscNumber() ? 1 : -1);

				wxString iso1_filename = wxFileNameFromPath(iso1->GetFileName());
				wxString iso2_filename = wxFileNameFromPath(iso2->GetFileName());

				if (iso1_filename != iso2_filename)
					return t * wxStricmp(iso1_filename, iso2_filename);
			}
			return strcasecmp(iso1->GetName().c_str(), iso2->GetName().c_str()) * t;
		case CGameListCtrl::COLUMN_MAKER:
			return strcasecmp(iso1->GetCompany().c_str(), iso2->GetCompany().c_str()) * t;
		case CGameListCtrl::COLUMN_FILENAME:
			return wxStricmp(wxFileNameFromPath(iso1->GetFileName()),
			                 wxFileNameFromPath(iso2->GetFileName())) * t;
		case CGameListCtrl::COLUMN_ID:
			return strcasecmp(iso1->GetUniqueID().c_str(), iso2->GetUniqueID().c_str()) * t;
		case CGameListCtrl::COLUMN_COUNTRY:
			if (iso1->GetCountry() > iso2->GetCountry())
				return  1 * t;
			if (iso1->GetCountry() < iso2->GetCountry())
				return -1 * t;
			return 0;
		case CGameListCtrl::COLUMN_SIZE:
			if (iso1->GetFileSize() > iso2->GetFileSize())
				return  1 * t;
			if (iso1->GetFileSize() < iso2->GetFileSize())
				return -1 * t;
			return 0;
		case CGameListCtrl::COLUMN_PLATFORM:
			if (iso1->GetPlatform() > iso2->GetPlatform())
				return  1 * t;
			if (iso1->GetPlatform() < iso2->GetPlatform())
				return -1 * t;
			return 0;

		case CGameListCtrl::COLUMN_EMULATION_STATE:
		{
			const int
				nState1 = iso1->GetEmuState(),
				nState2 = iso2->GetEmuState();

			if (nState1 > nState2)
				return  1 * t;
			if (nState1 < nState2)
				return -1 * t;
			else
				return 0;
		}
			break;
	}

	return 0;
}

CGameListCtrl::CGameListCtrl(wxWindow* parent, const wxWindowID id, const
		wxPoint& pos, const wxSize& size, long style)
	: wxListCtrl(parent, id, pos, size, style), toolTip(nullptr)
{
	Bind(wxEVT_SIZE, &CGameListCtrl::OnSize, this);
	Bind(wxEVT_RIGHT_DOWN, &CGameListCtrl::OnRightClick, this);
	Bind(wxEVT_LEFT_DOWN, &CGameListCtrl::OnLeftClick, this);
	Bind(wxEVT_MOTION, &CGameListCtrl::OnMouseMotion, this);
	Bind(wxEVT_LIST_KEY_DOWN, &CGameListCtrl::OnKeyPress, this);
	Bind(wxEVT_LIST_COL_BEGIN_DRAG, &CGameListCtrl::OnColBeginDrag, this);
	Bind(wxEVT_LIST_COL_CLICK, &CGameListCtrl::OnColumnClick, this);

	Bind(wxEVT_MENU, &CGameListCtrl::OnProperties, this, IDM_PROPERTIES);
	Bind(wxEVT_MENU, &CGameListCtrl::OnWiki, this, IDM_GAME_WIKI);
	Bind(wxEVT_MENU, &CGameListCtrl::OnOpenContainingFolder, this, IDM_OPEN_CONTAINING_FOLDER);
	Bind(wxEVT_MENU, &CGameListCtrl::OnOpenSaveFolder, this, IDM_OPEN_SAVE_FOLDER);
	Bind(wxEVT_MENU, &CGameListCtrl::OnExportSave, this, IDM_EXPORT_SAVE);
	Bind(wxEVT_MENU, &CGameListCtrl::OnSetDefaultISO, this, IDM_SET_DEFAULT_ISO);
	Bind(wxEVT_MENU, &CGameListCtrl::OnCompressISO, this, IDM_COMPRESS_ISO);
	Bind(wxEVT_MENU, &CGameListCtrl::OnMultiCompressISO, this, IDM_MULTI_COMPRESS_ISO);
	Bind(wxEVT_MENU, &CGameListCtrl::OnMultiDecompressISO, this, IDM_MULTI_DECOMPRESS_ISO);
	Bind(wxEVT_MENU, &CGameListCtrl::OnDeleteISO, this, IDM_DELETE_ISO);
	Bind(wxEVT_MENU, &CGameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC);

	wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &CGameListCtrl::OnLocalIniModified, this);
}

CGameListCtrl::~CGameListCtrl()
{
	ClearIsoFiles();
}

void CGameListCtrl::InitBitmaps()
{
	wxSize size(96, 32);
	wxImageList* img_list = new wxImageList(96, 32);
	AssignImageList(img_list, wxIMAGE_LIST_SMALL);

	m_FlagImageIndex.resize(DiscIO::IVolume::NUMBER_OF_COUNTRIES);
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_JAPAN]       = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Japan", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_EUROPE]      = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Europe", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_USA]         = img_list->Add(WxUtils::LoadResourceBitmap("Flag_USA", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_AUSTRALIA]   = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Australia", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_FRANCE]      = img_list->Add(WxUtils::LoadResourceBitmap("Flag_France", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_GERMANY]     = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Germany", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_ITALY]       = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Italy", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_KOREA]       = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Korea", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_NETHERLANDS] = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Netherlands", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_RUSSIA]      = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Russia", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_SPAIN]       = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Spain", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_TAIWAN]      = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Taiwan", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_WORLD]       = img_list->Add(WxUtils::LoadResourceBitmap("Flag_International", size));
	m_FlagImageIndex[DiscIO::IVolume::COUNTRY_UNKNOWN]     = img_list->Add(WxUtils::LoadResourceBitmap("Flag_Unknown", size));

	m_PlatformImageIndex.resize(4);
	m_PlatformImageIndex[DiscIO::IVolume::GAMECUBE_DISC]   = img_list->Add(WxUtils::LoadResourceBitmap("Platform_Gamecube", size));
	m_PlatformImageIndex[DiscIO::IVolume::WII_DISC]        = img_list->Add(WxUtils::LoadResourceBitmap("Platform_Wii", size));
	m_PlatformImageIndex[DiscIO::IVolume::WII_WAD]         = img_list->Add(WxUtils::LoadResourceBitmap("Platform_Wad", size));
	m_PlatformImageIndex[DiscIO::IVolume::ELF_DOL]         = img_list->Add(WxUtils::LoadResourceBitmap("Platform_File", size));

	m_EmuStateImageIndex.resize(6);
	m_EmuStateImageIndex[0] = img_list->Add(WxUtils::LoadResourceBitmap("rating0", size));
	m_EmuStateImageIndex[1] = img_list->Add(WxUtils::LoadResourceBitmap("rating1", size));
	m_EmuStateImageIndex[2] = img_list->Add(WxUtils::LoadResourceBitmap("rating2", size));
	m_EmuStateImageIndex[3] = img_list->Add(WxUtils::LoadResourceBitmap("rating3", size));
	m_EmuStateImageIndex[4] = img_list->Add(WxUtils::LoadResourceBitmap("rating4", size));
	m_EmuStateImageIndex[5] = img_list->Add(WxUtils::LoadResourceBitmap("rating5", size));
}

void CGameListCtrl::BrowseForDirectory()
{
	wxString dirHome;
	wxGetHomeDir(&dirHome);

	// browse
	wxDirDialog dialog(this, _("Browse for a directory to add"), dirHome,
			wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);

	if (dialog.ShowModal() == wxID_OK)
	{
		std::string sPath(WxStrToStr(dialog.GetPath()));
		std::vector<std::string>::iterator itResult = std::find(
				SConfig::GetInstance().m_ISOFolder.begin(),
				SConfig::GetInstance().m_ISOFolder.end(), sPath);

		if (itResult == SConfig::GetInstance().m_ISOFolder.end())
		{
			SConfig::GetInstance().m_ISOFolder.push_back(sPath);
			SConfig::GetInstance().SaveSettings();
		}

		Update();
	}
}

void CGameListCtrl::Update()
{
	int scrollPos = wxWindow::GetScrollPos(wxVERTICAL);
	// Don't let the user refresh it while a game is running
	if (Core::GetState() != Core::CORE_UNINITIALIZED)
		return;

	ScanForISOs();

	Freeze();
	ClearAll();

	if (m_ISOFiles.size() != 0)
	{
		// Don't load bitmaps unless there are games to list
		InitBitmaps();

		// add columns
		InsertColumn(COLUMN_DUMMY, "");
		InsertColumn(COLUMN_PLATFORM, "");
		InsertColumn(COLUMN_BANNER, _("Banner"));
		InsertColumn(COLUMN_TITLE, _("Title"));

		InsertColumn(COLUMN_MAKER, _("Maker"));
		InsertColumn(COLUMN_FILENAME, _("File"));
		InsertColumn(COLUMN_ID, _("ID"));
		InsertColumn(COLUMN_COUNTRY, "");
		InsertColumn(COLUMN_SIZE, _("Size"));
		InsertColumn(COLUMN_EMULATION_STATE, _("State"));

#ifdef __WXMSW__
		const int platform_padding = 0;
#else
		const int platform_padding = 8;
#endif

		const int platform_icon_padding = 1;

		// set initial sizes for columns
		SetColumnWidth(COLUMN_DUMMY, 0);
		SetColumnWidth(COLUMN_PLATFORM, SConfig::GetInstance().m_showSystemColumn ? 32 + platform_icon_padding + platform_padding : 0);
		SetColumnWidth(COLUMN_BANNER, SConfig::GetInstance().m_showBannerColumn ? 96 + platform_padding : 0);
		SetColumnWidth(COLUMN_TITLE, 175 + platform_padding);
		SetColumnWidth(COLUMN_MAKER, SConfig::GetInstance().m_showMakerColumn ? 150 + platform_padding : 0);
		SetColumnWidth(COLUMN_FILENAME, SConfig::GetInstance().m_showFileNameColumn ? 100 + platform_padding : 0);
		SetColumnWidth(COLUMN_ID, SConfig::GetInstance().m_showIDColumn ? 75 + platform_padding : 0);
		SetColumnWidth(COLUMN_COUNTRY, SConfig::GetInstance().m_showRegionColumn ? 32 + platform_padding : 0);
		SetColumnWidth(COLUMN_EMULATION_STATE, SConfig::GetInstance().m_showStateColumn ? 48 + platform_padding : 0);

		// add all items
		for (int i = 0; i < (int)m_ISOFiles.size(); i++)
		{
			InsertItemInReportView(i);
			if (SConfig::GetInstance().m_ColorCompressed && m_ISOFiles[i]->IsCompressed())
				SetItemTextColour(i, wxColour(0xFF0000));
		}

		// Sort items by Title
		if (!sorted)
			last_column = 0;
		sorted = false;
		wxListEvent event;
		event.m_col = SConfig::GetInstance().m_ListSort2;
		OnColumnClick(event);

		event.m_col = SConfig::GetInstance().m_ListSort;
		OnColumnClick(event);
		sorted = true;

		SetColumnWidth(COLUMN_SIZE, SConfig::GetInstance().m_showSizeColumn ? wxLIST_AUTOSIZE : 0);
	}
	else
	{
		// Remove existing image list and replace it with the smallest possible one.
		// The list needs an image list because it reserves screen pixels for the
		// image even if we aren't going to use one. It uses the dimensions of the
		// last non-null list so assigning nullptr doesn't work.
		AssignImageList(new wxImageList(1, 1), wxIMAGE_LIST_SMALL);

		wxString errorString;
		// We just check for one hide setting to be enabled, as we may only
		// have GC games for example, and hide them, so we should show the
		// first message instead
		if (IsHidingItems())
		{
			errorString = _("Dolphin is currently set to hide all games. Double-click here to show all games...");
		}
		else
		{
			errorString = _("Dolphin could not find any GameCube/Wii ISOs or WADs. Double-click here to set a games directory...");
		}
		InsertColumn(0, "");
		long index = InsertItem(0, errorString);
		SetItemFont(index, *wxITALIC_FONT);
		SetColumnWidth(0, wxLIST_AUTOSIZE);
	}
	if (GetSelectedISO() == nullptr)
		main_frame->UpdateGUI();
	// Thaw before calling AutomaticColumnWidth so that GetClientSize will
	// correctly account for the width of scrollbars if they appear.
	Thaw();

	AutomaticColumnWidth();
	ScrollLines(scrollPos);
	SetFocus();
}

static wxString NiceSizeFormat(u64 size)
{
	// Return a pretty filesize string from byte count.
	// e.g. 1134278 -> "1.08 MiB"

	const char* const unit_symbols[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };

	// Find largest power of 2 less than size.
	// div 10 to get largest named unit less than size
	// 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc)
	// Max value is 63 / 10 = 6
	const int unit = IntLog2(std::max<u64>(size, 1)) / 10;

	// Don't need exact values, only 5 most significant digits
	double unit_size = std::pow(2, unit * 10);
	return wxString::Format("%.2f %s", size / unit_size, unit_symbols[unit]);
}

// Update the column content of the item at _Index
void CGameListCtrl::UpdateItemAtColumn(long _Index, int column)
{
	GameListItem& rISOFile = *m_ISOFiles[GetItemData(_Index)];

	switch(column)
	{
		case COLUMN_PLATFORM:
		{
			SetItemColumnImage(_Index, COLUMN_PLATFORM,
			                   m_PlatformImageIndex[rISOFile.GetPlatform()]);
			break;
		}
		case COLUMN_BANNER:
		{
			int ImageIndex = -1;

			if (rISOFile.GetBitmap().IsOk())
				ImageIndex = GetImageList(wxIMAGE_LIST_SMALL)->Add(rISOFile.GetBitmap());

			SetItemColumnImage(_Index, COLUMN_BANNER, ImageIndex);
			break;
		}
		case COLUMN_TITLE:
		{
			wxString name = StrToWxStr(rISOFile.GetName());
			int disc_number = rISOFile.GetDiscNumber() + 1;

			if (disc_number > 1 &&
			    name.Lower().find(wxString::Format("disc %i", disc_number)) == std::string::npos &&
			    name.Lower().find(wxString::Format("disc%i", disc_number)) == std::string::npos)
			{
				name = wxString::Format(_("%s (Disc %i)"), name.c_str(), disc_number);
			}

			SetItem(_Index, COLUMN_TITLE, name, -1);
			break;
		}
		case COLUMN_MAKER:
			SetItem(_Index, COLUMN_MAKER, StrToWxStr(rISOFile.GetCompany()), -1);
			break;
		case COLUMN_FILENAME:
			SetItem(_Index, COLUMN_FILENAME,
			        wxFileNameFromPath(StrToWxStr(rISOFile.GetFileName())), -1);
			break;
		case COLUMN_EMULATION_STATE:
			SetItemColumnImage(_Index, COLUMN_EMULATION_STATE,
			                   m_EmuStateImageIndex[rISOFile.GetEmuState()]);
			break;
		case COLUMN_COUNTRY:
			SetItemColumnImage(_Index, COLUMN_COUNTRY,
			                   m_FlagImageIndex[rISOFile.GetCountry()]);
			break;
		case COLUMN_SIZE:
			SetItem(_Index, COLUMN_SIZE, NiceSizeFormat(rISOFile.GetFileSize()), -1);
			break;
		case COLUMN_ID:
			SetItem(_Index, COLUMN_ID, rISOFile.GetUniqueID(), -1);
			break;
	}
}

void CGameListCtrl::InsertItemInReportView(long index)
{
	// When using wxListCtrl, there is no hope of per-column text colors.
	// But for reference, here are the old colors that were used: (BGR)
	// title: 0xFF0000
	// company: 0x007030

	// Insert a first column with nothing in it, that will be used as the Index
	long item_index;
	{
		wxListItem li;
		li.SetId(index);
		li.SetData(index);
		li.SetMask(wxLIST_MASK_DATA);
		item_index = InsertItem(li);
	}

	// Iterate over all columns and fill them with content if they are visible
	for (int i = 1; i < NUMBER_OF_COLUMN; i++)
	{
		if (GetColumnWidth(i) != 0)
			UpdateItemAtColumn(item_index, i);
	}

	// Background color
	SetBackgroundColor();
}

static wxColour blend50(const wxColour& c1, const wxColour& c2)
{
	unsigned char r,g,b,a;
	r = c1.Red()/2   + c2.Red()/2;
	g = c1.Green()/2 + c2.Green()/2;
	b = c1.Blue()/2  + c2.Blue()/2;
	a = c1.Alpha()/2 + c2.Alpha()/2;
	return a << 24 | b << 16 | g << 8 | r;
}

void CGameListCtrl::SetBackgroundColor()
{
	for (long i = 0; i < GetItemCount(); i++)
	{
		wxColour color = (i & 1) ?
			blend50(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT),
					wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)) :
			wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
		CGameListCtrl::SetItemBackgroundColour(i, color);
	}
}

void CGameListCtrl::ScanForISOs()
{
	ClearIsoFiles();

	// Load custom game titles from titles.txt
	// http://www.gametdb.com/Wii/Downloads
	std::unordered_map<std::string, std::string> custom_title_map;
	std::ifstream titlestxt;
	OpenFStream(titlestxt, File::GetUserPath(D_LOAD_IDX) + "titles.txt", std::ios::in);

	if (!titlestxt.is_open())
		OpenFStream(titlestxt, File::GetUserPath(D_LOAD_IDX) + "wiitdb.txt", std::ios::in);

	if (titlestxt.is_open())
	{
		std::string line;
		while (!titlestxt.eof() && std::getline(titlestxt, line))
		{
			const size_t equals_index = line.find('=');
			if (equals_index != std::string::npos)
				custom_title_map.emplace(StripSpaces(line.substr(0, equals_index)),
			                             StripSpaces(line.substr(equals_index + 1)));
		}
		titlestxt.close();
	}

	std::vector<std::string> Extensions;

	if (SConfig::GetInstance().m_ListGC)
		Extensions.push_back(".gcm");
	if (SConfig::GetInstance().m_ListWii || SConfig::GetInstance().m_ListGC)
	{
		Extensions.push_back(".iso");
		Extensions.push_back(".ciso");
		Extensions.push_back(".gcz");
		Extensions.push_back(".wbfs");
	}
	if (SConfig::GetInstance().m_ListWad)
		Extensions.push_back(".wad");
	if (SConfig::GetInstance().m_ListElfDol)
	{
		Extensions.push_back(".dol");
		Extensions.push_back(".elf");
	}

	auto rFilenames = DoFileSearch(Extensions, SConfig::GetInstance().m_ISOFolder, SConfig::GetInstance().m_RecursiveISOFolder);

	if (rFilenames.size() > 0)
	{
		wxProgressDialog dialog(
			_("Scanning for ISOs"),
			_("Scanning..."),
			(int)rFilenames.size() - 1,
			this,
			wxPD_APP_MODAL |
			wxPD_AUTO_HIDE |
			wxPD_CAN_ABORT |
			wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
			wxPD_SMOOTH // - makes updates as small as possible (down to 1px)
			);

		for (u32 i = 0; i < rFilenames.size(); i++)
		{
			std::string FileName;
			SplitPath(rFilenames[i], nullptr, &FileName, nullptr);

			// Update with the progress (i) and the message
			dialog.Update(i, wxString::Format(_("Scanning %s"),
				StrToWxStr(FileName)));
			if (dialog.WasCancelled())
				break;

			auto iso_file = std::make_unique<GameListItem>(rFilenames[i], custom_title_map);

			if (iso_file->IsValid())
			{
				bool list = true;

				switch(iso_file->GetPlatform())
				{
					case DiscIO::IVolume::WII_DISC:
						if (!SConfig::GetInstance().m_ListWii)
							list = false;
						break;
					case DiscIO::IVolume::WII_WAD:
						if (!SConfig::GetInstance().m_ListWad)
							list = false;
						break;
					case DiscIO::IVolume::ELF_DOL:
						if (!SConfig::GetInstance().m_ListElfDol)
							list = false;
						break;
					default:
						if (!SConfig::GetInstance().m_ListGC)
							list = false;
						break;
				}

				switch(iso_file->GetCountry())
				{
					case DiscIO::IVolume::COUNTRY_AUSTRALIA:
						if (!SConfig::GetInstance().m_ListAustralia)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_EUROPE:
						if (!SConfig::GetInstance().m_ListPal)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_FRANCE:
						if (!SConfig::GetInstance().m_ListFrance)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_GERMANY:
						if (!SConfig::GetInstance().m_ListGermany)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_ITALY:
						if (!SConfig::GetInstance().m_ListItaly)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_JAPAN:
						if (!SConfig::GetInstance().m_ListJap)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_KOREA:
						if (!SConfig::GetInstance().m_ListKorea)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_NETHERLANDS:
						if (!SConfig::GetInstance().m_ListNetherlands)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_RUSSIA:
						if (!SConfig::GetInstance().m_ListRussia)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_SPAIN:
						if (!SConfig::GetInstance().m_ListSpain)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_TAIWAN:
						if (!SConfig::GetInstance().m_ListTaiwan)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_USA:
						if (!SConfig::GetInstance().m_ListUsa)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_WORLD:
						if (!SConfig::GetInstance().m_ListWorld)
							list = false;
						break;
					case DiscIO::IVolume::COUNTRY_UNKNOWN:
					default:
						if (!SConfig::GetInstance().m_ListUnknown)
							list = false;
						break;
				}

				if (list)
					m_ISOFiles.push_back(iso_file.release());
			}
		}
	}

	if (SConfig::GetInstance().m_ListDrives)
	{
		const std::vector<std::string> drives = cdio_get_devices();

		for (const auto& drive : drives)
		{
			auto gli = std::make_unique<GameListItem>(drive, custom_title_map);

			if (gli->IsValid())
				m_ISOFiles.push_back(gli.release());
		}
	}

	std::sort(m_ISOFiles.begin(), m_ISOFiles.end());
}

void CGameListCtrl::OnLocalIniModified(wxCommandEvent& ev)
{
	ev.Skip();
	std::string game_id = WxStrToStr(ev.GetString());
	// NOTE: The same game may occur multiple times if there are multiple
	//   physical copies in the search paths.
	for (std::size_t i = 0; i < m_ISOFiles.size(); ++i)
	{
		if (m_ISOFiles[i]->GetUniqueID() != game_id)
			continue;
		m_ISOFiles[i]->ReloadINI();

		// The indexes in m_ISOFiles and the list do not line up.
		// We need to find the corresponding item in the list (if it exists)
		long item_id = 0;
		for (; item_id < GetItemCount(); ++item_id)
		{
			if (i == static_cast<std::size_t>(GetItemData(item_id)))
				break;
		}
		// If the item is not currently being displayed then we're done.
		if (item_id == GetItemCount())
			continue;

		// Update all the columns
		for (int j = 1; j < NUMBER_OF_COLUMN; ++j)
		{
			// NOTE: Banner is not modified by the INI and updating it will
			//  duplicate it in memory which is not wanted.
			if (j != COLUMN_BANNER && GetColumnWidth(j) != 0)
				UpdateItemAtColumn(item_id, j);
		}
	}
}

void CGameListCtrl::OnColBeginDrag(wxListEvent& event)
{
	const int column_id = event.GetColumn();

	if (column_id != COLUMN_TITLE && column_id != COLUMN_MAKER && column_id != COLUMN_FILENAME)
		event.Veto();
}

const GameListItem* CGameListCtrl::GetISO(size_t index) const
{
	if (index < m_ISOFiles.size())
		return m_ISOFiles[index];
	else
		return nullptr;
}

static CGameListCtrl* caller;
static int wxCALLBACK wxListCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)
{
	// return 1 if item1 > item2
	// return -1 if item1 < item2
	// return 0 for identity
	const GameListItem* iso1 = caller->GetISO(item1);
	const GameListItem* iso2 = caller->GetISO(item2);

	return CompareGameListItems(iso1, iso2, sortData);
}

void CGameListCtrl::OnColumnClick(wxListEvent& event)
{
	if (event.GetColumn() != COLUMN_BANNER)
	{
		int current_column = event.GetColumn();
		if (sorted)
		{
			if (last_column == current_column)
			{
				last_sort = -last_sort;
			}
			else
			{
				SConfig::GetInstance().m_ListSort2 = last_sort;
				last_column = current_column;
				last_sort = current_column;
			}
			SConfig::GetInstance().m_ListSort = last_sort;
		}
		else
		{
			last_sort = current_column;
			last_column = current_column;
		}
		caller = this;
		SortItems(wxListCompare, last_sort);
	}

	SetBackgroundColor();

	event.Skip();
}

// This is used by keyboard gamelist search
void CGameListCtrl::OnKeyPress(wxListEvent& event)
{
	static int lastKey = 0, sLoop = 0;
	int Loop = 0;

	for (int i = 0; i < (int)m_ISOFiles.size(); i++)
	{
		// Easy way to get game string
		wxListItem bleh;
		bleh.SetId(i);
		bleh.SetColumn(COLUMN_TITLE);
		bleh.SetMask(wxLIST_MASK_TEXT);
		GetItem(bleh);

		wxString text = bleh.GetText();

		if (text.MakeUpper()[0] == event.GetKeyCode())
		{
			if (lastKey == event.GetKeyCode() && Loop < sLoop)
			{
				Loop++;
				if (i+1 == (int)m_ISOFiles.size())
					i = -1;
				continue;
			}
			else if (lastKey != event.GetKeyCode())
			{
				sLoop = 0;
			}

			lastKey = event.GetKeyCode();
			sLoop++;

			UnselectAll();
			SetItemState(i, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED,
					wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
			EnsureVisible(i);
			break;
		}

		// If we get past the last game in the list,
		// we'll have to go back to the first one.
		if (i+1 == (int)m_ISOFiles.size() && sLoop > 0 && Loop > 0)
			i = -1;
	}

	event.Skip();
}

// This shows a little tooltip with the current Game's emulation state
void CGameListCtrl::OnMouseMotion(wxMouseEvent& event)
{
	int flags;
	long subitem = 0;
	const long item = HitTest(event.GetPosition(), flags, &subitem);
	static int lastItem = -1;

	if (GetColumnCount() <= 1)
		return;

	if (item != wxNOT_FOUND)
	{
		wxRect Rect;
#ifdef __WXMSW__
		if (subitem == COLUMN_EMULATION_STATE)
#else
		// The subitem parameter of HitTest is only implemented for wxMSW.  On
		// all other platforms it will always be -1.  Check the x position
		// instead.
		GetItemRect(item, Rect);
		if (Rect.GetX() + Rect.GetWidth() - GetColumnWidth(COLUMN_EMULATION_STATE) < event.GetX())
#endif
		{
			if (toolTip || lastItem == item || this != FindFocus())
			{
				if (lastItem != item) lastItem = -1;
				event.Skip();
				return;
			}

			// Emulation status
			static const char* const emuState[] = { "Broken", "Intro", "In-Game", "Playable", "Perfect" };

			const GameListItem& rISO = *m_ISOFiles[GetItemData(item)];

			const int emu_state = rISO.GetEmuState();
			const std::string& issues = rISO.GetIssues();

			// Show a tooltip containing the EmuState and the state description
			if (emu_state > 0 && emu_state < 6)
			{
				char temp[2048];
				sprintf(temp, "^ %s%s%s", emuState[emu_state - 1],
						issues.size() > 0 ? " :\n" : "", issues.c_str());
				toolTip = new wxEmuStateTip(this, StrToWxStr(temp), &toolTip);
			}
			else
			{
				toolTip = new wxEmuStateTip(this, _("Not Set"), &toolTip);
			}

			// Get item Coords
			GetItemRect(item, Rect);
			int mx = Rect.GetWidth();
			int my = Rect.GetY();
#ifndef __WXMSW__
			// For some reason the y position does not account for the header
			// row, so subtract the y position of the first visible item.
			GetItemRect(GetTopItem(), Rect);
			my -= Rect.GetY();
#endif
			// Convert to screen coordinates
			ClientToScreen(&mx, &my);
			toolTip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE),
						my, GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight()));
			toolTip->SetPosition(wxPoint(mx - GetColumnWidth(COLUMN_EMULATION_STATE),
						my - 5 + Rect.GetHeight()));
			lastItem = item;
		}
	}
	if (!toolTip)
		lastItem = -1;

	event.Skip();
}

void CGameListCtrl::OnLeftClick(wxMouseEvent& event)
{
	// Focus the clicked item.
	int flags;
	long item = HitTest(event.GetPosition(), flags);
	if ((item != wxNOT_FOUND) && (GetSelectedItemCount() == 0) &&
			(!event.ControlDown()) && (!event.ShiftDown()))
	{
		SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
		SetItemState(item, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
		wxGetApp().GetCFrame()->UpdateGUI();
	}

	event.Skip();
}

void CGameListCtrl::OnRightClick(wxMouseEvent& event)
{
	// Focus the clicked item.
	int flags;
	long item = HitTest(event.GetPosition(), flags);
	if (item != wxNOT_FOUND)
	{
		if (GetItemState(item, wxLIST_STATE_SELECTED) != wxLIST_STATE_SELECTED)
		{
			UnselectAll();
			SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
		}
		SetItemState(item, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
	}
	if (GetSelectedItemCount() == 1)
	{
		const GameListItem* selected_iso = GetSelectedISO();
		if (selected_iso)
		{
			wxMenu popupMenu;
			DiscIO::IVolume::EPlatform platform = selected_iso->GetPlatform();

			if (platform != DiscIO::IVolume::ELF_DOL)
			{
				popupMenu.Append(IDM_PROPERTIES, _("&Properties"));
				popupMenu.Append(IDM_GAME_WIKI, _("&Wiki"));
				popupMenu.AppendSeparator();
			}
			if (platform == DiscIO::IVolume::WII_DISC || platform == DiscIO::IVolume::WII_WAD)
			{
				popupMenu.Append(IDM_OPEN_SAVE_FOLDER, _("Open Wii &save folder"));
				popupMenu.Append(IDM_EXPORT_SAVE, _("Export Wii save (Experimental)"));
			}
			popupMenu.Append(IDM_OPEN_CONTAINING_FOLDER, _("Open &containing folder"));

			if (platform != DiscIO::IVolume::ELF_DOL)
				popupMenu.AppendCheckItem(IDM_SET_DEFAULT_ISO, _("Set as &default ISO"));

			// First we have to decide a starting value when we append it
			if (platform == SConfig::GetInstance().m_strDefaultISO)
				popupMenu.FindItem(IDM_SET_DEFAULT_ISO)->Check();

			popupMenu.AppendSeparator();
			popupMenu.Append(IDM_DELETE_ISO, _("&Delete File..."));

			if (platform == DiscIO::IVolume::GAMECUBE_DISC || platform == DiscIO::IVolume::WII_DISC)
			{
				if (selected_iso->GetBlobType() == DiscIO::BlobType::GCZ)
					popupMenu.Append(IDM_COMPRESS_ISO, _("Decompress ISO..."));
				else if (selected_iso->GetBlobType() == DiscIO::BlobType::PLAIN)
					popupMenu.Append(IDM_COMPRESS_ISO, _("Compress ISO..."));

				wxMenuItem* changeDiscItem = popupMenu.Append(IDM_LIST_CHANGE_DISC, _("Change &Disc"));
				changeDiscItem->Enable(Core::IsRunning());
			}

			if (platform == DiscIO::IVolume::WII_WAD)
				popupMenu.Append(IDM_LIST_INSTALL_WAD, _("Install to Wii Menu"));

			PopupMenu(&popupMenu);
		}
	}
	else if (GetSelectedItemCount() > 1)
	{
		wxMenu popupMenu;
		popupMenu.Append(IDM_DELETE_ISO, _("&Delete selected ISOs..."));
		popupMenu.AppendSeparator();
		popupMenu.Append(IDM_MULTI_COMPRESS_ISO, _("Compress selected ISOs..."));
		popupMenu.Append(IDM_MULTI_DECOMPRESS_ISO, _("Decompress selected ISOs..."));
		PopupMenu(&popupMenu);
	}
}

const GameListItem* CGameListCtrl::GetSelectedISO() const
{
	if (m_ISOFiles.size() == 0)
	{
		return nullptr;
	}
	else if (GetSelectedItemCount() == 0)
	{
		return nullptr;
	}
	else
	{
		long item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == wxNOT_FOUND)
			return nullptr;
		return m_ISOFiles[GetItemData(item)];
	}
}

std::vector<const GameListItem*> CGameListCtrl::GetAllSelectedISOs() const
{
	std::vector<const GameListItem*> result;
	long item = -1;
	while (true)
	{
		item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
		if (item == wxNOT_FOUND)
			return result;
		result.push_back(m_ISOFiles[GetItemData(item)]);
	}
}

bool CGameListCtrl::IsHidingItems()
{
	return !(SConfig::GetInstance().m_ListGC &&
	         SConfig::GetInstance().m_ListWii &&
	         SConfig::GetInstance().m_ListWad &&
	         SConfig::GetInstance().m_ListElfDol &&
	         SConfig::GetInstance().m_ListJap &&
	         SConfig::GetInstance().m_ListUsa &&
	         SConfig::GetInstance().m_ListPal &&
	         SConfig::GetInstance().m_ListAustralia &&
	         SConfig::GetInstance().m_ListFrance &&
	         SConfig::GetInstance().m_ListGermany &&
	         SConfig::GetInstance().m_ListItaly &&
	         SConfig::GetInstance().m_ListKorea &&
	         SConfig::GetInstance().m_ListNetherlands &&
	         SConfig::GetInstance().m_ListRussia &&
	         SConfig::GetInstance().m_ListSpain &&
	         SConfig::GetInstance().m_ListTaiwan &&
	         SConfig::GetInstance().m_ListWorld &&
	         SConfig::GetInstance().m_ListUnknown);
}

void CGameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;

	wxFileName path = wxFileName::FileName(StrToWxStr(iso->GetFileName()));
	path.MakeAbsolute();
	WxUtils::Explore(WxStrToStr(path.GetPath()));
}

void CGameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;
	std::string path = iso->GetWiiFSPath();
	if (!path.empty())
		WxUtils::Explore(path);
}

void CGameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;

	u64 title_id;
	std::unique_ptr<DiscIO::IVolume> volume(DiscIO::CreateVolumeFromFilename(iso->GetFileName()));
	if (volume && volume->GetTitleID(&title_id))
	{
		CWiiSaveCrypted::ExportWiiSave(title_id);
	}
}

// Save this file as the default file
void CGameListCtrl::OnSetDefaultISO(wxCommandEvent& event)
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso) return;

	if (event.IsChecked())
	{
		// Write the new default value and save it the ini file
		SConfig::GetInstance().m_strDefaultISO =
			iso->GetFileName();
		SConfig::GetInstance().SaveSettings();
	}
	else
	{
		// Otherwise blank the value and save it
		SConfig::GetInstance().m_strDefaultISO = "";
		SConfig::GetInstance().SaveSettings();
	}
}

void CGameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED (event))
{
	const wxString message = GetSelectedItemCount() == 1 ?
			_("Are you sure you want to delete this file? It will be gone forever!") :
			_("Are you sure you want to delete these files? They will be gone forever!");

	if (wxMessageBox(message, _("Warning"), wxYES_NO | wxICON_EXCLAMATION) == wxYES)
	{
		for (const GameListItem* iso : GetAllSelectedISOs())
			File::Delete(iso->GetFileName());
		Update();
	}
}

void CGameListCtrl::OnProperties(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;

	CISOProperties* ISOProperties = new CISOProperties(*iso, this);
	ISOProperties->Show();
}

void CGameListCtrl::OnWiki(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;

	std::string wikiUrl = "https://wiki.dolphin-emu.org/dolphin-redirect.php?gameid=" + iso->GetUniqueID();
	WxUtils::Launch(wikiUrl);
}

bool CGameListCtrl::MultiCompressCB(const std::string& text, float percent, void* arg)
{
	CompressionProgress* progress = static_cast<CompressionProgress*>(arg);

	float total_percent = ((float)progress->items_done + percent) / (float)progress->items_total;
	wxString text_string(StrToWxStr(StringFromFormat("%s (%i/%i) - %s",
				progress->current_filename.c_str(), progress->items_done + 1,
				progress->items_total, text.c_str())));

	return progress->dialog->Update(total_percent * progress->dialog->GetRange(), text_string);
}

void CGameListCtrl::OnMultiCompressISO(wxCommandEvent& /*event*/)
{
	CompressSelection(true);
}

void CGameListCtrl::OnMultiDecompressISO(wxCommandEvent& /*event*/)
{
	CompressSelection(false);
}

void CGameListCtrl::CompressSelection(bool _compress)
{
	std::vector<const GameListItem*> items_to_compress;
	bool wii_compression_warning_accepted = false;
	for (const GameListItem* iso : GetAllSelectedISOs())
	{
		// Don't include items that we can't do anything with
		if (iso->GetPlatform() != DiscIO::IVolume::GAMECUBE_DISC && iso->GetPlatform() != DiscIO::IVolume::WII_DISC)
			continue;
		if (iso->GetBlobType() != DiscIO::BlobType::PLAIN && iso->GetBlobType() != DiscIO::BlobType::GCZ)
			continue;

		items_to_compress.push_back(iso);

		// Show the Wii compression warning if it's relevant and it hasn't been shown already
		if (!wii_compression_warning_accepted && _compress &&
		    !iso->IsCompressed() && iso->GetPlatform() == DiscIO::IVolume::WII_DISC)
		{
			if (WiiCompressWarning())
				wii_compression_warning_accepted = true;
			else
				return;
		}
	}

	wxString dirHome;
	wxGetHomeDir(&dirHome);

	wxDirDialog browseDialog(this, _("Browse for output directory"), dirHome,
			wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
	if (browseDialog.ShowModal() != wxID_OK)
		return;

	bool all_good = true;

	{
		wxProgressDialog progressDialog(
			_compress ? _("Compressing ISO") : _("Decompressing ISO"),
			_("Working..."),
			1000, // Arbitrary number that's larger than the dialog's width in pixels
			this,
			wxPD_APP_MODAL |
			wxPD_CAN_ABORT |
			wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
			wxPD_SMOOTH
			);

		CompressionProgress progress(0, items_to_compress.size(), "", &progressDialog);

		for (const GameListItem* iso : items_to_compress)
		{
			if (!iso->IsCompressed() && _compress)
			{
				std::string FileName;
				SplitPath(iso->GetFileName(), nullptr, &FileName, nullptr);
				progress.current_filename = FileName;
				FileName.append(".gcz");

				std::string OutputFileName;
				BuildCompleteFilename(OutputFileName,
						WxStrToStr(browseDialog.GetPath()),
						FileName);

				if (File::Exists(OutputFileName) &&
						wxMessageBox(
							wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"),
							StrToWxStr(OutputFileName)),
							_("Confirm File Overwrite"),
							wxYES_NO) == wxNO)
					continue;

				all_good &= DiscIO::CompressFileToBlob(iso->GetFileName(),
						OutputFileName,
						(iso->GetPlatform() == DiscIO::IVolume::WII_DISC) ? 1 : 0,
						16384, &MultiCompressCB, &progress);
			}
			else if (iso->IsCompressed() && !_compress)
			{
				std::string FileName;
				SplitPath(iso->GetFileName(), nullptr, &FileName, nullptr);
				progress.current_filename = FileName;
				if (iso->GetPlatform() == DiscIO::IVolume::WII_DISC)
					FileName.append(".iso");
				else
					FileName.append(".gcm");

				std::string OutputFileName;
				BuildCompleteFilename(OutputFileName,
						WxStrToStr(browseDialog.GetPath()),
						FileName);

				if (File::Exists(OutputFileName) &&
						wxMessageBox(
							wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"),
							StrToWxStr(OutputFileName)),
							_("Confirm File Overwrite"),
							wxYES_NO) == wxNO)
					continue;

				all_good &= DiscIO::DecompressBlobToFile(iso->GetFileName().c_str(),
						OutputFileName.c_str(), &MultiCompressCB, &progress);
			}

			progress.items_done++;
		}
	}

	if (!all_good)
		WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));

	Update();
}

bool CGameListCtrl::CompressCB(const std::string& text, float percent, void* arg)
{
	return ((wxProgressDialog*)arg)->
		Update((int)(percent * 1000), StrToWxStr(text));
}

void CGameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED (event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso)
		return;

	bool is_compressed = iso->GetBlobType() == DiscIO::BlobType::GCZ;
	wxString path;

	std::string FileName, FilePath, FileExtension;
	SplitPath(iso->GetFileName(), &FilePath, &FileName, &FileExtension);

	do
	{
		if (is_compressed)
		{
			wxString FileType;
			if (iso->GetPlatform() == DiscIO::IVolume::WII_DISC)
				FileType = _("All Wii ISO files (iso)") + "|*.iso";
			else
				FileType = _("All GameCube GCM files (gcm)") + "|*.gcm";

			path = wxFileSelector(
					_("Save decompressed GCM/ISO"),
					StrToWxStr(FilePath),
					StrToWxStr(FileName) + FileType.After('*'),
					wxEmptyString,
					FileType + "|" + wxGetTranslation(wxALL_FILES),
					wxFD_SAVE,
					this);
		}
		else
		{
			if (iso->GetPlatform() == DiscIO::IVolume::WII_DISC && !WiiCompressWarning())
				return;

			path = wxFileSelector(
					_("Save compressed GCM/ISO"),
					StrToWxStr(FilePath),
					StrToWxStr(FileName) + ".gcz",
					wxEmptyString,
					_("All compressed GC/Wii ISO files (gcz)") +
						wxString::Format("|*.gcz|%s", wxGetTranslation(wxALL_FILES)),
					wxFD_SAVE,
					this);
		}
		if (!path)
			return;
	} while (wxFileExists(path) &&
			wxMessageBox(
				wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"), path.c_str()),
				_("Confirm File Overwrite"),
				wxYES_NO) == wxNO);

	bool all_good = false;

	{
	wxProgressDialog dialog(
		is_compressed ? _("Decompressing ISO") : _("Compressing ISO"),
		_("Working..."),
		1000,
		this,
		wxPD_APP_MODAL |
		wxPD_CAN_ABORT |
		wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
		wxPD_SMOOTH
		);


	if (is_compressed)
		all_good = DiscIO::DecompressBlobToFile(iso->GetFileName(),
				WxStrToStr(path), &CompressCB, &dialog);
	else
		all_good = DiscIO::CompressFileToBlob(iso->GetFileName(),
				WxStrToStr(path),
				(iso->GetPlatform() == DiscIO::IVolume::WII_DISC) ? 1 : 0,
				16384, &CompressCB, &dialog);
	}

	if (!all_good)
		WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));

	Update();
}

void CGameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event))
{
	const GameListItem* iso = GetSelectedISO();
	if (!iso || !Core::IsRunning())
		return;
	DVDInterface::ChangeDisc(WxStrToStr(iso->GetFileName()));
}

void CGameListCtrl::OnSize(wxSizeEvent& event)
{
	event.Skip();
	if (lastpos == event.GetSize())
		return;

	lastpos = event.GetSize();
	AutomaticColumnWidth();
}

void CGameListCtrl::AutomaticColumnWidth()
{
	wxRect rc(GetClientRect());

	Freeze();
	if (GetColumnCount() == 1)
	{
		SetColumnWidth(0, rc.GetWidth());
	}
	else if (GetColumnCount() > 0)
	{
		int resizable = rc.GetWidth() - (
			GetColumnWidth(COLUMN_PLATFORM)
			+ GetColumnWidth(COLUMN_BANNER)
			+ GetColumnWidth(COLUMN_ID)
			+ GetColumnWidth(COLUMN_COUNTRY)
			+ GetColumnWidth(COLUMN_SIZE)
			+ GetColumnWidth(COLUMN_EMULATION_STATE));

		if (SConfig::GetInstance().m_showMakerColumn &&
		    SConfig::GetInstance().m_showFileNameColumn)
		{
			SetColumnWidth(COLUMN_TITLE, resizable / 3);
			SetColumnWidth(COLUMN_MAKER, resizable / 3);
			SetColumnWidth(COLUMN_FILENAME, resizable / 3);
		}
		else if (SConfig::GetInstance().m_showMakerColumn)
		{
			SetColumnWidth(COLUMN_TITLE, resizable / 2);
			SetColumnWidth(COLUMN_MAKER, resizable / 2);
		}
		else if (SConfig::GetInstance().m_showFileNameColumn)
		{
			SetColumnWidth(COLUMN_TITLE, resizable / 2);
			SetColumnWidth(COLUMN_FILENAME, resizable / 2);
		}
		else
		{
			SetColumnWidth(COLUMN_TITLE, resizable);
		}
	}
	Thaw();
}

void CGameListCtrl::UnselectAll()
{
	for (int i = 0; i < GetItemCount(); i++)
	{
		SetItemState(i, 0, wxLIST_STATE_SELECTED);
	}
}
bool CGameListCtrl::WiiCompressWarning()
{
	return wxMessageBox(
			_("Compressing a Wii disc image will irreversibly change the compressed copy by removing padding data. Your disc image will still work. Continue?"),
			_("Warning"),
			wxYES_NO) == wxYES;
}
