/*
	SHARP MZ-800 Emulator 'EmuZ-800'
	SHARP MZ-1500 Emulator 'EmuZ-1500'
	SHARP MZ-2200 Emulator 'EmuZ-2200'

	Author : Takeda.Toshiya
	Date   : 2011.02.17-

	[ quick disk ]
*/

#include "quickdisk.h"
#include "../z80sio.h"

#define MZT_HEADER_SIZE	128
#define HEADER_SIZE	64

#define EVENT_RESTORE	0
#define EVENT_END	1

// 100usec
#define PERIOD_RESTORE	100
// 1sec
#define PERIOD_END	1000000

#define DATA_SYNC	0x16
#define DATA_MARK	0xa5
#define DATA_CRC	0xff
#define DATA_BREAK	0x100
#define DATA_EMPTY	0x101

#define HEADER_BLOCK_ID	0
#define DATA_BLOCK_ID	1

namespace MZ700 {

void QUICKDISK::initialize()
{
	insert = protect = false;
	home = true;
	first_data = send_break = true;
}

void QUICKDISK::release()
{
	release_disk();
}

void QUICKDISK::reset()
{
	wrga = mton = true;
	sync = false;
	motor_on = false;
	accessed = false;
	restore_id = end_id = -1;
	
	set_insert(insert);
	set_protect(protect);
	set_home(true);
}

/*
	PROTECT -> CTSA
		H: write protected
	INSERT -> DCDA
		L: inserted
	HOME -> DCDB
		L: reach to head position
		H: reset, reach to end of disk, DTRB is L->H

	RTSA -> WRGA
		L: write disk / stop motor at the end of disk
		H: read disk
	DTRB -> MTON
		H->L: start motor
		H: stop motor at the end of disk
*/

#define REGISTER_RESTORE_EVENT() { \
	if(restore_id == -1) { \
		register_event(this, EVENT_RESTORE, PERIOD_RESTORE, false, &restore_id); \
	} \
}

#define CANCEL_RESTORE_EVENT() { \
	if(restore_id != -1) { \
		cancel_event(this, restore_id); \
		restore_id = -1; \
	} \
}

#define REGISTER_END_EVENT() { \
	if(end_id != -1) { \
		cancel_event(this, end_id); \
	} \
	register_event(this, EVENT_END, PERIOD_END, false, &end_id); \
}

#define CANCEL_END_EVENT() { \
	if(end_id != -1) { \
		cancel_event(this, end_id); \
		end_id = -1; \
	} \
}

#define WRITE_BUFFER(v) { \
	if(buffer_ptr < QUICKDISK_BUFFER_SIZE) { \
		if(buffer[buffer_ptr] != v) { \
			buffer[buffer_ptr] = v; \
			modified = true; \
		} \
		buffer_ptr++; \
	} \
}

void QUICKDISK::write_signal(int id, uint32_t data, uint32_t mask)
{
	bool next = ((data & mask) != 0);
	
	if(id == QUICKDISK_SIO_RTSA) {
		if(wrga && !next) {
			// start to write
			first_data = true;
			write_ptr = 0;
		} else if(!wrga && next) {
			// end to write
			write_crc();
		}
		wrga = next;
	} else if(id == QUICKDISK_SIO_DTRB) {
		if(mton && !next) {
			// H->L: start motor
			if(motor_on && wrga) {
				// restart to send
				send_data();
				REGISTER_END_EVENT();
			} else {
				// start motor and restore to home position
				motor_on = true;
				REGISTER_RESTORE_EVENT();
				CANCEL_END_EVENT();
			}
		} else if(!mton && next) {
			// L->H: home signal is high
			set_home(true);
		}
		mton = next;
	} else if(id == QUICKDISK_SIO_SYNC) {
		// enter hunt/sync phase
		sync = next;
		if(sync) {
			// hack: start to send for verify
			if(!wrga) {
				write_crc();
				wrga = true;
			}
			send_data();
		}
	} else if(id == QUICKDISK_SIO_RXDONE) {
		// send next data
		send_data();
	} else if(id == QUICKDISK_SIO_DATA || id == QUICKDISK_SIO_BREAK) {
		// write data
		if(!(motor_on && !wrga)) {
			return;
		}
		if(id == QUICKDISK_SIO_DATA) {
			if(first_data) {
				// write sync chars at the top of message
				WRITE_BUFFER(DATA_SYNC);
				WRITE_BUFFER(DATA_SYNC);
				first_data = false;
			}
			WRITE_BUFFER(data);
			write_ptr = buffer_ptr;
		} else if(id == QUICKDISK_SIO_BREAK) {
			write_crc();
			WRITE_BUFFER(DATA_BREAK);
			first_data = true;
			write_ptr = 0;
		}
		accessed = true;
		
		if(buffer_ptr < QUICKDISK_BUFFER_SIZE) {
			REGISTER_END_EVENT();
		} else {
			CANCEL_END_EVENT();
			end_of_disk();
		}
	}
}

uint32_t QUICKDISK::read_signal(int ch)
{
	// access lamp signal
	if(accessed) {
		accessed = false;
		return 1;
	}
	return 0;
}

void QUICKDISK::event_callback(int event_id, int err)
{
	if(event_id == EVENT_RESTORE) {
		// reached to home position
		restore_id = -1;
		restore();
	} else if(event_id == EVENT_END) {
		// reached to end of disk
		end_id = -1;
		end_of_disk();
	}
}

void QUICKDISK::restore()
{
	// reached to home position
	set_home(false);
	buffer_ptr = 0;
	first_data = send_break = true;
	
	// start to send
	send_data();
}

void QUICKDISK::send_data()
{
	if(!(motor_on && wrga) || restore_id != -1) {
		return;
	}
retry:
	if(buffer_ptr < QUICKDISK_BUFFER_SIZE && buffer[buffer_ptr] != DATA_EMPTY) {
		if(buffer[buffer_ptr] == DATA_BREAK) {
			// send break signal
			if(send_break) {
				d_sio->write_signal(SIG_Z80SIO_BREAK_CH0, 1, 1);
				send_break = false;
			}
			// wait until sio enters hunt/sync phase
			if(!sync) {
				return;
			}
			buffer_ptr++;
			goto retry;
		}
		// send data
		d_sio->write_signal(SIG_Z80SIO_RECV_CH0, buffer[buffer_ptr++], 0xff);
		send_break = true;
		accessed = true;
		REGISTER_END_EVENT();
	} else {
		// reached to end of disk
		CANCEL_END_EVENT();
		end_of_disk();
	}
}

void QUICKDISK::write_crc()
{
	if(!wrga && write_ptr != 0) {
		buffer_ptr = write_ptr;
		
		WRITE_BUFFER(DATA_CRC);
		WRITE_BUFFER(DATA_CRC);
		WRITE_BUFFER(DATA_SYNC);
		WRITE_BUFFER(DATA_SYNC);
		// don't increment pointer !!!
		WRITE_BUFFER(DATA_BREAK);
		buffer_ptr--;
	}
	write_ptr = 0;
}

void QUICKDISK::end_of_disk()
{
	// write crc
	write_crc();
	
	// reached to end of disk
	if(mton || !wrga) {
		motor_on = false;
	} else {
		REGISTER_RESTORE_EVENT();
	}
	set_home(true);
}

void QUICKDISK::set_insert(bool val)
{
	// L=inserted
	d_sio->write_signal(SIG_Z80SIO_DCD_CH0, val ? 0 : 1, 1);
	insert = val;
}

void QUICKDISK::set_protect(bool val)
{
	// H=protected
	d_sio->write_signal(SIG_Z80SIO_CTS_CH0, val ? 1 : 0, 1);
	protect = val;
}

void QUICKDISK::set_home(bool val)
{
	if(home != val) {
		d_sio->write_signal(SIG_Z80SIO_DCD_CH1, val ? 1 : 0, 1);
		home = val;
	}
}

void QUICKDISK::open_disk(const _TCHAR* path)
{
	// check current disk image
	if(insert) {
		if(_tcsicmp(file_path, path) == 0) {
			return;
		}
		// close current disk
		close_disk();
	}
	memset(buffer, 0, sizeof(buffer));
	
	// load disk image
	FILEIO* fio = new FILEIO();
	if(fio->Fopen(path, FILEIO_READ_BINARY)) {
		my_tcscpy_s(file_path, _MAX_PATH, path);
		
		// clear buffer
		for(int i = 0; i < QUICKDISK_BUFFER_SIZE; i++) {
			buffer[i] = DATA_EMPTY;
		}
		buffer_ptr = 0;
		modified = false;
		
		// check extension
		if(check_file_extension(file_path, _T(".mzt")) || check_file_extension(file_path, _T(".q20"))) {
			// load mzt file
			fio->Fseek(0, FILEIO_SEEK_END);
			int remain = fio->Ftell();
			fio->Fseek(0, FILEIO_SEEK_SET);
			
			int num_block = 0;
			int block_num_ptr = 0;
			
			// create block file
			buffer[buffer_ptr++] = DATA_BREAK;
			buffer[buffer_ptr++] = DATA_SYNC;
			buffer[buffer_ptr++] = DATA_SYNC;
			buffer[buffer_ptr++] = DATA_MARK;
			block_num_ptr = buffer_ptr;
			buffer[buffer_ptr++] = 0; // block number
			buffer[buffer_ptr++] = DATA_CRC;
			buffer[buffer_ptr++] = DATA_CRC;
			buffer[buffer_ptr++] = DATA_SYNC;
			buffer[buffer_ptr++] = DATA_SYNC;
			buffer[buffer_ptr++] = DATA_BREAK;
			
			while(remain >= MZT_HEADER_SIZE) {
				// load header
				uint8_t header[MZT_HEADER_SIZE], ram[0x20000];
				fio->Fread(header, MZT_HEADER_SIZE, 1);
				remain -= MZT_HEADER_SIZE;
				
				// load data
				int size = header[0x12] | (header[0x13] << 8);
				int offs = header[0x14] | (header[0x15] << 8);
				memset(ram, 0, sizeof(ram));
				fio->Fread(ram + offs, size, 1);
				remain -= size;
#if 0
				// apply mz700win patch
				if(header[0x40] == 'P' && header[0x41] == 'A' && header[0x42] == 'T' && header[0x43] == ':') {
					int patch_ofs = 0x44;
					for(; patch_ofs < 0x80; ) {
						uint16_t patch_addr = header[patch_ofs] | (header[patch_ofs + 1] << 8);
						patch_ofs += 2;
						if(patch_addr == 0xffff) {
							break;
						}
						int patch_len = header[patch_ofs++];
						for(int i = 0; i < patch_len; i++) {
							ram[patch_addr + i] = header[patch_ofs++];
						}
					}
					// clear patch data
					for(int i = 0x40; i < patch_ofs; i++) {
						header[i] = 0;
					}
				}
#endif
				// copy header
				buffer[block_num_ptr] = ++num_block;
				
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_MARK;
				buffer[buffer_ptr++] = HEADER_BLOCK_ID;
				buffer[buffer_ptr++] = HEADER_SIZE;
				buffer[buffer_ptr++] = 0;
				buffer[buffer_ptr++] = header[0];	// attribute
				for(int i = 0; i < 17; i++) {
					buffer[buffer_ptr++] = header[i + 1]; // file name
				}
				buffer[buffer_ptr++] = header[0x3e];	// lock
				buffer[buffer_ptr++] = header[0x3f];	// secret
				buffer[buffer_ptr++] = header[0x12];	// file size
				buffer[buffer_ptr++] = header[0x13];
				buffer[buffer_ptr++] = header[0x14];	// load addr
				buffer[buffer_ptr++] = header[0x15];
				buffer[buffer_ptr++] = header[0x16];	// exec addr
				buffer[buffer_ptr++] = header[0x17];
				for(int i = 26; i < HEADER_SIZE; i++) {
					buffer[buffer_ptr++] = 0;	// comment
				}
				buffer[buffer_ptr++] = DATA_CRC;
				buffer[buffer_ptr++] = DATA_CRC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_BREAK;
				
				// copy data
				buffer[block_num_ptr] = ++num_block;
				
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_MARK;
				buffer[buffer_ptr++] = DATA_BLOCK_ID;
				buffer[buffer_ptr++] = (uint8_t)(size & 0xff);
				buffer[buffer_ptr++] = (uint8_t)(size >> 8);
				for(int i = 0; i < size; i++) {
					buffer[buffer_ptr++] = ram[offs + i];
				}
				buffer[buffer_ptr++] = DATA_CRC;
				buffer[buffer_ptr++] = DATA_CRC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_SYNC;
				buffer[buffer_ptr++] = DATA_BREAK;
			}
		} else {
			// check header
			uint8_t header[16];
			fio->Fread(header, sizeof(header), 1);
			if(memcmp(header, "-QD format-", 11) != 0) {
				fio->Fseek(0, FILEIO_SEEK_SET);
			}
			
			// load raw file
			bool in_gap = true;
			int sync_top_ptr = 0, sync_num = 0, sync_num_prev = 0, data;
			
			buffer[buffer_ptr++] = DATA_BREAK;
			
			while((data = fio->Fgetc()) != EOF) {
				if(data == DATA_SYNC) {
					if(sync_num == 0) {
						sync_top_ptr = buffer_ptr;
					}
					sync_num++;
				} else {
					sync_num_prev = sync_num;
					sync_num = 0;
				}
				if(in_gap) {
					if(sync_num_prev >= 4 && sync_num == 0) {
						buffer[buffer_ptr++] = DATA_SYNC;
						buffer[buffer_ptr++] = DATA_SYNC;
						buffer[buffer_ptr++] = data;
						in_gap = false;
					}
				} else {
					if(sync_num_prev >= 4 && sync_num == 0 && data == 0x00) {
						buffer_ptr = sync_top_ptr;
						buffer[buffer_ptr++] = DATA_SYNC;
						buffer[buffer_ptr++] = DATA_SYNC;
						buffer[buffer_ptr++] = DATA_BREAK;
						in_gap = true;
					} else {
						buffer[buffer_ptr++] = data;
					}
				}
			}
		}
		set_insert(true);
		set_protect(FILEIO::IsFileProtected(path));
		set_home(true);
		
		fio->Fclose();
	}
	delete fio;
}

void QUICKDISK::close_disk()
{
	release_disk();
	set_insert(false);
	set_protect(false);
	set_home(true);
	
	// cancel all events
	CANCEL_RESTORE_EVENT();
	CANCEL_END_EVENT();
}

void QUICKDISK::release_disk()
{
	if(insert && !protect && modified) {
		// check extension
		_TCHAR file_path_tmp[_MAX_PATH];
		if(check_file_extension(file_path, _T(".mzt")) || check_file_extension(file_path, _T(".q20"))) {
			my_tcscpy_s(file_path_tmp, _MAX_PATH, file_path);
		} else {
			my_stprintf_s(file_path_tmp, _MAX_PATH, _T("%s.mzt"), get_file_path_without_extensiton(file_path));
		}
		// save blocks as mzt file
		FILEIO* fio = new FILEIO();
		if(!fio->Fopen(file_path_tmp, FILEIO_WRITE_BINARY)) {
			fio->Fopen(create_local_path(_T("temporary_saved_quick_disk.mzt")), FILEIO_WRITE_BINARY);
		}
		if(fio->IsOpened()) {
			int block_num = buffer[4];
			buffer_ptr = 10;
			
			for(int i = 0; i < block_num; i++) {
				if(buffer[buffer_ptr] == DATA_EMPTY) {
					break;
				}
				int id = buffer[buffer_ptr + 3] & 3;
				int size = buffer[buffer_ptr + 4] | (buffer[buffer_ptr + 5] << 8);
				buffer_ptr += 6;
				
				if(id == HEADER_BLOCK_ID) {
					// create mzt header
					uint8_t header[MZT_HEADER_SIZE];
					memset(header, 0, sizeof(header));
					
					header[0x00] = (uint8_t)buffer[buffer_ptr + 0];	// attribute
					for(int i = 1; i <= 17; i++) {
						header[i] = (uint8_t)buffer[buffer_ptr + i];	// file name
					}
					header[0x3e] = (uint8_t)buffer[buffer_ptr + 18];	// lock
					header[0x3f] = (uint8_t)buffer[buffer_ptr + 19];	// lock
					header[0x12] = (uint8_t)buffer[buffer_ptr + 20];	// file size
					header[0x13] = (uint8_t)buffer[buffer_ptr + 21];
					header[0x14] = (uint8_t)buffer[buffer_ptr + 22];	// load addr
					header[0x15] = (uint8_t)buffer[buffer_ptr + 23];
					header[0x16] = (uint8_t)buffer[buffer_ptr + 24];	// exec addr
					header[0x17] = (uint8_t)buffer[buffer_ptr + 25];
					fio->Fwrite(header, MZT_HEADER_SIZE, 1);
				} else {
					// data
					for(int i = 0; i < size; i++) {
						fio->Fputc(buffer[buffer_ptr + i]);
					}
				}
				buffer_ptr += size + 5;
			}
			fio->Fclose();
		}
		delete fio;
	}
}

#define STATE_VERSION	1

bool QUICKDISK::process_state(FILEIO* state_fio, bool loading)
{
	if(!state_fio->StateCheckUint32(STATE_VERSION)) {
		return false;
	}
	if(!state_fio->StateCheckInt32(this_device_id)) {
		return false;
	}
	state_fio->StateArray(file_path, sizeof(file_path), 1);
	state_fio->StateValue(insert);
	state_fio->StateValue(protect);
	state_fio->StateValue(home);
	state_fio->StateValue(modified);
	state_fio->StateValue(accessed);
	state_fio->StateArray(buffer, sizeof(buffer), 1);
	state_fio->StateValue(buffer_ptr);
	state_fio->StateValue(write_ptr);
	state_fio->StateValue(first_data);
	state_fio->StateValue(send_break);
	state_fio->StateValue(wrga);
	state_fio->StateValue(mton);
	state_fio->StateValue(sync);
	state_fio->StateValue(motor_on);
	state_fio->StateValue(restore_id);
	state_fio->StateValue(end_id);
	return true;
}

}
