#include <stdlib.h>
#include "cdrom-image.h"
#include "dosbox/cdrom.h"

extern "C"
{
#include "ibm.h"
#include "ide.h"
}

char image_path[1024];
static int image_changed = 0;

extern ATAPI image_atapi;

CDROM_Interface_Image *cdrom = NULL;

#define MSFtoLBA(m, s, f)  (((((m*60)+s)*75)+f))

static uint32_t cdrom_capacity = 0;

enum {
	CD_STOPPED = 0,
	CD_PLAYING,
	CD_PAUSED
};

static int image_cd_state = CD_STOPPED;
static uint32_t image_cd_pos = 0, image_cd_end = 0;

#define BUF_SIZE 32768
static int16_t cd_buffer[BUF_SIZE];
static int cd_buflen = 0;

void image_audio_callback(int16_t *output, int len) {
	if (image_cd_state != CD_PLAYING)
		return;
	while (cd_buflen < len) {
		if (image_cd_pos < image_cd_end) {
//                      pclog("Read to %i\n", cd_buflen);
			if (!cdrom->ReadSector((unsigned char *)&cd_buffer[cd_buflen], true, image_cd_pos - 150)) {
//                                pclog("DeviceIoControl returned false\n");
				memset(&cd_buffer[cd_buflen], 0, (BUF_SIZE - cd_buflen) * 2);
				image_cd_state = CD_STOPPED;
				cd_buflen = len;
			} else {
//                                pclog("DeviceIoControl returned true\n");
				image_cd_pos++;
				cd_buflen += (RAW_SECTOR_SIZE / 2);
			}
		} else {
			memset(&cd_buffer[cd_buflen], 0, (BUF_SIZE - cd_buflen) * 2);
			image_cd_state = CD_STOPPED;
			cd_buflen = len;
		}
	}
	memcpy(output, cd_buffer, len * 2);
	memmove(cd_buffer, &cd_buffer[len], (BUF_SIZE - len) * 2);
	cd_buflen -= len;
}

void image_audio_stop() {
	image_cd_state = CD_STOPPED;
}

static int image_is_track_audio(uint32_t pos, int ismsf) {
	if (!cdrom)
		return 0;
	if (ismsf) {
		int m = (pos >> 16) & 0xff;
		int s = (pos >> 8) & 0xff;
		int f = pos & 0xff;
		pos = MSF_TO_FRAMES(m, s, f);
	}

	unsigned char attr;
	TMSF tmsf;
	int number;
	cdrom->GetAudioTrackInfo(cdrom->GetTrack(pos), number, tmsf, attr);

	return attr == AUDIO_TRACK;
}

static void image_playaudio(uint32_t pos, uint32_t len, int ismsf) {
	if (!cdrom)
		return;
	int number;
	unsigned char attr;
	TMSF tmsf;
	cdrom->GetAudioTrackInfo(cdrom->GetTrack(pos), number, tmsf, attr);
	if (attr == DATA_TRACK) {
		pclog("Can't play data track\n");
		image_cd_pos = 0;
		image_cd_state = CD_STOPPED;
		return;
	}
	pclog("Play audio - %08X %08X %i\n", pos, len, ismsf);
	if (ismsf) {
		int m, s, f;
		m = (pos >> 16) & 0xff;
		s = (pos >> 8) & 0xff;
		f = pos & 0xff;
		pos = MSFtoLBA(m, s, f);

		m = (len >> 16) & 0xff;
		s = (len >> 8) & 0xff;
		f = len & 0xff;
		len = MSFtoLBA(m, s, f);

		pclog("MSF - pos = %08X len = %08X\n", pos, len);
	} else
		len += pos;
	image_cd_pos = pos;// + 150;
	image_cd_end = len;// + 150;
	image_cd_state = CD_PLAYING;
	if (image_cd_pos < 150)
		image_cd_pos = 150;
	cd_buflen = 0;
	pclog("Audio start %08X %08X %i %i %i\n", image_cd_pos, image_cd_end, image_cd_state, cd_buflen, len);
}

static void image_pause(void) {
	if (!cdrom)
		return;
	if (image_cd_state == CD_PLAYING)
		image_cd_state = CD_PAUSED;
}

static void image_resume(void) {
	if (!cdrom)
		return;
	if (image_cd_state == CD_PAUSED)
		image_cd_state = CD_PLAYING;
}

static void image_stop(void) {
	if (!cdrom)
		return;
	image_cd_state = CD_STOPPED;
}

static void image_seek(uint32_t pos) {
	if (!cdrom)
		return;
	image_cd_pos = pos;
	image_cd_state = CD_STOPPED;
}

static int image_ready(void) {
	if (!cdrom)
		return 0;

	if (strlen(image_path) == 0)
		return 0;

	if (image_changed)
		image_changed = 0;

	return 1;
}

static int image_get_last_block(unsigned char starttrack, int msf, int maxlen, int single) {
	int c;
	uint32_t lb = 0;

	if (!cdrom)
		return 0;

	int first_track;
	int last_track;
	int number;
	unsigned char attr;
	TMSF tmsf;
	cdrom->GetAudioTracks(first_track, last_track, tmsf);

	for (c = 0; c <= last_track; c++) {
		uint32_t address;
		cdrom->GetAudioTrackInfo(c + 1, number, tmsf, attr);
		address = MSFtoLBA(tmsf.min, tmsf.sec, tmsf.fr) - 150;
		if (address > lb)
			lb = address;
	}
	return lb;
}

static int image_medium_changed(void) {
	if (!cdrom)
		return 0;

	if (strlen(image_path) == 0)
		return 0;

	if (old_cdrom_drive != cdrom_drive) {
		old_cdrom_drive = cdrom_drive;
		return 1;
	}

	if (image_changed) {
		image_changed = 0;
		return 1;
	}
	return 0;
}

static uint8_t image_getcurrentsubchannel(uint8_t *b, int msf) {
	if (!cdrom)
		return 0;
	uint8_t ret;
	int pos = 0;

	uint32_t cdpos = image_cd_pos;
	if (cdpos >= 150)
		cdpos -= 150;
	TMSF relPos, absPos;
	unsigned char attr, track, index;
	cdrom->GetAudioSub(cdpos, attr, track, index, relPos, absPos);

	if (image_cd_state == CD_PLAYING)
		ret = 0x11;
	else if (image_cd_state == CD_PAUSED)
		ret = 0x12;
	else
		ret = 0x13;

	b[pos++] = attr;
	b[pos++] = track;
	b[pos++] = index;

	if (msf) {
		uint32_t dat = MSFtoLBA(absPos.min, absPos.sec, absPos.fr);
		b[pos + 3] = (uint8_t)(dat % 75);
		dat /= 75;
		b[pos + 2] = (uint8_t)(dat % 60);
		dat /= 60;
		b[pos + 1] = (uint8_t)dat;
		b[pos] = 0;
		pos += 4;
		dat = MSFtoLBA(relPos.min, relPos.sec, relPos.fr);
		b[pos + 3] = (uint8_t)(dat % 75);
		dat /= 75;
		b[pos + 2] = (uint8_t)(dat % 60);
		dat /= 60;
		b[pos + 1] = (uint8_t)dat;
		b[pos] = 0;
		pos += 4;
	} else {
		uint32_t dat = MSFtoLBA(absPos.min, absPos.sec, absPos.fr);
		b[pos++] = (dat >> 24) & 0xff;
		b[pos++] = (dat >> 16) & 0xff;
		b[pos++] = (dat >> 8) & 0xff;
		b[pos++] = dat & 0xff;
		dat = MSFtoLBA(relPos.min, relPos.sec, relPos.fr);
		b[pos++] = (dat >> 24) & 0xff;
		b[pos++] = (dat >> 16) & 0xff;
		b[pos++] = (dat >> 8) & 0xff;
		b[pos++] = dat & 0xff;
	}

	return ret;
}

static void image_eject(void) {
}

static void image_load(void) {
}

static int image_readsector(uint8_t *b, int sector, int count) {
	if (!cdrom)
		return -1;
	return !cdrom->ReadSectors((PhysPt)b, false, sector, count);
}

static void image_readsector_raw(uint8_t *b, int sector) {
	if (!cdrom)
		return;
	cdrom->ReadSector(b, true, sector);
}

static int image_readtoc(unsigned char *b, unsigned char starttrack, int msf, int maxlen, int single) {
	if (!cdrom)
		return 0;
	int len = 4;
	int c, d;
	uint32_t temp;

	int first_track;
	int last_track;
	int number;
	unsigned char attr;
	TMSF tmsf;
	cdrom->GetAudioTracks(first_track, last_track, tmsf);

	b[2] = first_track;
	b[3] = last_track;

	d = 0;
	for (c = 0; c <= last_track; c++) {
		cdrom->GetAudioTrackInfo(c + 1, number, tmsf, attr);
		if (number >= starttrack) {
			d = c;
			break;
		}
	}
	cdrom->GetAudioTrackInfo(c + 1, number, tmsf, attr);
	b[2] = number;

	for (c = d; c <= last_track; c++) {
		if ((len + 8) > maxlen)
			break;
		cdrom->GetAudioTrackInfo(c + 1, number, tmsf, attr);

//                pclog("Len %i max %i Track %02X - %02X %02X %02i:%02i:%02i %08X\n",len,maxlen,toc[c].cdte_track,toc[c].cdte_adr,toc[c].cdte_ctrl,toc[c].cdte_addr.msf.minute, toc[c].cdte_addr.msf.second, toc[c].cdte_addr.msf.frame,MSFtoLBA(toc[c].cdte_addr.msf.minute, toc[c].cdte_addr.msf.second, toc[c].cdte_addr.msf.frame));
		b[len++] = 0; /* reserved */
		b[len++] = attr;
		b[len++] = number; /* track number */
		b[len++] = 0; /* reserved */

		if (msf) {
			b[len++] = 0;
			b[len++] = tmsf.min;
			b[len++] = tmsf.sec;
			b[len++] = tmsf.fr;
		} else {
			temp = MSFtoLBA(tmsf.min, tmsf.sec, tmsf.fr) - 150;
			b[len++] = temp >> 24;
			b[len++] = temp >> 16;
			b[len++] = temp >> 8;
			b[len++] = temp;
		}
		if (single)
			break;
	}
	b[0] = (uint8_t)(((len - 2) >> 8) & 0xff);
	b[1] = (uint8_t)((len - 2) & 0xff);
	/*
	pclog("Table of Contents:\n");
	pclog("First track - %02X\n", first_track);
	pclog("Last  track - %02X\n", last_track);
	for (c = 0; c <= last_track; c++)
	{
		cdrom->GetAudioTrackInfo(c+1, number, tmsf, attr);
		pclog("Track %02X - number %02X control %02X adr %02X address %02X %02X %02X %02X\n", c, number, attr, 0, 0, tmsf.min, tmsf.sec, tmsf.fr);
	}
	for (c = 0;c <= last_track; c++) {
		cdrom->GetAudioTrackInfo(c+1, number, tmsf, attr);
	    pclog("Track %02X - number %02X control %02X adr %02X address %06X\n", c, number, attr, 0, MSF_TO_FRAMES(tmsf.min, tmsf.sec, tmsf.fr));
	}
	*/
	return len;
}

static int image_readtoc_session(unsigned char *b, int msf, int maxlen) {
	if (!cdrom)
		return 0;
	int len = 4;

	int number;
	TMSF tmsf;
	unsigned char attr;
	cdrom->GetAudioTrackInfo(1, number, tmsf, attr);

//        pclog("Read TOC session - %i %02X %02X %i %i %02X %02X %02X\n",0, 0, 0,1,1,attr,0,number);

	b[2] = 1;
	b[3] = 1;
	b[len++] = 0; /* reserved */
	b[len++] = attr;
	b[len++] = number; /* track number */
	b[len++] = 0; /* reserved */
	if (msf) {
		b[len++] = 0;
		b[len++] = tmsf.min;
		b[len++] = tmsf.sec;
		b[len++] = tmsf.fr;
	} else {
		uint32_t temp = MSFtoLBA(tmsf.min, tmsf.sec, tmsf.fr) - 150;
		b[len++] = temp >> 24;
		b[len++] = temp >> 16;
		b[len++] = temp >> 8;
		b[len++] = temp;
	}

	return len;
}

static int image_readtoc_raw(unsigned char *b, int maxlen) {
	if (!cdrom)
		return 0;

	int track;
	int len = 4;

	int first_track;
	int last_track;
	int number;
	unsigned char attr;
	TMSF tmsf;
	cdrom->GetAudioTracks(first_track, last_track, tmsf);

	b[2] = first_track;
	b[3] = last_track;

	for (track = first_track; track <= last_track; track++) {
		if ((len + 11) > maxlen) {
			pclog("image_readtocraw: This iteration would fill the buffer beyond the bounds, aborting...\n");
			return len;
		}

		cdrom->GetAudioTrackInfo(track, number, tmsf, attr);

//              pclog("read_toc: Track %02X - number %02X control %02X adr %02X address %02X %02X %02X %02X\n", track, toc[track].cdte_track, toc[track].cdte_ctrl, toc[track].cdte_adr, 0, toc[track].cdte_addr.msf.minute, toc[track].cdte_addr.msf.second, toc[track].cdte_addr.msf.frame);

		b[len++] = track;
		b[len++] = attr;
		b[len++] = 0;
		b[len++] = 0;
		b[len++] = 0;
		b[len++] = 0;
		b[len++] = 0;
		b[len++] = 0;
		b[len++] = tmsf.min;
		b[len++] = tmsf.sec;
		b[len++] = tmsf.fr;
	}
	return len;
}

static uint32_t image_size() {
	return cdrom_capacity;
}

static int image_status() {
	if (!cdrom)
		return CD_STATUS_EMPTY;
	if (cdrom->HasAudioTracks()) {
		switch (image_cd_state) {
		case CD_PLAYING:return CD_STATUS_PLAYING;
		case CD_PAUSED:return CD_STATUS_PAUSED;
		case CD_STOPPED:
		default:return CD_STATUS_STOPPED;
		}
	}
	return CD_STATUS_DATA_ONLY;
}
void image_reset() {
}

void image_close(void) {
	image_cd_state = CD_STOPPED;
	if (cdrom) {
		delete cdrom;
		cdrom = NULL;
	}
//        memset(image_path, 0, 1024);
}

int image_open(char *fn) {
	if (strcmp(fn, image_path) != 0)
		image_changed = 1;

	/* Make sure image_changed stays when changing from an image to another image. */
	if (cdrom_drive != CDROM_IMAGE)
		image_changed = 1;
	/* strcpy fails on OSX if both parameters are pointing to the same address */
	if (image_path != fn)
		strcpy(image_path, fn);

	cdrom = new CDROM_Interface_Image();
	if (!cdrom->SetDevice(fn, false)) {
		image_close();
		return 1;
	}
	image_cd_state = CD_STOPPED;
	image_cd_pos = 0;
	cd_buflen = 0;
	cdrom_capacity = image_get_last_block(0, 0, 4096, 0) + 1;
	atapi = &image_atapi;
	return 0;
}

static void image_exit(void) {
}

ATAPI image_atapi =
	{
		image_ready,
		image_medium_changed,
		image_readtoc,
		image_readtoc_session,
		image_readtoc_raw,
		image_getcurrentsubchannel,
		image_readsector,
		image_readsector_raw,
		image_playaudio,
		image_seek,
		image_load,
		image_eject,
		image_pause,
		image_resume,
		image_size,
		image_status,
		image_is_track_audio,
		image_stop,
		image_exit
	};
