/*********************************************************************

	appldriv.c

	Apple 5.25" floppy drive emulation (to be interfaced with applefdc.c)

*********************************************************************/

#include "appldriv.h"
#include "devices/flopdrv.h"
#include "devices/mflopimg.h"
#include "formats/ap2_dsk.h"

#define APPLE525TAG	"apple525driv"

struct apple525_disk
{
	unsigned int state : 4;		/* bits 0-3 are the phase */
	unsigned int tween_tracks : 1;
	unsigned int track_loaded : 1;
	unsigned int track_dirty : 1;
	int position;
	int spin_count; 		/* simulate drive spin to fool RWTS test at $BD34 */
	UINT8 track_data[APPLE2_NIBBLE_SIZE * APPLE2_SECTOR_COUNT];
};

static const device_class parent_devclass = { floppy_device_getinfo, NULL };
static int apple525_enable_mask = 1;



void apple525_set_enable_lines(int enable_mask)
{
	apple525_enable_mask = enable_mask;
}



/* ----------------------------------------------------------------------- */

static void apple525_load_current_track(mess_image *image)
{
	int len;
	struct apple525_disk *disk;

	disk = (struct apple525_disk *) image_lookuptag(image, APPLE525TAG);
	len = sizeof(disk->track_data);

	floppy_drive_read_track_data_info_buffer(image, 0, disk->track_data, &len);
	disk->track_loaded = 1;
	disk->track_dirty = 0;
}



static void apple525_save_current_track(mess_image *image, int unload)
{
	int len;
	struct apple525_disk *disk;

	disk = (struct apple525_disk *) image_lookuptag(image, APPLE525TAG);

	if (disk->track_dirty)
	{
		len = sizeof(disk->track_data);
		floppy_drive_write_track_data_info_buffer(image, 0, disk->track_data, &len);
		disk->track_dirty = 0;
	}
	if (unload)
		disk->track_loaded = 0;
}



static void apple525_seek_disk(mess_image *img, struct apple525_disk *disk, signed int step)
{
	int track;
	int pseudo_track;

	apple525_save_current_track(img, FALSE);

	track = floppy_drive_get_current_track(img);
	pseudo_track = (track * 2) + disk->tween_tracks;

	pseudo_track += step;
	if (pseudo_track < 0)
		pseudo_track = 0;
	else if (pseudo_track/2 >= APPLE2_TRACK_COUNT)
		pseudo_track = APPLE2_TRACK_COUNT*2-1;

	if (pseudo_track/2 != track)
	{
		floppy_drive_seek(img, pseudo_track/2 - floppy_drive_get_current_track(img));
		disk->track_loaded = 0;
	}

	if (pseudo_track & 1)
		disk->tween_tracks = 1;
	else
		disk->tween_tracks = 0;
}



static void apple525_disk_set_lines(mess_image *image, UINT8 new_state)
{
	struct apple525_disk *cur_disk;
	UINT8 old_state;
	unsigned int phase;

	cur_disk = (struct apple525_disk *) image_lookuptag(image, APPLE525TAG);

	old_state = cur_disk->state;
	cur_disk->state = new_state;

	if ((new_state & 0x0F) > (old_state & 0x0F))
	{
		phase = 0;
		switch((old_state ^ new_state) & 0x0F)
		{
			case 1:	phase = 0; break;
			case 2:	phase = 1; break;
			case 4:	phase = 2; break;
			case 8:	phase = 3; break;
		}

		phase -= floppy_drive_get_current_track(image) * 2;
		if (cur_disk->tween_tracks)
			phase--;
		phase %= 4;

		switch(phase)
		{
			case 1:
				apple525_seek_disk(image, cur_disk, +1);
				break;
			case 3:
				apple525_seek_disk(image, cur_disk, -1);
				break;
		}
	}
}



void apple525_set_lines(UINT8 lines)
{
	int i, count;
	mess_image *image;

	count = device_count_tag(Machine->devices, APPLE525TAG);

	for (i = 0; i < count; i++)
	{
		if (apple525_enable_mask & (1 << i))
		{
			image = image_from_devtag_and_index(APPLE525TAG, i);
			if (image)
				apple525_disk_set_lines(image, lines);
		}
	}
}



/* reads/writes a byte; write_value is -1 for read only */
static UINT8 apple525_process_byte(mess_image *img, int write_value)
{
	UINT8 read_value;
	struct apple525_disk *disk;
	int spinfract_divisor;
	int spinfract_dividend;
	const struct IODevice *dev;

	disk = (struct apple525_disk *) image_lookuptag(img, APPLE525TAG);
	dev = image_device(img);
	spinfract_dividend = (int) device_get_info_int(&dev->devclass, DEVINFO_INT_APPLE525_SPINFRACT_DIVIDEND);
	spinfract_divisor = (int) device_get_info_int(&dev->devclass, DEVINFO_INT_APPLE525_SPINFRACT_DIVISOR);

	/* no image initialized for that drive ? */
	if (!image_exists(img))
		return 0xFF;

	/* check the spin count if reading*/
	if (write_value < 0)
	{
		disk->spin_count++;
		disk->spin_count %= spinfract_divisor;
		if (disk->spin_count >= spinfract_dividend)
			return 0x00;
	}

	/* load track if need be */
	if (disk->track_loaded == 0)
		apple525_load_current_track(img);

	/* perform the read */
	read_value = disk->track_data[disk->position];

	/* perform the write, if applicable */
	if (write_value >= 0)
	{
		disk->track_data[disk->position] = write_value;
		disk->track_dirty = 1;
	}

	disk->position++;
	disk->position %= (sizeof(disk->track_data) / sizeof(disk->track_data[0]));

	/* when writing; save the current track after every full sector write */
	if ((write_value >= 0) && ((disk->position % APPLE2_NIBBLE_SIZE) == 0))
		apple525_save_current_track(img, FALSE);

	return read_value;
}



static mess_image *apple525_selected_image(void)
{
	int i, count;

	count = device_count_tag(Machine->devices, APPLE525TAG);

	for (i = 0; i < count; i++)
	{
		if (apple525_enable_mask & (1 << i))
			return image_from_devtag_and_index(APPLE525TAG, i);
	}
	return NULL;
}



UINT8 apple525_read_data(void)
{
	mess_image *image;
	image = apple525_selected_image();
	return image ? apple525_process_byte(image, -1) : 0xFF;
}



void apple525_write_data(UINT8 data)
{
	mess_image *image;
	image = apple525_selected_image();
	if (image)
		apple525_process_byte(image, data);
}



int apple525_read_status(void)
{
	int i, count, result = 0;
	mess_image *image;

	count = device_count_tag(Machine->devices, APPLE525TAG);

	for (i = 0; i < count; i++)
	{
		if (apple525_enable_mask & (1 << i))
		{
			image = image_from_devtag_and_index(APPLE525TAG, i);
			if (image && !image_is_writable(image))
				result = 1;
		}
	}
	return result;
}



/* ----------------------------------------------------------------------- */

static int device_init_apple525_floppy(mess_image *image)
{
	device_init_handler parent_init;

	if (!image_alloctag(image, APPLE525TAG, sizeof(struct apple525_disk)))
		return INIT_FAIL;

	parent_init = (device_init_handler) device_get_info_fct(&parent_devclass, DEVINFO_PTR_INIT);
	return parent_init(image);
}



static int device_load_apple525_floppy(mess_image *image)
{
	int result;
	device_load_handler parent_load;

	parent_load = (device_load_handler) device_get_info_fct(&parent_devclass, DEVINFO_PTR_LOAD);
	result = parent_load(image);

	floppy_drive_seek(image, -999);
	floppy_drive_seek(image, +35/2);
	return result;
}



static void device_unload_apple525_floppy(mess_image *image)
{
	device_unload_handler parent_unload;

	apple525_save_current_track(image, TRUE);

	parent_unload = (device_unload_handler) device_get_info_fct(&parent_devclass, DEVINFO_PTR_UNLOAD);
	parent_unload(image);
}



void apple525_device_getinfo(const device_class *devclass, UINT32 state, union devinfo *info)
{
	switch(state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_COUNT:				info->i = 2; break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_DEV_TAG:			strcpy(info->s = device_temp_str(), APPLE525TAG); break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_PTR_INIT:				info->init = device_init_apple525_floppy; break;
		case DEVINFO_PTR_LOAD:				info->load = device_load_apple525_floppy; break;
		case DEVINFO_PTR_UNLOAD:			info->unload = device_unload_apple525_floppy; break;
		case DEVINFO_PTR_FLOPPY_OPTIONS:	info->p = (void *) floppyoptions_apple2; break;

		default: floppy_device_getinfo(devclass, state, info); break;
	}
}
