//	Altirra - Atari 800/800XL/5200 emulator
//	Device emulation library - disk drive module
//	Copyright (C) 2009-2015 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program 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 this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdafx.h>
#include <vd2/system/binary.h>
#include <vd2/system/error.h>
#include <at/atcore/propertyset.h>
#include "diskdrive.h"

void ATCreateDeviceDiskDrive(const ATPropertySet& pset, IATDevice **dev) {
	vdrefptr<ATDeviceDiskDrive> p(new ATDeviceDiskDrive);

	*dev = p.release();
}

extern const ATDeviceDefinition g_ATDeviceDefDiskDrive = { "disk", "disk", L"Disk Drive", ATCreateDeviceDiskDrive };

ATDeviceDiskDrive::ATDeviceDiskDrive() {
}

ATDeviceDiskDrive::~ATDeviceDiskDrive() {
}

void *ATDeviceDiskDrive::AsInterface(uint32 iid) {
	switch(iid) {
		case IATDeviceSIO::kTypeID: return static_cast<IATDeviceSIO *>(this);
			break;
	}

	return ATDevice::AsInterface(iid);
}

void ATDeviceDiskDrive::GetDeviceInfo(ATDeviceInfo& info) {
	info.mpDef = &g_ATDeviceDefDiskDrive;
}

bool ATDeviceDiskDrive::SetSettings(const ATPropertySet& pset) {
	const wchar_t *path = pset.GetString("path", L"");

	mPath = path;
	return true;
}

void ATDeviceDiskDrive::Init() {
	mpDiskImage = ATLoadDiskImage(mPath.c_str());
}

void ATDeviceDiskDrive::Shutdown() {
	if (mpSIOMgr) {
		mpSIOMgr->RemoveDevice(this);
		mpSIOMgr = nullptr;
	}
}

void ATDeviceDiskDrive::InitSIO(IATDeviceSIOManager *mgr) {
	mpSIOMgr = mgr;

	mgr->AddDevice(this);
}

IATDeviceSIO::CmdResponse ATDeviceDiskDrive::OnSerialBeginCommand(const ATDeviceSIOCommand& cmd) {
	if (!cmd.mbStandardRate)
		return kCmdResponse_NotHandled;

	if (cmd.mDevice != 0x31)
		return kCmdResponse_NotHandled;

	switch(cmd.mCommand) {
		case 0x52:		// read sector
		{
			uint16 sector = VDReadUnalignedLEU16(cmd.mAUX);

			uint8 buf[128];
			if (!sector || sector > mpDiskImage->GetVirtualSectorCount())
				return kCmdResponse_Fail_NAK;

			try {
				mpDiskImage->ReadVirtualSector(sector - 1, buf, 128);
			} catch(const MyError&) {
				mpSIOMgr->BeginCommand();
				mpSIOMgr->SendACK();
				mpSIOMgr->SendError();
				mpSIOMgr->EndCommand();
				return kCmdResponse_Start;
			}


			mpSIOMgr->BeginCommand();
			mpSIOMgr->SendACK();
			mpSIOMgr->SendComplete();
			mpSIOMgr->SendData(buf, 128, true);
			mpSIOMgr->EndCommand();
			return kCmdResponse_Start;
		}

		case 0x53:		// status
		{
			mpSIOMgr->BeginCommand();
			mpSIOMgr->SendACK();
			mpSIOMgr->Delay(10000);
			mpSIOMgr->SendComplete();
			mpSIOMgr->Delay(10000);

			const uint8 status[4] = { 0x08, 0xFF, 0xE0, 0 };
			mpSIOMgr->SendData(status, 4, true);
			mpSIOMgr->EndCommand();
			return kCmdResponse_Start;
		}
	}

	// unrecognized command
	return kCmdResponse_Fail_NAK;
}

void ATDeviceDiskDrive::OnSerialAbortCommand() {
}

void ATDeviceDiskDrive::OnSerialReceiveComplete(uint32 id, const void *data, uint32 len, bool checksumOK) {
}

void ATDeviceDiskDrive::OnSerialFence(uint32 id) {
}

// Attempt to accelerate a command via SIOV intercept. This receives a superset
// of the command structure received by OnSerialBeginCommand() and is intended
// to allow a direct forward.
//
// This routine can also return the additional BypassAccel value, which means
// to abort acceleration and force usage of native SIO. It is used for requests
// that the device recognizes but which cannot be safely accelerated by any
// device.
IATDeviceSIO::CmdResponse ATDeviceDiskDrive::OnSerialAccelCommand(const ATDeviceSIORequest& request) {
	return OnSerialBeginCommand(request);
}
