/*
 *  1541job.cpp - Emulation of 1541 GCR disk reading/writing
 *
 *  Frodo (C) 1994-1997,2002-2005 Christian Bauer
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Notes:
 * ------
 *
 *  - This is only used for processor-level 1541 emulation.
 *    It simulates the 1541 disk controller hardware (R/W head,
 *    GCR reading/writing).
 *  - The preferences settings for drive 8 are used to
 *    specify the .d64 file
 *
 * Incompatibilities:
 * ------------------
 *
 *  - No GCR writing possible (WriteSector is a ROM patch)
 *  - Programs depending on the exact timing of head movement/disk
 *    rotation don't work
 *  - The .d64 error info is unused
 */

#include "sysdeps.h"

#include "1541job.h"
#include "CPU1541.h"
#include "Prefs.h"

#include "utils.hh"

// Number of tracks/sectors
const int NUM_TRACKS = 35;
const int NUM_SECTORS = 683;

// Size of GCR encoded data
const int GCR_SECTOR_SIZE = 1+10+9+1+325+8;			// SYNC Header Gap SYNC Data Gap (should be 5 SYNC bytes each)
const int GCR_TRACK_SIZE = GCR_SECTOR_SIZE * 21;	// Each track in gcr_data has 21 sectors
const int GCR_DISK_SIZE = GCR_TRACK_SIZE * NUM_TRACKS;

// Job return codes
const int RET_OK = 1;				// No error
const int RET_NOT_FOUND = 2;		// Block not found
const int RET_NOT_READY = 15;		// Drive not ready


// Number of sectors of each track
const int num_sectors[36] = {
	0,
	21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
	19,19,19,19,19,19,19,
	18,18,18,18,18,18,
	17,17,17,17,17
};

// Sector offset of start of track in .d64 file
const int sector_offset[36] = {
	0,
	0,21,42,63,84,105,126,147,168,189,210,231,252,273,294,315,336,
	357,376,395,414,433,452,471,
	490,508,526,544,562,580,
	598,615,632,649,666
};


/*
 *  Constructor: Open .d64 file if processor-level 1541
 *   emulation is enabled
 */

Job1541::Job1541(uint8 *ram1541) : ram(ram1541)
{
	the_file = NULL;

	gcr_data = gcr_ptr = gcr_track_start = new uint8[GCR_DISK_SIZE];
	gcr_track_end = gcr_track_start + GCR_TRACK_SIZE;
	current_halftrack = 2;

	disk_changed = true;

	if (ThePrefs.Emul1541Proc)
		open_d64_file(ThePrefs.DrivePath[0]);
}


/*
 *  Destructor: Close .d64 file
 */

Job1541::~Job1541()
{
	close_d64_file();
	delete[] gcr_data;
}


/*
 *  Preferences may have changed
 */

void Job1541::NewPrefs(Prefs *prefs)
{
	// 1541 emulation turned off?
	if (!prefs->Emul1541Proc)
		close_d64_file();

	// 1541 emulation turned on?
	else if (!ThePrefs.Emul1541Proc && prefs->Emul1541Proc)
		open_d64_file(prefs->DrivePath[0]);

	// .d64 file name changed?
	else if (strcmp(ThePrefs.DrivePath[0], prefs->DrivePath[0])) {
		close_d64_file();
		open_d64_file(prefs->DrivePath[0]);
		disk_changed = true;
	}
}


/*
 *  Open .d64 file
 */

void Job1541::open_d64_file(char *filepath)
{
	long size;
	uint8 magic[4];
	uint8 bam[256];

	// Clear GCR buffer
	memset(gcr_data, 0x55, GCR_DISK_SIZE);

	// Try opening the file for reading/writing first, then for reading only
	write_protected = false;
	the_file = fopen(filepath, "rb+");
	if (the_file == NULL) {
		write_protected = true;
		the_file = fopen(filepath, "rb");
	}
	if (the_file != NULL) {

		// Check length
		fseek(the_file, 0, SEEK_END);
		if ((size = ftell(the_file)) < NUM_SECTORS * 256) {
			fclose(the_file);
			the_file = NULL;
			return;
		}

		// x64 image?
		fseek(the_file, 0, SEEK_SET);
		if (fread(&magic, 4, 1, the_file) != 1)
			warning("Can't read all bytes\n");
		if (magic[0] == 0x43 && magic[1] == 0x15 && magic[2] == 0x41 && magic[3] == 0x64)
			image_header = 64;
		else
			image_header = 0;

		// Preset error info (all sectors no error)
		memset(error_info, 1, NUM_SECTORS);

		// Load sector error info from .d64 file, if present
		if (!image_header && size == NUM_SECTORS * 257) {
			fseek(the_file, NUM_SECTORS * 256, SEEK_SET);
			if (fread(&error_info, NUM_SECTORS, 1, the_file) != 1)
				warning("Can't read all bytes\n");
		};

		// Read BAM and get ID
		read_sector(18, 0, bam);
		id1 = bam[162];
		id2 = bam[163];

		// Create GCR encoded disk data from image
		disk2gcr();
	}
}


/*
 *  Close .d64 file
 */

void Job1541::close_d64_file(void)
{
	if (the_file != NULL) {
		fclose(the_file);
		the_file = NULL;
	}
}


/*
 *  Write sector to disk (1541 ROM patch)
 */

void Job1541::WriteSector(void)
{
	int track = ram[0x18];
	int sector = ram[0x19];
	uint16 buf = ram[0x30] | (ram[0x31] << 8);

	if (buf <= 0x0700)
		if (write_sector(track, sector, ram + buf))
			sector2gcr(track, sector);
}


/*
 *  Format one track (1541 ROM patch)
 */

void Job1541::FormatTrack(void)
{
	int track = ram[0x51];

	// Get new ID
	uint8 bufnum = ram[0x3d];
	id1 = ram[0x12 + bufnum];
	id2 = ram[0x13 + bufnum];

	// Create empty block
	uint8 buf[256];
	memset(buf, 1, 256);
	buf[0] = 0x4b;

	// Write block to all sectors on track
	for(int sector=0; sector<num_sectors[track]; sector++) {
		write_sector(track, sector, buf);
		sector2gcr(track, sector);
	}

	// Clear error info (all sectors no error)
	if (track == 35)
		memset(error_info, 1, NUM_SECTORS);
		// Write error_info to disk?
}


/*
 *  Read sector (256 bytes)
 *  true: success, false: error
 */

bool Job1541::read_sector(int track, int sector, uint8 *buffer)
{
	int offset;

	// Convert track/sector to byte offset in file
	if ((offset = offset_from_ts(track, sector)) < 0)
		return false;

#ifdef AMIGA
	if (offset != ftell(the_file))
		fseek(the_file, offset + image_header, SEEK_SET);
#else
	fseek(the_file, offset + image_header, SEEK_SET);
#endif
	if (fread(buffer, 256, 1, the_file) != 1)
		warning("Can't read all bytes\n");
	return true;
}


/*
 *  Write sector (256 bytes) !! -> GCR
 *  true: success, false: error
 */

bool Job1541::write_sector(int track, int sector, uint8 *buffer)
{
	int offset;

	// Convert track/sector to byte offset in file
	if ((offset = offset_from_ts(track, sector)) < 0)
		return false;

#ifdef AMIGA
	if (offset != ftell(the_file))
		fseek(the_file, offset + image_header, SEEK_SET);
#else
	fseek(the_file, offset + image_header, SEEK_SET);
#endif
	fwrite(buffer, 256, 1, the_file);
	return true;
}


/*
 *  Convert track/sector to offset
 */

int Job1541::secnum_from_ts(int track, int sector)
{
	return sector_offset[track] + sector;
}

int Job1541::offset_from_ts(int track, int sector)
{
	if ((track < 1) || (track > NUM_TRACKS) ||
		(sector < 0) || (sector >= num_sectors[track]))
		return -1;

	return (sector_offset[track] + sector) << 8;
}


/*
 *  Convert 4 bytes to 5 GCR encoded bytes
 */

const uint16 gcr_table[16] = {
	0x0a, 0x0b, 0x12, 0x13, 0x0e, 0x0f, 0x16, 0x17,
	0x09, 0x19, 0x1a, 0x1b, 0x0d, 0x1d, 0x1e, 0x15
};

void Job1541::gcr_conv4(uint8 *from, uint8 *to)
{
	uint16 g;

	g = (gcr_table[*from >> 4] << 5) | gcr_table[*from & 15];
	*to++ = g >> 2;
	*to = (g << 6) & 0xc0;
	from++;

	g = (gcr_table[*from >> 4] << 5) | gcr_table[*from & 15];
	*to++ |= (g >> 4) & 0x3f;
	*to = (g << 4) & 0xf0;
	from++;

	g = (gcr_table[*from >> 4] << 5) | gcr_table[*from & 15];
	*to++ |= (g >> 6) & 0x0f;
	*to = (g << 2) & 0xfc;
	from++;

	g = (gcr_table[*from >> 4] << 5) | gcr_table[*from & 15];
	*to++ |= (g >> 8) & 0x03;
	*to = g;
}


/*
 *  Create GCR encoded disk data from image
 */

void Job1541::sector2gcr(int track, int sector)
{
	uint8 block[256];
	uint8 buf[4];
	uint8 *p = gcr_data + (track-1) * GCR_TRACK_SIZE + sector * GCR_SECTOR_SIZE;

	read_sector(track, sector, block);

	// Create GCR header
	*p++ = 0xff;							// SYNC
	buf[0] = 0x08;							// Header mark
	buf[1] = sector ^ track ^ id2 ^ id1;	// Checksum
	buf[2] = sector;
	buf[3] = track;
	gcr_conv4(buf, p);
	buf[0] = id2;
	buf[1] = id1;
	buf[2] = 0x0f;
	buf[3] = 0x0f;
	gcr_conv4(buf, p+5);
	p += 10;
	memset(p, 0x55, 9);						// Gap
	p += 9;

	// Create GCR data
	uint8 sum;
	*p++ = 0xff;							// SYNC
	buf[0] = 0x07;							// Data mark
	sum = buf[1] = block[0];
	sum ^= buf[2] = block[1];
	sum ^= buf[3] = block[2];
	gcr_conv4(buf, p);
	p += 5;
	for (int i=3; i<255; i+=4) {
		sum ^= buf[0] = block[i];
		sum ^= buf[1] = block[i+1];
		sum ^= buf[2] = block[i+2];
		sum ^= buf[3] = block[i+3];
		gcr_conv4(buf, p);
		p += 5;
	}
	sum ^= buf[0] = block[255];
	buf[1] = sum;							// Checksum
	buf[2] = 0;
	buf[3] = 0;
	gcr_conv4(buf, p);
	p += 5;
	memset(p, 0x55, 8);						// Gap
}

void Job1541::disk2gcr(void)
{
	// Convert all tracks and sectors
	for (int track=1; track<=NUM_TRACKS; track++)
		for(int sector=0; sector<num_sectors[track]; sector++)
			sector2gcr(track, sector);
}


/*
 *  Move R/W head out (lower track numbers)
 */

void Job1541::MoveHeadOut(void)
{
	if (current_halftrack == 2)
		return;
	current_halftrack--;
#if !defined(__riscos__) && !defined(GEKKO)
	printf("Head move %d\n", current_halftrack);
#endif
	gcr_ptr = gcr_track_start = gcr_data + ((current_halftrack >> 1) - 1) * GCR_TRACK_SIZE;
	gcr_track_end = gcr_track_start + num_sectors[current_halftrack >> 1] * GCR_SECTOR_SIZE;
}


/*
 *  Move R/W head in (higher track numbers)
 */

void Job1541::MoveHeadIn(void)
{
	if (current_halftrack == NUM_TRACKS*2)
		return;
	current_halftrack++;
#if !defined(__riscos__) && !defined(GEKKO)
	printf("Head move %d\n", current_halftrack);
#endif
	gcr_ptr = gcr_track_start = gcr_data + ((current_halftrack >> 1) - 1) * GCR_TRACK_SIZE;
	gcr_track_end = gcr_track_start + num_sectors[current_halftrack >> 1] * GCR_SECTOR_SIZE;
}


/*
 *  Get state
 */

void Job1541::GetState(Job1541State *state)
{
	state->current_halftrack = current_halftrack;
	state->gcr_ptr = gcr_ptr - gcr_data;
	state->write_protected = write_protected;
	state->disk_changed = disk_changed;
}


/*
 *  Set state
 */

void Job1541::SetState(Job1541State *state)
{
	current_halftrack = state->current_halftrack;
	gcr_ptr = gcr_data + state->gcr_ptr;
	gcr_track_start = gcr_data + ((current_halftrack >> 1) - 1) * GCR_TRACK_SIZE;
	gcr_track_end = gcr_track_start + num_sectors[current_halftrack >> 1] * GCR_SECTOR_SIZE;
	write_protected = state->write_protected;
	disk_changed = state->disk_changed;
}
