#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include "DiskManager.h"
#include "AtrMemoryImage.h"
#include "AtrSIOHandler.h"

#include "AtariDebug.h"

DiskManager::DiskManager()
	: fSIOWrapper(),
	  fSIOManager(&fSIOWrapper)
{
	if (fSIOWrapper.SetSIOServerMode()) {
		printf("cannot init SIO server mode!\n");
		exit (1);
	}

	SetUseHighSpeed(true);
}

DiskManager::~DiskManager()
{
}

bool DiskManager::DriveInUse(int driveno) const
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}
	return fSIOManager.GetConstHandler(eSIODriveBase+driveno) != 0;
}

int DiskManager::LoadAtrMemoryImage(int driveno, const char* filename)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}

	if (DriveInUse(driveno)) {
		DPRINTF("already loaded image into D%d: - unload first\n",driveno);
		return 1;
	}

	char absPath[PATH_MAX];
	if (realpath(filename,absPath) == 0) {
		DPRINTF("get realpath failed\n");
		return 1;
	}

	AtrMemoryImage *img = new AtrMemoryImage;
	if (img->ReadImageFromFile(absPath)) {
		DPRINTF("cannot open \"%s\"\n", absPath);
		delete img;
		return 1;
	}

	AtrSIOHandler* handler = new AtrSIOHandler(img, true);
	handler->EnableHighSpeed(fUseHighSpeed);

	if (fSIOManager.RegisterHandler(eSIODriveBase+driveno, handler)) {
		DPRINTF("cannot register device %d!\n",eSIODriveBase+driveno);
		delete handler;
		return 1;
	}
	img->SetFilename(absPath);

	return 0;
}

int DiskManager::CreateAtrMemoryImage(int driveno, EDiskFormat format)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}

	if (DriveInUse(driveno)) {
		return 1;
	}

	AtrMemoryImage *img = new AtrMemoryImage;
	img->CreateImage(format);

	AtrSIOHandler* handler = new AtrSIOHandler(img, true);
	handler->EnableHighSpeed(fUseHighSpeed);

	if (fSIOManager.RegisterHandler(eSIODriveBase+driveno, handler)) {
		DPRINTF("cannot register device %d!\n",eSIODriveBase+driveno);
		delete handler;
		return 1;
	}

	return 0;
}

int DiskManager::CreateAtrMemoryImage(int driveno, ESectorLength density, size_t sectors)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}

	if (DriveInUse(driveno)) {
		return 1;
	}

	AtrMemoryImage *img = new AtrMemoryImage;
	img->CreateImage(density, sectors);

	AtrSIOHandler* handler = new AtrSIOHandler(img, true);
	handler->EnableHighSpeed(fUseHighSpeed);

	if (fSIOManager.RegisterHandler(eSIODriveBase+driveno, handler)) {
		DPRINTF("cannot register device %d!\n",eSIODriveBase+driveno);
		delete handler;
		return 1;
	}

	return 0;
}

int DiskManager::UnloadAtrImage(int driveno)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}

	if (DriveInUse(driveno)) {
		AbstractSIOHandler* handler = fSIOManager.GetHandler(eSIODriveBase+driveno);
		fSIOManager.UnregisterHandler(eSIODriveBase+driveno);
		delete(handler);
	}
	return 0;
}

int DiskManager::WriteBackImagesIfChanged()
{
	int i;
	int status=0;
	for (i=eMinDriveNumber;i<=eMaxDriveNumber;i++) {
		if (DriveInUse(i)) {
			AbstractSIOHandler* absHandler = fSIOManager.GetHandler(eSIODriveBase+i);
			if (absHandler->IsAtrSIOHandler()) {
				AtrSIOHandler* handler = static_cast<AtrSIOHandler*>(absHandler);
				if (handler->GetAtrImage()->IsAtrMemoryImage()) {
					AtrMemoryImage* img = static_cast<AtrMemoryImage*>(handler->GetAtrImage());
					if (img->GetFilename()) {
						if (img->Changed()) {
							if (img->WriteBackImageToFile()) {
								DPRINTF("writing \"%s\" failed\n", img->GetFilename());
								status=1;
							} else {
								DPRINTF("wrote \"%s\"\n",img->GetFilename());
							}
						}
					}
				} else {
					DPRINTF("writing non-AtrMemoryImage is not implemented yet!\n");
					status = 1;
				}
			} else {
				DPRINTF("writing non-AtrSioHandler is not implemented yet!\n");
				status = 1;
			}
		}
	}
	return status;
}

int DiskManager::WriteAtrMemoryImage(int driveno, const char* filename)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}
	if (!DriveInUse(driveno)) {
		DPRINTF("drive D%d: is empty\n",driveno);
		return 1;
	}

	if (!filename) {
		DPRINTF("filename is NULL!\n");
		return 1;
	}
	
	AbstractSIOHandler* absHandler = fSIOManager.GetHandler(eSIODriveBase+driveno);

	if (absHandler->IsAtrSIOHandler()) {
		AtrSIOHandler* handler = static_cast<AtrSIOHandler*>(absHandler);
		if (handler->GetAtrImage()->IsAtrMemoryImage()) {
			AtrMemoryImage* img = static_cast<AtrMemoryImage*>(handler->GetAtrImage());
			if (img->WriteImageToFile(filename)) {
				DPRINTF("writing \"%s\" failed\n",filename);
				return 1;
			} else {
				char absPath[PATH_MAX];
				if (realpath(filename,absPath) == 0) {
					DPRINTF("get realpath failed\n");
					return 1;
				}
				img->SetFilename(absPath);
			}
		} else {
			DPRINTF("writing non-AtrMemoryImage is not implemented yet!\n");
			return 1;
		}
	} else {
		DPRINTF("writing non-AtrSioHandler is not implemented yet!\n");
		return 1;
	}
	return 0;
}

int DiskManager::SetWriteProtectImage(int driveno, bool on)
{
	if (!DriveNumberOK(driveno)) {
		return 1;
	}
	if (!DriveInUse(driveno)) {
		DPRINTF("drive D%d: is empty\n",driveno);
		return 1;
	}

	AbstractSIOHandler* absHandler = fSIOManager.GetHandler(eSIODriveBase+driveno);

	if (absHandler->IsAtrSIOHandler()) {
		AtrSIOHandler* handler = static_cast<AtrSIOHandler*>(absHandler);
		handler->GetAtrImage()->SetWriteProtect(on);
	} else {
		DPRINTF("write (un)protecting non-AtrSioHandler is not implemented yet!\n");
		return 1;
	}
	return 0;
}

int DiskManager::ExchangeDrives(int drive1, int drive2)
{
	if (!DriveNumberOK(drive1) || !DriveNumberOK(drive2)) {
		return 1;
	}
	if (drive1 == drive2) {
		DPRINTF("exchanging a drive with itself is not allowed!\n");
		return 1;
	}
	AbstractSIOHandler* h1;
	AbstractSIOHandler* h2;
	h1 = fSIOManager.GetHandler(eSIODriveBase+drive1);
	h2 = fSIOManager.GetHandler(eSIODriveBase+drive2);
	fSIOManager.UnregisterHandler(eSIODriveBase+drive1);
	fSIOManager.UnregisterHandler(eSIODriveBase+drive2);
	fSIOManager.RegisterHandler(eSIODriveBase+drive1,h2);
	fSIOManager.RegisterHandler(eSIODriveBase+drive2,h1);
	return 0;
}

const char* DiskManager::GetImageFilename(int driveno) const
{
	if (!DriveNumberOK(driveno)) {
		return 0;
	}
	if (DriveInUse(driveno)) {
		const AbstractSIOHandler* absHandler = fSIOManager.GetConstHandler(eSIODriveBase+driveno);

		if (absHandler->IsAtrSIOHandler()) {
			const AtrSIOHandler* handler = static_cast<const AtrSIOHandler*>(absHandler);
			return handler->GetConstAtrImage()->GetFilename();
		}
	}
	return 0;
}

bool DiskManager::DriveIsWriteProtected(int driveno) const
{
	if (!DriveNumberOK(driveno)) {
		return false;
	}
	if (DriveInUse(driveno)) {
		const AbstractSIOHandler* absHandler = fSIOManager.GetConstHandler(eSIODriveBase+driveno);

		if (absHandler->IsAtrSIOHandler()) {
			const AtrSIOHandler* handler = static_cast<const AtrSIOHandler*>(absHandler);
			return handler->GetConstAtrImage()->IsWriteProtected();
		}
	}
	return false;
}

bool DiskManager::DriveIsChanged(int driveno) const
{
	if (!DriveNumberOK(driveno)) {
		return false;
	}
	if (DriveInUse(driveno)) {
		const AbstractSIOHandler* absHandler = fSIOManager.GetConstHandler(eSIODriveBase+driveno);

		if (absHandler->IsAtrSIOHandler()) {
			const AtrSIOHandler* handler = static_cast<const AtrSIOHandler*>(absHandler);
			const AtrImage* atrImage = handler->GetConstAtrImage();
			if (atrImage->IsAtrMemoryImage()) {
				const AtrMemoryImage* img = static_cast<const AtrMemoryImage*>(atrImage);
				return img->Changed();
			}
		}
	}
	return false;
}

size_t DiskManager::GetDriveImageSize(int driveno) const
{
	if (!DriveNumberOK(driveno)) {
		return 0;
	}
	if (DriveInUse(driveno)) {
		const AbstractSIOHandler* absHandler = fSIOManager.GetConstHandler(eSIODriveBase+driveno);

		if (absHandler->IsAtrSIOHandler()) {
			const AtrSIOHandler* handler = static_cast<const AtrSIOHandler*>(absHandler);
			return handler->GetConstAtrImage()->GetImageSize();
		}
	}
	return 0;
}

int DiskManager::DoServing(int otherReadPollDevice)
{
	return fSIOManager.DoServing(otherReadPollDevice);
}

bool DiskManager::DriveNumberOK(int driveno) const
{
	if (driveno < eMinDriveNumber || driveno > eMaxDriveNumber ) {
		DPRINTF("illegal drive number %d\n",driveno);
		return false;
	}
	return true;
}

int DiskManager::SetUseHighSpeed(bool on)
{
	fUseHighSpeed = on;

	if (fUseHighSpeed) {
		if (fSIOWrapper.SetBaudrate(57600)) {
			return 1;
		}
		if (fSIOWrapper.SetAutobaud(1)) {
			return 1;
		}
	} else {
		if (fSIOWrapper.SetBaudrate(19200)) {
			return 1;
		}
		if (fSIOWrapper.SetAutobaud(0)) {
			return 1;
		}
	}

	for (int i=eMinDriveNumber; i<=eMaxDriveNumber; i++) {
		if (DriveInUse(i)) {
			fSIOManager.GetHandler(eSIODriveBase+i)->EnableHighSpeed(on);
		}
	}
	return 0;
}
