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

#include <cstdio>
#include <cstring>
#include <string>

#include "Common/Common.h"
#include "Common/FileUtil.h"
#include "DiscIO/Blob.h"
#include "DiscIO/DriveBlob.h"

#ifdef _WIN32
#include "Common/StringUtil.h"
#endif

namespace DiscIO
{

DriveReader::DriveReader(const std::string& drive)
{
#ifdef _WIN32
	SectorReader::SetSectorSize(2048);
	auto const path = UTF8ToTStr(std::string("\\\\.\\") + drive);
	hDisc = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
						nullptr, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
	if (hDisc != INVALID_HANDLE_VALUE)
	{
		// Do a test read to make sure everything is OK, since it seems you can get
		// handles to empty drives.
		DWORD not_used;
		u8 *buffer = new u8[m_blocksize];
		if (!ReadFile(hDisc, buffer, m_blocksize, (LPDWORD)&not_used, nullptr))
		{
			delete [] buffer;
			// OK, something is wrong.
			CloseHandle(hDisc);
			hDisc = INVALID_HANDLE_VALUE;
			return;
		}
		delete [] buffer;

	#ifdef _LOCKDRIVE // Do we want to lock the drive?
		// Lock the compact disc in the CD-ROM drive to prevent accidental
		// removal while reading from it.
		pmrLockCDROM.PreventMediaRemoval = TRUE;
		DeviceIoControl(hDisc, IOCTL_CDROM_MEDIA_REMOVAL,
					&pmrLockCDROM, sizeof(pmrLockCDROM), nullptr,
					0, &dwNotUsed, nullptr);
	#endif
#else
	SectorReader::SetSectorSize(2048);
	file_.Open(drive, "rb");
	if (file_)
	{
#endif
	}
	else
	{
		NOTICE_LOG(DISCIO, "Load from DVD backup failed or no disc in drive %s", drive.c_str());
	}
}

DriveReader::~DriveReader()
{
#ifdef _WIN32
#ifdef _LOCKDRIVE // Do we want to lock the drive?
	// Unlock the disc in the CD-ROM drive.
	pmrLockCDROM.PreventMediaRemoval = FALSE;
	DeviceIoControl (hDisc, IOCTL_CDROM_MEDIA_REMOVAL,
		&pmrLockCDROM, sizeof(pmrLockCDROM), nullptr,
		0, &dwNotUsed, nullptr);
#endif
	if (hDisc != INVALID_HANDLE_VALUE)
	{
		CloseHandle(hDisc);
		hDisc = INVALID_HANDLE_VALUE;
	}
#else
	file_.Close();
#endif
}

DriveReader* DriveReader::Create(const std::string& drive)
{
	DriveReader* reader = new DriveReader(drive);

	if (!reader->IsOK())
	{
		delete reader;
		return nullptr;
	}

	return reader;
}

void DriveReader::GetBlock(u64 block_num, u8* out_ptr)
{
	u8* const lpSector = new u8[m_blocksize];
#ifdef _WIN32
	u32 NotUsed;
	u64 offset = m_blocksize * block_num;
	LONG off_low = (LONG)offset & 0xFFFFFFFF;
	LONG off_high = (LONG)(offset >> 32);
	SetFilePointer(hDisc, off_low, &off_high, FILE_BEGIN);
	if (!ReadFile(hDisc, lpSector, m_blocksize, (LPDWORD)&NotUsed, nullptr))
		PanicAlertT("Disc Read Error");
#else
	file_.Seek(m_blocksize * block_num, SEEK_SET);
	file_.ReadBytes(lpSector, m_blocksize);
#endif
	memcpy(out_ptr, lpSector, m_blocksize);
	delete[] lpSector;
}

bool DriveReader::ReadMultipleAlignedBlocks(u64 block_num, u64 num_blocks, u8* out_ptr)
{
#ifdef _WIN32
	u32 NotUsed;
	u64 offset = m_blocksize * block_num;
	LONG off_low = (LONG)offset & 0xFFFFFFFF;
	LONG off_high = (LONG)(offset >> 32);
	SetFilePointer(hDisc, off_low, &off_high, FILE_BEGIN);
	if (!ReadFile(hDisc, out_ptr, (DWORD)(m_blocksize * num_blocks), (LPDWORD)&NotUsed, nullptr))
	{
		PanicAlertT("Disc Read Error");
		return false;
	}
#else
	fseeko(file_.GetHandle(), m_blocksize*block_num, SEEK_SET);
	if (fread(out_ptr, 1, m_blocksize * num_blocks, file_.GetHandle()) != m_blocksize * num_blocks)
		return false;
#endif
	return true;
}

}  // namespace