#include "DumpDVD.hpp"
#include <imgui.h>
#include <SDL.h>
#include <portable-file-dialogs.h>
#include <mutex>
#include <thread>
#include <filesystem>



#include "../Utils.hpp"
#include "../Scsi/IoCtl.hpp"
#include "../Scsi/OpticalDrive.hpp"
#include "../Dvd/DvdRipper.hpp"
#include "../Dvd/TitleKey.hpp"


namespace Li::Gui {

	DumpDVD::DumpDVD() {
		this->discInserted = false;
		this->keepPolling = true;
		this->showDemoWindow = true;
		this->imRippinIt = false;
		this->selectedDrive = 0;
		this->selectedDriveSpeed = 0;
		this->counter = 0;

		this->drivesList = Li::Scsi::OpticalDrive::ListOpticalDrives();
		this->pollDrives = new std::thread(&DumpDVD::pollDrivesThread, this);

		this->sectorsAtOnce = 0x400;
		this->lock = new std::mutex();
		
		this->reset();
	}

	DumpDVD::~DumpDVD() {
		this->keepPolling = false;
		this->pollDrives->join();
		delete this->lock;
		delete this->pollDrives;
		freeOldDriveList();
	}

	void DumpDVD::pollDrivesThread() {
		while (this->keepPolling) {
			if ( (this->counter % (60 * 10)) == 0) {
				this->refreshDriveList();
			}
		}
	}

	void DumpDVD::refreshDriveList() {
		std::vector<Li::Scsi::OpticalDrive*>* pollDrives = Li::Scsi::OpticalDrive::ListOpticalDrives();
		this->lock->lock();
		this->freeOldDriveList();
		this->drivesList = pollDrives;

		if (this->selectedDrive >= this->drivesList->size())
			this->reset();

		this->lock->unlock();

	}
	void DumpDVD::freeOldDriveList() {
		std::vector<Li::Scsi::OpticalDrive*>* prevList = this->drivesList;
		this->drivesList = nullptr;

		for (Li::Scsi::OpticalDrive* drive : *prevList) {
			delete drive;
		}
		delete prevList;
	}
	std::string DumpDVD::getDrivesStr() {
		std::string drivesComboBox = "";
		for (Li::Scsi::OpticalDrive* drive : *this->drivesList) {
			drivesComboBox += drive->DrivePath() + " - " + drive->VolumeName() + '\0';
		}
		return drivesComboBox;
	}

	void DumpDVD::reset() {
		if (this->GetCurrentSelectedDrive()->DiscInDrive()) {
			this->selectedDriveSpeed = this->GetCurrentSelectedDrive()->SupportedSpeeds()->size();
			if (this->GetCurrentSelectedDrive()->HasCss()) {
				this->decrypt = true;
			}
		}
		else {
			this->selectedDriveSpeed = 0;
		}

		std::filesystem::path p = std::filesystem::path(Utils::GetDocumentsFolder());
		p = p.append(GetCurrentSelectedDrive()->VolumeName()).replace_extension(".iso");
		strncpy(this->outputFile, p.string().c_str(), sizeof(DumpDVD::outputFile)-1);
	}

	Li::Scsi::OpticalDrive* DumpDVD::GetCurrentSelectedDrive() {
		return this->drivesList->at(this->selectedDrive);
	}

	void DumpDVD::startRip() {
		if (this->GetCurrentSelectedDrive()->DiscInDrive()) {
			this->keepPolling = false;
			this->pollDrives->join();
			delete this->pollDrives;

			std::vector<uint32_t>* speeds = this->GetCurrentSelectedDrive()->SupportedSpeeds();
			uint32_t speed = 0xFFFF;
			if (this->selectedDriveSpeed < speeds->size()) {
				speed = speeds->at(this->selectedDriveSpeed);
			}

			this->dvdRipper = new Li::Dvd::DvdRipper(this->GetCurrentSelectedDrive(), std::string(this->outputFile), this->sectorsAtOnce);
			this->dvdRipper->StartRip(speed, this->decrypt);

			this->imRippinIt = true;
		}
	}

	void DumpDVD::endRipAndGoBackToMenu() {
		this->dvdRipper->EndRip();
		delete this->dvdRipper;

		this->imRippinIt = false;
		this->reset();

		this->keepPolling = true;
		this->drivesList = Li::Scsi::OpticalDrive::ListOpticalDrives();
		this->pollDrives = new std::thread(&DumpDVD::pollDrivesThread, this);
	}

	

	void DumpDVD::showRippingStatus() {
		ImGui::SeparatorText("Status");
		ImGui::Text("Copying Disc ... ");
		ImGui::SameLine();
		ImGui::ProgressBar(this->dvdRipper->PercentDone());

		ImGui::Text("Sector %i / %i", this->dvdRipper->SectorsReadSoFar(), this->GetCurrentSelectedDrive()->Sectors());		

		uint64_t szSoFar = (uint64_t)(this->dvdRipper->SectorsReadSoFar()) * (uint64_t)(DVDCSS_BLOCK_SIZE);
		uint64_t szTotal = (uint64_t)(this->GetCurrentSelectedDrive()->Sectors()) * (uint64_t)(DVDCSS_BLOCK_SIZE);

		ImGui::Text("Total %s / %s", Utils::HumanReadableByteStr(szSoFar).c_str(), Utils::HumanReadableByteStr(szTotal).c_str());
		
		ImGui::SeparatorText("Copy Protection");

		if (this->GetCurrentSelectedDrive()->HasCss()) {
			ImGui::Text("Copy Protection: \"Content Scrambling System\" (CSS)");
			ImGui::Text("Decrypt Enabled? %s", this->decrypt ? "Yes" : "No");

			if (this->decrypt) {
				std::byte* discKey = this->dvdRipper->GetDiscKey();
				std::byte* titleKey = this->dvdRipper->GetTitleKey();
				
				if (discKey != nullptr)
					ImGui::Text("Disc Key: %02X%02X%02X%02X%02X", discKey[0], discKey[1], discKey[2], discKey[3], discKey[4]);
				
				if (titleKey != nullptr)
					ImGui::Text("Title Key: %02X%02X%02X%02X%02X", titleKey[0], titleKey[1], titleKey[2], titleKey[3], titleKey[4]);
			}
		}
		else {
			ImGui::Text("Copy Protection: None Detected.");
		}
		ImGui::SeparatorText("Output");
		ImGui::Text("Output file: \"%s\"", this->outputFile);

		if (ImGui::Button("Cancel")) {
			this->endRipAndGoBackToMenu();
			std::filesystem::remove(std::string(this->outputFile));
			return;
		}

		if (this->dvdRipper->Done()) {
			if (!this->dvdRipper->Error()) {
				ImGui::Begin("Complete", &this->imRippinIt, ImGuiWindowFlags_NoCollapse);
				ImGui::Text("ISO file was created successfully.");
				if (ImGui::Button("OK")) {
					this->endRipAndGoBackToMenu();
				}
				ImGui::End();
			}
			else {
				ImGui::Begin("Error", &this->imRippinIt, ImGuiWindowFlags_NoCollapse);
				ImGui::Text("Error occured when creating an ISO\n\"%s\"", this->dvdRipper->ErrorMessage().c_str());
				if (ImGui::Button("OK")) {
					this->endRipAndGoBackToMenu();
					std::filesystem::remove(std::string(this->outputFile));
				}
				ImGui::End();
			}
		}

		
	}

	void DumpDVD::showRipMenu() {
		this->lock->lock();
		
		ImGui::SeparatorText("Input");

		ImGui::Text("Drive: ");
		ImGui::SameLine();
		ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
		if (ImGui::Combo("##driveList", &this->selectedDrive, this->getDrivesStr().c_str())) {
			this->reset();
		}

		if (!GetCurrentSelectedDrive()->DiscInDrive()) {
			ImGui::Text("Put a disc in the drive to continue ...");
			this->discInserted = false;
		}
		else {
			if (!this->discInserted) {
				this->discInserted = true;
				this->reset();
			}
			int numDrvSpeeds = GetCurrentSelectedDrive()->SupportedSpeeds()->size() + 1;

			std::string driveSpeedName = "Unknown";
			if (this->selectedDriveSpeed >= 0 && this->selectedDriveSpeed < GetCurrentSelectedDrive()->SupportedSpeeds()->size()) {
				uint32_t speed = GetCurrentSelectedDrive()->SupportedSpeeds()->at(this->selectedDriveSpeed);
				float spdFriendly = ((float)speed / (float)1352.0);

				std::ostringstream xAmt;
				xAmt.precision(1);
				xAmt << std::fixed << spdFriendly;

				driveSpeedName = xAmt.str() + "x (" + std::to_string(speed) + " kbps)";
			}
			else if (this->selectedDriveSpeed == GetCurrentSelectedDrive()->SupportedSpeeds()->size()) {
				driveSpeedName = "As Fast as Possible";
			}


			ImGui::Text("Read Speed: ");
			ImGui::SameLine();
			ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
			ImGui::SliderInt("##readSpeed", &this->selectedDriveSpeed, 0, numDrvSpeeds - 1, driveSpeedName.c_str());

			ImGui::Text("Buffer Size (in sectors): ");
			ImGui::SameLine();
			ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
			ImGui::InputInt("##bufferSize", &this->sectorsAtOnce, 1, 100);
			if (this->sectorsAtOnce <= 0) this->sectorsAtOnce = 1;

			ImGui::SeparatorText("Copy Protection");
			if (this->GetCurrentSelectedDrive()->HasCss()) {
				ImGui::Text("Copy Protection: \"Content Scrambling System\" (CSS)");
				ImGui::Checkbox("Decrypt/Remove CSS", &this->decrypt);
			}
			else {
				ImGui::Text("Copy Protection: None Detected.\n(This doesn't completely confirm there isn't any ...)");
			}

			ImGui::SeparatorText("Output");

			ImGui::Text("Output file: ");
			ImGui::SameLine();
			ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60);
			ImGui::InputTextWithHint("##outputIso", "Output ISO file path", this->outputFile, IM_ARRAYSIZE(DumpDVD::outputFile));
			ImGui::SameLine();
			if (ImGui::Button("Browse")) {
				auto sav = pfd::save_file("Save ISO", std::string(this->outputFile), { "ISO9660 Image Files (.iso)", "*.iso" }, pfd::opt::force_overwrite);
				strncpy(this->outputFile, sav.result().c_str(), sizeof(DumpDVD::outputFile) - 1);
			}

			if (ImGui::Button("Create ISO", ImVec2(ImGui::GetContentRegionAvail().x, 20))) {
				this->startRip();
			}
		}

		this->lock->unlock();
	}

	void DumpDVD::RenderUI() {
		if (this->drivesList == nullptr) return;

		const ImGuiViewport* mainViewport = ImGui::GetMainViewport();
		ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
		ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize, ImGuiCond_Always);

		ImGui::Begin("DVD Dumper", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove |
										 ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing);

		if (this->imRippinIt)
		{
			this->showRippingStatus();
		}
		else
		{
			this->showRipMenu();
		}
		

		ImGui::End();

#ifdef _DEBUG
		if (showDemoWindow)
			ImGui::ShowDemoWindow(&showDemoWindow);
#endif
		this->counter++;
	}

}