/*
   AtrSIOHandler.C - handles SIO commands, the image data is stored
   in an AtrImage object

   Copyright (C) 2002 Matthias Reichl <hias@horus.com>

   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 <string.h>
#include <stdio.h>
#include "AtrSIOHandler.h"
#include "AtariDebug.h"

/*
 * speed definition bytes for the Atari:
 * the default for ~19200 bit/sec is 40.
 * ~57600 bit/sec uses a value of 8.
 */

#define SPEED_BYTE_19200 40
#define SPEED_BYTE_38400 16
#define SPEED_BYTE_57600 8

AtrSIOHandler::AtrSIOHandler(AtrImage* image, bool takeOwnership)
	: fImage(image),
	  fImTheOwner(takeOwnership),
	  fEnableHighSpeed(false),
	  fSpeedByte(SPEED_BYTE_57600)
{
	if (fImage) {
		fImageConfig = fImage->GetImageConfig();
		fFormatConfig = fImageConfig;
	}
	fTracer = SIOTracer::GetInstance();
}

AtrSIOHandler::~AtrSIOHandler()
{
	if (fImage && fImTheOwner) {
		delete fImage;
		fImage = 0;
	}
}

int AtrSIOHandler::ProcessCommandFrame(SIO_command_frame& frame, SIOWrapper* wrapper)
{
	int ret=0, ret2;

	if (!fImage) {
		return AbstractSIOHandler::eImageError;
	}

	fTracer->TraceCommandFrame(frame);

	switch (frame.command) {
	case 0x53: {
		/* get status */

		if ((ret=wrapper->SendCommandACK())) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[4];
		buf[0] = 0;
		if (fImageConfig.fSectorLength == e128BytesPerSector) {
			if (fImageConfig.fNumberOfSectors==1040) {
				buf[0] |= 0x80; /* enhanced density */
			}
		} else {
			buf[0] |= 0x20; /* double density */
		}
		buf[1] = 0x40;
		buf[2] = 0xe0;
		buf[3] = 0;

		if (fImage->IsWriteProtected()) {
			buf[0] |= 0x08;
			buf[1] &= ~0x40;
		}

		fTracer->TraceDataBlock(4,buf,"[ get status ] ");

		if ((ret=wrapper->SendComplete())) {
			DPRINTF("send command complete failed\n");
			break;
		}

		if ((ret=wrapper->SendDataFrame(buf,4))) {
			DPRINTF("send data frame failed\n");
			break;
		}

		break;
	}
	case 0x52: {
		/* read sector */
		size_t sec = frame.aux1 + (frame.aux2<<8);
		if ( sec<=0 || sec > fImageConfig.fNumberOfSectors) {
			ret = AbstractSIOHandler::eIllegalSectorNumber;
			DPRINTF("illegal sector number %d\n",sec);

			if (wrapper->SendCommandNAK()) {
				DPRINTF("send command NAK failed\n");
				break;
			}
			break;
		}

		if ((ret=wrapper->SendCommandACK())) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[256];

		size_t len = fImageConfig.GetSectorLength(sec);

		if ((ret=fImage->ReadSector(sec, buf, len))) {
			ret = AbstractSIOHandler::eImageError;
			DPRINTF("read sector from image failed\n");
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
		} else {
			if ((ret=wrapper->SendComplete())) {
				DPRINTF("send complete failed\n");
				break;
			}
		}

		fTracer->TraceDataBlock(len,buf,"[ read sector ] ");

		if ((ret2=wrapper->SendDataFrame(buf, len))) {
			DPRINTF("send data frame failed\n");
			if (ret==0) ret=ret2;
			break;
		}
		break;
	}
	case 0x50:
	case 0x57: {
		/* write sector */
		size_t sec = frame.aux1 + (frame.aux2<<8);
		if ( sec<=0 || sec > fImageConfig.fNumberOfSectors ) {
			ret = AbstractSIOHandler::eIllegalSectorNumber;
			DPRINTF("illegal sector number %d\n",sec);
			if (wrapper->SendCommandNAK()) {
				DPRINTF("send command NAK failed\n");
				break;
			}
			break;
		}

		if ((ret=wrapper->SendCommandACK())) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[256];

		size_t len = fImageConfig.GetSectorLength(sec);

		if ((ret=wrapper->ReceiveDataFrame(buf, len))) {
			DPRINTF("receive data frame failed\n");
			break;
		}

		fTracer->TraceDataBlock(len,buf,"[ write sector ] ");

		if (fImage->IsWriteProtected() ) {
			ret = AbstractSIOHandler::eWriteProtected;
			DPRINTF("sending ERROR - image is write protected\n");
			if (wrapper->SendError()) {
				DPRINTF("send complete failed\n");
				break;
			}
			break;
		} else {
			if ((ret=fImage->WriteSector(sec, buf, len))) {
				ret = AbstractSIOHandler::eImageError;
				DPRINTF("write sector to image failed\n");
				if (wrapper->SendError()) {
					DPRINTF("send error failed\n");
					break;
				}
				break;
			}
			if ((ret=wrapper->SendComplete())) {
				DPRINTF("send complete failed\n");
				break;
			}
		}
		break;
	}
	case 0x4e: {
		/* percom get */

		if ((ret=wrapper->SendCommandACK()) ) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[12];
		buf[0] = fFormatConfig.fTracksPerSide;
		buf[1] = 0;
		buf[2] = fFormatConfig.fSectorsPerTrack >> 8;
		buf[3] = fFormatConfig.fSectorsPerTrack & 0xff;
		if (fFormatConfig.fSides) {
			buf[4] = fFormatConfig.fSides - 1;
		} else {
			buf[4] = 0;
		}
		if (fFormatConfig.fDiskFormat == e90kDisk) {
			buf[5] = 0;
		} else {
			buf[5] = 4;
		}
		if (fFormatConfig.fSectorLength == e256BytesPerSector) {
			buf[6] = 1;
			buf[7] = 0;
		} else {
			buf[6] = 0;
			buf[7] = 128;
		}
		buf[8] = 1;
		buf[9] = 1;
		buf[10] = 0;
		buf[11] = 0;

		fTracer->TraceDataBlock(12, buf,"[ percom get ] ");
		fTracer->TraceDecodedPercomBlock(buf,"");

		if ((ret=wrapper->SendComplete())) {
			DPRINTF("send complete failed\n");
			break;
		}

		if ((ret=wrapper->SendDataFrame(buf, 12))) {
			DPRINTF("send data frame failed\n");
			break;
		}

		break;
	}
	case 0x4f: {
		/* percom put */

		if ((ret=wrapper->SendCommandACK()) ) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[12];

		if ((ret=wrapper->ReceiveDataFrame(buf, 12)) ) {
			DPRINTF("receive data frame failed\n");
			break;
		}

		fTracer->TraceDataBlock(12, buf,"[ percom put ] ");
		fTracer->TraceDecodedPercomBlock(buf,"");

		size_t sec, sides, total, secLen;

		sec = buf[3] + (buf[2] << 8);
		sides = buf[4] + 1;
		secLen = buf[7] + (buf[6]<<8);

		total = buf[0] * sec * sides;
		if (total > 0 && total < 65536 &&
		    (secLen == 128 || secLen == 256) ) {
			fFormatConfig.fTracksPerSide = buf[0];
			fFormatConfig.fSectorsPerTrack = sec;
			fFormatConfig.fSides = sides;
			if (secLen == 256) {
				fFormatConfig.fSectorLength = e256BytesPerSector;
			} else {
				fFormatConfig.fSectorLength = e128BytesPerSector;
			}

			fFormatConfig.fNumberOfSectors = total;
			fFormatConfig.DetermineDiskFormatFromLayout();

			switch (fFormatConfig.fDiskFormat) {
			case e90kDisk: DPRINTF("configured for 90k disk\n"); break;
			case e130kDisk: DPRINTF("configured for 130k disk\n"); break;
			case e180kDisk: DPRINTF("configured for 180k disk\n"); break;
			case e360kDisk: DPRINTF("configured for 360k disk\n"); break;
			default:
				DPRINTF("configured for %d sectors / %d bytes per sector\n",
					fFormatConfig.fNumberOfSectors,
					(int) fFormatConfig.fSectorLength);
				break;
			}

			if ((ret=wrapper->SendComplete()) ) {
				DPRINTF("send complete failed\n");
				break;
			}
		} else {
			ret = AbstractSIOHandler::eInvalidPercomConfig;
			DPRINTF("invalid config: %d sectors, %d bytes per sector\n",total,secLen);
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
		}

		break;

	}
	case 0x20: {
		/* format disk automatically (speedy 1050) */
		//DPRINTF("format automatic (0x20)\n");

		if (!fEnableHighSpeed) {
			if (wrapper->SendCommandNAK()) {
				DPRINTF("send command NAK failed\n");
			}
			ret = AbstractSIOHandler::eUnsupportedCommand;
			break;
		}
		if ((ret=wrapper->SendCommandACK()) ) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		if ( fImage->IsWriteProtected()) {
			ret = AbstractSIOHandler::eWriteProtected;
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
			break;
		}

		if (fImage->CreateImage(fFormatConfig.fSectorLength,
					fFormatConfig.fSectorsPerTrack,
					fFormatConfig.fTracksPerSide,
					fFormatConfig.fSides)) {
			ret = AbstractSIOHandler::eImageError;
			DPRINTF("create image failed [%d %d]\n",
				fFormatConfig.fSectorLength, fFormatConfig.fNumberOfSectors);
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
			break;
		} else {
			fImageConfig = fImage->GetImageConfig();
			fFormatConfig = fImageConfig;

			if ((ret=wrapper->SendComplete())) {
				DPRINTF("send complete failed\n");
				break;
			}
		}

		break;
	}

	case 0x21: {
		/* format disk */

		if ((ret=wrapper->SendCommandACK())) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[256];
		size_t len = (size_t) fFormatConfig.fSectorLength;

		if (fImage->IsWriteProtected()) {
			ret = AbstractSIOHandler::eWriteProtected;
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
			memset(buf,0,len);
		} else {
			if (fImage->CreateImage(fFormatConfig.fSectorLength,
						fFormatConfig.fSectorsPerTrack,
						fFormatConfig.fTracksPerSide,
						fFormatConfig.fSides)) {
				ret = AbstractSIOHandler::eImageError;
				DPRINTF("create image failed [%d %d]\n",
					fFormatConfig.fSectorLength, fFormatConfig.fNumberOfSectors);
				if (wrapper->SendError()) {
					DPRINTF("send error failed\n");
					break;
				}
				memset(buf,0,len);
			} else {
				fImageConfig = fImage->GetImageConfig();
				fFormatConfig = fImageConfig;

				if ((ret=wrapper->SendComplete())) {
					DPRINTF("send complete failed\n");
					break;
				}
				memset(buf,255,len);
			}
		}

		fTracer->TraceDataBlock(len, buf,"[ format disk ] ");

		if ((ret2=wrapper->SendDataFrame(buf, len))) {
			DPRINTF("send data frame failed\n");
			if (ret==0) ret=ret2;
			break;
		}

		break;
	}

	case 0x22: {
		/* format disk in enhanced density */
		//DPRINTF("format enhanced (0x22)\n");

		if ((ret=wrapper->SendCommandACK())) {
			DPRINTF("send command ACK failed\n");
			break;
		}

		unsigned char buf[128];
		if ( fImage->IsWriteProtected()) {
			ret = AbstractSIOHandler::eWriteProtected;
			if (wrapper->SendError()) {
				DPRINTF("send error failed\n");
				break;
			}
			memset(buf,0,128);
		} else {

			if (fImage->CreateImage(e130kDisk)) {
				ret = AbstractSIOHandler::eImageError;
				DPRINTF("create image 130k failed\n");
				if (wrapper->SendError()) {
					DPRINTF("send error failed\n");
					break;
				}
				memset(buf,0,128);
			} else {
				fImageConfig = fImage->GetImageConfig();
				fFormatConfig = fImageConfig;

				if ((ret=wrapper->SendComplete())) {
					DPRINTF("send complete failed\n");
					break;
				}
				memset(buf,255,128);
			}
		}

		fTracer->TraceDataBlock(128, buf,"[ format enhanced ] ");

		if ((ret2=wrapper->SendDataFrame(buf, 128))) {
			DPRINTF("send data frame failed\n");
			if (ret==0) ret=ret2;
			break;
		}

		break;
	}

	case 0x3f: {
		/* get ultra speed byte */

		if (fEnableHighSpeed) {

			if ((ret=wrapper->SendCommandACK())) {
				DPRINTF("send command ACK failed\n");
				break;
			}
	
			if ((ret=wrapper->SendComplete())) {
				DPRINTF("send complete failed\n");
				break;
			}

			fTracer->TraceDataBlock(1, &fSpeedByte,"[ get speed byte ] ");

			if ((ret=wrapper->SendDataFrame(&fSpeedByte, 1))) {
				DPRINTF("send data frame failed\n");
				break;
			}

		} else {
			if (wrapper->SendCommandNAK()) {
				DPRINTF("send command NAK failed\n");
			}
			ret = AbstractSIOHandler::eUnsupportedCommand;
		}

		break;
	}
	case 0x44: // configure drive
	case 0x4b: // slow/fast config
	case 0x51: // flush disks
	{
		// just ack these commands, but ignore them.

		if (fEnableHighSpeed) {

			if ((ret=wrapper->SendCommandACK())) {
				DPRINTF("send command ACK failed\n");
				break;
			}
	
			if ((ret=wrapper->SendComplete())) {
				DPRINTF("send complete failed\n");
				break;
			}

		} else {
			if (wrapper->SendCommandNAK()) {
				DPRINTF("send command NAK failed\n");
			}
			ret = AbstractSIOHandler::eUnsupportedCommand;
		}

		break;
	}
	default:
		if (wrapper->SendCommandNAK()) {
			DPRINTF("send command NAK failed\n");
		}
		ret = AbstractSIOHandler::eUnsupportedCommand;
		break;
	}
	if (ret == 0) {
		fTracer->TraceCommandOK();
	} else {
		fTracer->TraceCommandError(ret);
	}
	return ret;
}

bool AtrSIOHandler::IsAtrSIOHandler() const
{
	return true;
}

void AtrSIOHandler::EnableHighSpeed(bool on)
{
	fEnableHighSpeed = on;
/*
	if (on) {
		fSpeedByte = SPEED_BYTE_57600;
	} else {
		fSpeedByte = SPEED_BYTE_19200;
	}
*/
}
