/*
   xfer.C - demo implementation of using the 1050-2-PC interface:
       transfer ATRs from/to a connected Atari disk drive

   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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <getopt.h>

#include "atarisio.h"
#include "AtrMemoryImage.h"
#include "SIOWrapper.h"

#define VERSION_STRING "0.01 beta"

static SIOWrapper* SIO = 0;

static size_t drive_no = 1;

int get_density(size_t& bytesPerSector, size_t& sectorsPerDisk)
{
	unsigned char buf[256];
	int result;


	printf("reading sector 1 ..."); fflush(stdout);
	result = SIO->ReadSector(drive_no, 1, buf, 128);
	if (result) {
		printf(" failed [error %d]\n", result);
	} else {
		printf(" OK\n");
	}

	/*
	 * try percom get
	 */

	printf("get percom block ..."); fflush(stdout);
	result = SIO->PercomGet(drive_no, buf);
	if (result) {
		printf(" failed [error %d]\n",result);
	} else {
		printf(" OK\n");

		bytesPerSector = buf[7] + 256*buf[6];
		if (bytesPerSector != 128 && bytesPerSector != 256) {
			return 1;
		}

		int secPerTrack = buf[3] + 256*buf[2];
		int sides = buf[4] + 1;
		int tracks = buf[0];
		
		sectorsPerDisk = secPerTrack * tracks * sides;	

		printf("[%d BytesPerSec, %d Sectors]\n", bytesPerSector, sectorsPerDisk);
		return 0;
	}

	/*
	 * if the drive doesn't support percom get/put, try to figure
	 * out the density by using the status command
	 */

	printf("get status ..."); fflush(stdout);
	result = SIO->GetStatus(drive_no, buf);
	if (result) {
		printf(" failed [error %d]\n",result);
		return 1;
	} else {
		printf(" OK\n");
		if (buf[0] & 0x80) {
			/* enhanced density */
			bytesPerSector = 128;
			sectorsPerDisk = 1040;
			return 0;
		}
		if (buf[0] & 0x20) {
			bytesPerSector = 256;
			sectorsPerDisk = 720;
			return 0;
		}
		bytesPerSector = 128;
		sectorsPerDisk = 720;
		return 0;
	}

	return 1;
}

int format_disk(EDiskFormat diskFormat)
{
	unsigned char buf[256];
	int result;
	int bytesPerSector;

	/*
	 * try to configure disk with percom get & put
	 */

	if (diskFormat != e90kDisk &&
	    diskFormat != e130kDisk &&
	    diskFormat != e180kDisk) {
		printf("unsupported disk format!\n");
		return 1;
	}

	printf("reading percom block\n");
	if (!(result = SIO->PercomGet(drive_no, buf)) ) {

		/*
		 * Drive seems to be percom-configurable, now
		 * try to set density for format command
		 */
		switch (diskFormat) {
		case e90kDisk:
			buf[0] = 40;
			buf[2] = 0;
			buf[3] = 18;
			buf[4] = 0;
			buf[5] = 0;
			buf[6] = 0;
			buf[7] = 128;
			bytesPerSector = 128;
			printf("single density\n");
			break;
		case e130kDisk:
			buf[0] = 40;
			buf[2] = 0;
			buf[3] = 26;
			buf[4] = 0;
			buf[5] = 4;
			buf[6] = 0;
			buf[7] = 128;
			bytesPerSector = 128;
			printf("enhanced density\n");
			break;
		case e180kDisk:
			buf[0] = 40;
			buf[2] = 0;
			buf[3] = 18;
			buf[4] = 0;
			buf[5] = 4;
			buf[6] = 1;
			buf[7] = 0;
			bytesPerSector = 256;
			printf("double density\n");
			break;
		default:
			printf("illegal disk format\n");
			return 1;
		}

		printf("writing new percom block\n");
		if ( !(result = SIO->PercomPut(drive_no, buf)) ) {
			printf("disk configured, starting formatting\n");

			if ( (result = SIO->FormatDisk(drive_no, buf, bytesPerSector)) ) {
				printf("error formatting disk [error %d]\n",result);
				return 1;
			} else {
				printf("disk formatting OK\n");
				return 0;
			}
		} else {
			printf("failed: [error %d]\n",result);
		}
	} else {
		printf("failed: [error %d]\n",result);
	}
	switch (diskFormat) {
	case e90kDisk:
		printf("using SD format command\n");
		result = SIO->FormatDisk(drive_no, buf, 128);
		break;
	case e130kDisk:
		printf("using ED format command\n");
		result = SIO->FormatEnhanced(drive_no, buf);
		break;
	case e180kDisk:
		printf("disk is not double-density capable!\n");
		return 1;
	default:
		printf("illegal disk format\n");
		return 1;
	}
	if ( result ) {
		printf("error formatting disk [error %d]\n", result);
		return 1;
	} else {
		printf("disk formatting OK\n");
		return 0;
	}
}

int read_atr(char* filename)
{
	AtrMemoryImage image;

	int result,length;
	size_t total_sectors, sector_length;
	size_t sec;

	bool OK = true;
	unsigned char buf[256];

	if (get_density(sector_length, total_sectors)) {
		printf("cannot determine density!\n");
		goto failure;
	}

	if (total_sectors == 720 && sector_length == 128) {
		printf("single density disk\n");
		image.CreateImage(e90kDisk);
	} else if (total_sectors == 1040 && sector_length == 128) {
		printf("enhanced density disk\n");
		image.CreateImage(e130kDisk);
	} else if (total_sectors == 720 && sector_length == 256) {
		printf("double density disk\n");
		image.CreateImage(e180kDisk);
	} else {
		printf("illegal disk format: %d sectors, %d bytes per sector\n",
				total_sectors, sector_length);
		goto failure;
	}
	
	printf("starting to read disk\n");

	for (sec=1;sec<=total_sectors;sec++) {
		printf("\b\b\b\b\b%5d",sec); fflush(stdout);
	
		length = 128;
		if ( (sector_length == 256) && (sec>3) ) {
			length = 256;
		}

		result = SIO->ReadSector(drive_no, sec, buf, length);

		if (result) {
			printf("\nReadSector failed [error %d]\n",result);
			OK = false;
			break;
		}
		if (image.WriteSector(sec, buf, length)) {
			printf("\nunable to write sector %d to atr image!\n",sec);
			OK = false;
			break;
		}
	}
	if (OK) {
		if (image.WriteImageToFile(filename)) {
			printf("\nwriting %s failed!\n", filename);
			OK = false;
		}
	}

	if (OK) {
		printf("\nsuccessfully created %s from disk\n", filename);
		return 0;
	}
failure:
	return 1;
}

int write_atr(char *filename)
{
	AtrMemoryImage image;

	if (image.ReadImageFromFile(filename)) {
		printf("error reading %s\n", filename);
		return 1;
	}

	int result,length;
	size_t total_sectors;
	size_t sec;
	EDiskFormat diskFormat;

	unsigned char buf[256];

	diskFormat = image.GetDiskFormat();
	total_sectors = image.GetNumberOfSectors();

	if (format_disk(diskFormat)) {
		goto failure;
	}

	printf("writing image to disk:\n");
	for (sec=1; sec <= total_sectors; sec++) {
		printf("\b\b\b\b\b%5d", sec); fflush(stdout);
		length = image.GetSectorLength(sec);
		if (length == 0) {
			printf("error getting sector length\n");
			return 1;
		}

		if (image.ReadSector(sec, buf, length)) {
			printf("\nerror accessing sector %d of image\n",sec);
			goto failure;
		}

		if ((result = SIO->WriteSector(drive_no, sec, buf, length)) ) {
			printf("\nerror writing sector %d to disk [error %d]\n", sec, result);
			goto failure;
		}
	}
	printf("\nwriting finished successfully!\n");

	return 0;

failure:
	return 1;
}

int main(int argc, char** argv)
{
	int c;

	int mode=-1; /* 0=read, 1=write */
	int finished = 0;
	int prosys = 0;
	int ret;

	printf("atarixfer V%s\n(c) 2002, 2003 by Matthias Reichl <hias@horus.com>\n\n",VERSION_STRING);
	while(!finished) {
		c = getopt(argc, argv, "prw");
		if (c == -1) {
			break;
		}
		switch (c) {
		case 'r':
			if (mode==-1) {
				mode = 0;
			} else {
				goto usage;
			}
			break;
		case 'w':
			if (mode==-1) {
				mode = 1;
			} else {
				goto usage;
			}
			break;
		case 'p':
			if (prosys == 0) {
				prosys = 1;
			} else {
				goto usage;
			}
			break;
		default:
			printf("forgot to catch option %d\n",c);
			break;
		}
	}
	if (optind + 1 != argc) {
		goto usage;
	}

	if (mode == 0 || mode ==1) {
		SIO = new SIOWrapper;
		if (prosys) {
			ret= SIO->SetCableType_APE_Prosystem();
		} else {
			ret = SIO->SetCableType_1050_2_PC();
		}

		if (ret) {
			printf("couldn't set %s mode\n", prosys ? "prosystem" : "1050-2-PC");
			delete SIO;
			return 1;
		}

		if (mode == 0) {
			return read_atr(argv[optind]);
		} else {
			return write_atr(argv[optind]);
		}
		delete SIO;
		return 0;
	} else {
		goto usage;
	}
usage:
	printf("usage: [-p] -r|-w atr-file\n\n");
	printf("  -p           use APE prosystem cable (default: 1050-2-PC cable)\n");
	printf("  -r atr-file  create ATR image of inserted disk\n");
	printf("  -w atr-file  write given ATR image to inserted disk\n");
	return 1;
}
