/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2022  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#include "PrecompiledHeader.h"

#include "MemoryCardConvertDialog.h"

#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QProgressDialog>

#include "common/Path.h"
#include "common/StringUtil.h"

#include "pcsx2/System.h"

MemoryCardConvertDialog::MemoryCardConvertDialog(QWidget* parent, QString selectedCard)
	: QDialog(parent)
{
	m_ui.setupUi(this);
	m_selectedCard = selectedCard;
	std::optional<AvailableMcdInfo> srcCardInfo = FileMcd_GetCardInfo(m_selectedCard.toStdString());

	if (srcCardInfo.has_value())
	{
		m_srcCardInfo = srcCardInfo.value();
	}

	isSetup = SetupPicklist();
	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

	m_ui.progressBar->setRange(0, 100);
	m_ui.progressBar->setValue(0);

	connect(m_ui.conversionTypeSelect, &QComboBox::currentIndexChanged, this, [this]() 
		{
			switch (m_srcCardInfo.type)
			{
				case MemoryCardType::File:
					SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
					break;
				case MemoryCardType::Folder:
					switch (m_ui.conversionTypeSelect->currentData().toInt())
					{
						case 8:
							SetType(MemoryCardType::File, MemoryCardFileType::PS2_8MB, "A standard, 8 MB memory card. Most compatible, but smallest capacity.");
							break;
						case 16:
							SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger as a standard memory card. May have some compatibility issues.");
							break;
						case 32:
							SetType(MemoryCardType::File, MemoryCardFileType::PS2_32MB, "4x larger than a standard memory card. Likely to have compatibility issues.");
							break;
						case 64:
							SetType(MemoryCardType::File, MemoryCardFileType::PS2_64MB, "8x larger than a standard memory card. Likely to have compatibility issues.");
							break;
						default:
							QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
							return;
					}
					break;
				default:
					QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
					return;
			}
		}
	);
	
	disconnect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, nullptr);

	connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &MemoryCardConvertDialog::ConvertCard);
	connect(m_ui.buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &MemoryCardConvertDialog::close);
}

MemoryCardConvertDialog::~MemoryCardConvertDialog() = default;

bool MemoryCardConvertDialog::IsSetup()
{
	return isSetup;
}

void MemoryCardConvertDialog::onStatusUpdated()
{

}

void MemoryCardConvertDialog::onProgressUpdated(int value, int range)
{
	m_ui.progressBar->setRange(0, range);
	m_ui.progressBar->setValue(value);
}

void MemoryCardConvertDialog::onThreadFinished()
{
	QMessageBox::information(this, tr("Conversion Complete"), tr("Memory card \"%1\" converted to \"%2\"").arg(m_selectedCard).arg(m_destCardName));
	accept();
}

void MemoryCardConvertDialog::StartThread()
{
	m_thread = std::make_unique<MemoryCardConvertWorker>(this, m_srcCardInfo.type, m_fileType, m_selectedCard.toStdString(), m_destCardName.toStdString());
	connect(m_thread.get(), &MemoryCardConvertWorker::statusUpdated, this, &MemoryCardConvertDialog::onStatusUpdated);
	connect(m_thread.get(), &MemoryCardConvertWorker::progressUpdated, this, &MemoryCardConvertDialog::onProgressUpdated);
	connect(m_thread.get(), &MemoryCardConvertWorker::threadFinished, this, &MemoryCardConvertDialog::onThreadFinished);
	m_thread->start();
	UpdateEnabled();
}

void MemoryCardConvertDialog::CancelThread()
{
	if (!m_thread)
	{
		return;
	}

	m_thread->requestInterruption();
	m_thread->join();
	m_thread.reset();
}

void MemoryCardConvertDialog::UpdateEnabled()
{
	if (m_thread)
	{
		m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
		m_ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
	}
}

bool MemoryCardConvertDialog::SetupPicklist()
{
	FileSystem::FindResultsArray rootDir;
	size_t sizeBytes = 0;
	bool typeSet = false;

	m_ui.conversionTypeSelect->clear();

	switch (m_srcCardInfo.type)
	{
		case MemoryCardType::File:
			m_ui.conversionTypeSelect->addItems({"Folder"});
			SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
			break;
		case MemoryCardType::Folder:
			// Compute which file types should be allowed.
			FileSystem::FindFiles(m_srcCardInfo.path.c_str(), "*", FLAGS, &rootDir);

			for (auto dirEntry : rootDir)
			{
				const std::string_view fileName = Path::GetFileName(dirEntry.FileName);
				
				if (fileName.size() >= 7 && fileName.substr(0, 7).compare("_pcsx2_") == 0)
				{
					continue;
				}
				else if (dirEntry.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
				{
					sizeBytes += 512;
				}
				else
				{
					size_t toAdd = static_cast<size_t>(dirEntry.Size + (1024 - (dirEntry.Size % 1024)));
					sizeBytes += toAdd + 512; // The file content needs to be added, PLUS a directory entry
				}
			}

			// Finally, round up to the nearest erase block.
			sizeBytes += (512 * 16) - (sizeBytes % (512 * 16));

			if (sizeBytes < CardCapacity::_8_MB)
			{
				m_ui.conversionTypeSelect->addItem("8 MB File", 8);
				
				if (!typeSet)
				{
					SetType_8();
					typeSet = true;
				}
			}

			if (sizeBytes < CardCapacity::_16_MB)
			{
				m_ui.conversionTypeSelect->addItem("16 MB File", 16);
				
				if (!typeSet)
				{
					SetType_16();
					typeSet = true;
				}
			}

			if (sizeBytes < CardCapacity::_32_MB)
			{
				m_ui.conversionTypeSelect->addItem("32 MB File", 32);
				
				if (!typeSet)
				{
					SetType_32();
					typeSet = true;
				}
			}

			if (sizeBytes < CardCapacity::_64_MB)
			{
				m_ui.conversionTypeSelect->addItem("64 MB File", 64);
				
				if (!typeSet)
				{
					SetType_64();
					typeSet = true;
				}
			}

			if (!typeSet)
			{
				QMessageBox::critical(this, tr("Cannot Convert Memory Card"), tr("Your folder memory card has too much data inside it to be converted to a file memory card. The largest supported file memory card has a capacity of 64 MB. To convert your folder memory card, you must remove game folders until its size is 64 MB or less."));
				return false;
			}

			break;
		default:
			QMessageBox::critical(this, tr("Convert Memory Card Failed"), tr("Invalid MemoryCardType"));
			return false;
	}

	return true;
}

void MemoryCardConvertDialog::ConvertCard()
{
	if (m_thread)
	{
		CancelThread();
	}
	else
	{
		QString baseName = m_selectedCard;
		
		// Get our destination file name
		size_t extensionPos = baseName.lastIndexOf(".ps2", -1);
		// Strip the extension off of it
		baseName.replace(extensionPos, 4, "");
		// Add _converted to the end of it
		baseName.append("_converted");
		
		size_t num = 0;
		QString destName = baseName;
		destName.append(".ps2");
		
		// If a match is found, revert back to the base name, add a number and the extension, and try again.
		// Keep incrementing the number until we get a unique result.
		while (m_srcCardInfo.type == MemoryCardType::File ? FileSystem::DirectoryExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()) : FileSystem::FileExists(Path::Combine(EmuFolders::MemoryCards, destName.toStdString()).c_str()))
		{
			destName = baseName;
			destName.append(StringUtil::StdStringFromFormat("_%02d.ps2", ++num).c_str());
		}
		
		m_destCardName = destName;
		StartThread();
	}
}

void MemoryCardConvertDialog::ConvertCallback()
{
	Console.WriteLn("%s() Finished", __FUNCTION__);
}

void MemoryCardConvertDialog::SetType(MemoryCardType type, MemoryCardFileType fileType, const QString& description)
{
	m_type = type;
	m_fileType = fileType;
	m_ui.conversionTypeDescription->setPlainText(description);	
}

void MemoryCardConvertDialog::SetType_8()
{
	SetType(MemoryCardType::File, MemoryCardFileType::PS2_8MB, "A standard, 8 MB memory card. Most compatible, but smallest capacity.");
}

void MemoryCardConvertDialog::SetType_16()
{
	SetType(MemoryCardType::File, MemoryCardFileType::PS2_16MB, "2x larger as a standard memory card. May have some compatibility issues.");
}

void MemoryCardConvertDialog::SetType_32()
{
	SetType(MemoryCardType::File, MemoryCardFileType::PS2_32MB, "4x larger than a standard memory card. Likely to have compatibility issues.");
}

void MemoryCardConvertDialog::SetType_64()
{
	SetType(MemoryCardType::File, MemoryCardFileType::PS2_64MB, "8x larger than a standard memory card. Likely to have compatibility issues.");
}

void MemoryCardConvertDialog::SetType_Folder()
{
	SetType(MemoryCardType::Folder, MemoryCardFileType::Unknown, "Uses a folder on your PC filesystem, instead of a file. Infinite capacity, while keeping the same compatibility as an 8 MB memory card.");
}
