#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "ibm.h"
#include "ide.h"
#include "cdrom-ioctl.h"
#include <util.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOBSD.h>
#include <IOKit/storage/IOCDMedia.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDTypes.h>
#include <IOKit/storage/IOCDMediaBSDClient.h>
#include <IOKit/storage/IOMedia.h>
#include <errno.h>
static ATAPI ioctl_atapi;

int old_cdrom_drive;

static uint32_t cdrom_capacity = 0;
static int tocvalid = 0;
static int toc_tracks;
static int first_track, last_track;
static CDTOC *toc = NULL;
static CDDiscInfo disc_info;
static int track_addr[256];

static int msf_to_lba(CDMSF *msf)
{
        return msf->frame + (msf->second * 75) + (msf->minute * 60 * 75);
}

enum
{
    CD_STOPPED = 0,
    CD_PLAYING,
    CD_PAUSED
};

static int ioctl_cd_state = CD_STOPPED;
static uint32_t ioctl_cd_pos = 0, ioctl_cd_end = 0;
#define BUF_SIZE 32768
static int16_t cd_buffer[BUF_SIZE];
static int cd_buflen = 0;
static int cd_drive = -1;

static int cd_open()
{
        char s[80];
  
        if (cd_drive < 0)
                return 0;
        
        sprintf(s, "disk%i", cd_drive);
        return opendev(s, O_RDONLY, 0, NULL);
}
static void cd_close(int fd)
{
        close(fd);
}

void ioctl_audio_callback(int16_t *output, int len)
{
        int ioctl_fd;
        
//        pclog("Audio callback %08X %08X %i %i %i %04X %i\n", ioctl_cd_pos, ioctl_cd_end, ioctl_cd_state, cd_buflen, len, cd_buffer[4], GetTickCount());
        if (ioctl_cd_state != CD_PLAYING) 
        {
                memset(output, 0, len * 2);
                return;
        }

        ioctl_fd = cd_open();
	if (ioctl_fd <= 0)
        {
                memset(output, 0, len * 2);
                return;
        }

        while (cd_buflen < len)
        {
                if (ioctl_cd_pos < ioctl_cd_end)
                {
                        dk_cd_read_t cd_read;
        
                        bzero(&cd_read, sizeof(cd_read));
        
                        cd_read.offset = (ioctl_cd_pos-150)*kCDSectorSizeCDDA;
                        cd_read.buffer = &cd_buffer[cd_buflen];
                        cd_read.bufferLength = kCDSectorSizeCDDA;
                        cd_read.sectorType = kCDSectorTypeCDDA;
                        cd_read.sectorArea = kCDSectorAreaUser;
                        //pclog("CDDA read %llx %d %d\n", cd_read.offset, cd_read.bufferLength, ioctl_cd_pos);
        
                        if (ioctl(ioctl_fd, DKIOCCDREAD, &cd_read) < 0)
        		{
                                //pclog("DeviceIoControl returned false %d\n", errno);
                                memset(&cd_buffer[cd_buflen], 0, (BUF_SIZE - cd_buflen) * 2);
                                ioctl_cd_state = CD_STOPPED;
                                cd_buflen = len;
                        }
                        else
                        {
                                //pclog("DeviceIoControl returned true\n");
                                ioctl_cd_pos++;
                                cd_buflen += (2352 / 2);
                        }
                }
                else
                {
                        memset(&cd_buffer[cd_buflen], 0, (BUF_SIZE - cd_buflen) * 2);
                        ioctl_cd_state = CD_STOPPED;
                        cd_buflen = len;
                }
        }
        
        cd_close(ioctl_fd);

        memcpy(output, cd_buffer, len * 2);
//        for (c = 0; c < BUF_SIZE - len; c++)
//            cd_buffer[c] = cd_buffer[c + cd_buflen];
        memcpy(&cd_buffer[0], &cd_buffer[len], (BUF_SIZE - len) * 2);
        cd_buflen -= len;
//        pclog("Done %i\n", GetTickCount());
}

void ioctl_audio_stop()
{
	ioctl_cd_state = CD_STOPPED;
}

static int get_track_nr(uint32_t pos)
{
        int c;
        int track = 0;
        
        if (!tocvalid)
                return 0;

        for (c = first_track; c < last_track; c++)
        {
                uint32_t track_address = toc->descriptors[c].p.frame +
                                         (toc->descriptors[c].p.second * 75) +
                                         (toc->descriptors[c].p.minute * 75 * 60);
//pclog("get_track_nr: track=%i pos=%x track_address=%x\n", c, pos, track_address);
                if (track_address <= pos)
                        track = c;
        }
        return track;
}

static int is_track_audio(uint32_t pos)
{
        int c;
        int control = 0;
        //pclog("is_track_audio: %08x %i\n", pos, tocvalid);
        if (!tocvalid)
                return 0;
        //pclog(" scan tracks %i-%i\n", first_track, last_track);
        for (c = first_track; c < last_track; c++)
        {
                uint32_t track_address = toc->descriptors[c].p.frame +
                                         (toc->descriptors[c].p.second * 75) +
                                         (toc->descriptors[c].p.minute * 75 * 60);
//pclog("get_track_nr: track=%i pos=%x track_address=%x\n", c, pos, track_address);
                if (track_address <= pos)
                        control = toc->descriptors[c].control;
        }
        return (control & 4) ? 0 : 1;
}

static int ioctl_is_track_audio(uint32_t pos, int ismsf)
{
	if (ismsf)
	{
                CDMSF msf;
		msf.minute = (pos >> 16) & 0xff;
		msf.second = (pos >> 8) & 0xff;
		msf.frame = pos & 0xff;
		pos = msf_to_lba(&msf);
	}
	return is_track_audio(pos);
}

static void ioctl_playaudio(uint32_t pos, uint32_t len, int ismsf)
{
        //pclog("Play audio - %08X %08X %i\n", pos, len, ismsf);
        if (ismsf)
        {
                pos = (pos & 0xff) + (((pos >> 8) & 0xff) * 75) + (((pos >> 16) & 0xff) * 75 * 60);
                len = (len & 0xff) + (((len >> 8) & 0xff) * 75) + (((len >> 16) & 0xff) * 75 * 60);
                //pclog("MSF - pos = %08X len = %08X\n", pos, len);
        }
        else
        	len += pos;
        ioctl_cd_pos   = pos;// + 150;
        ioctl_cd_end   = pos+len;// + 150;
        ioctl_cd_state = CD_PLAYING;
	if (ioctl_cd_pos < 150)
		ioctl_cd_pos = 150;
//        pclog("Audio start %08X %08X %i %i %i\n", ioctl_cd_pos, ioctl_cd_end, ioctl_cd_state, 0, len);
}

static void ioctl_pause(void)
{
        if (ioctl_cd_state == CD_PLAYING)
        	ioctl_cd_state = CD_PAUSED;
}

static void ioctl_resume(void)
{
        if (ioctl_cd_state == CD_PAUSED)
        	ioctl_cd_state = CD_PLAYING;
}

static void ioctl_stop(void)
{
        ioctl_cd_state = CD_STOPPED;
}

static void ioctl_seek(uint32_t pos)
{
//        pclog("Seek %08X\n", pos);
        ioctl_cd_pos   = pos;
        ioctl_cd_state = CD_STOPPED;
}

static int read_toc(int fd)
{
        dk_cd_read_toc_t cd_read_toc;
        dk_cd_read_disc_info_t cd_read_disc_info;
        int result;
        int c;

        bzero(&cd_read_toc, sizeof(cd_read_toc));
        bzero(&cd_read_disc_info, sizeof(cd_read_disc_info));
        bzero(toc, sizeof(CDTOC) + sizeof(CDTOCDescriptor)*100);
	
        cd_read_toc.format = kCDTOCFormatTOC;
        cd_read_toc.buffer = toc;
        cd_read_toc.bufferLength = 2048;//sizeof(CDTOC) + sizeof(CDTOCDescriptor)*100;
	//pclog("buffer=%p bufferLength=%u\n", cd_read_toc.buffer, cd_read_toc.bufferLength);
        result = ioctl(fd, DKIOCCDREADTOC, &cd_read_toc);
        
        //pclog("Read_toc - fd=%d result=%d errno=%d toc=%p\n", fd, result, errno, toc);
	if (result < 0)
	{
		//pclog("read_toc: DKIOCCDREADTOC failed\n");
		return 0;
	}
        
        //pclog("toc length=%u first=%u last=%u\n", toc->length, toc->sessionFirst, toc->sessionFirst);
        first_track = 1;
        last_track = 0;
        toc_tracks = CDTOCGetDescriptorCount(toc);
        
        for (c = 0; c < toc_tracks; c++)
        {
                if (toc->descriptors[c].session > 1)
                        continue;
                track_addr[c] = msf_to_lba(&toc->descriptors[c].p);
                //pclog("Track %i %02x addr %08x\n", c, toc->descriptors[c].point, track_addr[c]);
                if (toc->descriptors[c].point <= 99 && toc->descriptors[c].adr == 1)
                        last_track++;
        }
        
        //pclog("Tracks %i %i  %i\n", first_track, last_track, toc_tracks);
  
        cd_read_disc_info.buffer = &disc_info;
        cd_read_disc_info.bufferLength = sizeof(disc_info);
        result = ioctl(fd, DKIOCCDREADDISCINFO, &cd_read_disc_info);
	if (result < 0)
	{
		//pclog("read_toc: DKIOCCDREADDISCINFO failed\n");
		return 0;
	}    
          
        return 1;
}

static int ioctl_ready(void)
{
        int ioctl_fd = cd_open();
        //pclog("ioctl_ready - %d %d\n", ioctl_fd, tocvalid);
	if (ioctl_fd <= 0)
		return 0;
        cd_close(ioctl_fd);

	if (tocvalid)
		return 1;
        
        return 0;
}

static int ioctl_get_last_block(unsigned char starttrack, int msf, int maxlen, int single)
{
        int c;
	int lb = 0;
	int tv = 0;
        int ioctl_fd;

        ioctl_fd = cd_open();
	if (ioctl_fd <= 0)
		return 0;

        //ioctl_cd_state = CD_STOPPED;

	if (!tocvalid)
		tv = read_toc(ioctl_fd);

        cd_close(ioctl_fd);
        
	if (!tv)
		return 0;

        for (c = 0; c <= toc_tracks; c++)
        {
                if (track_addr[c] > lb)
                        lb = track_addr[c];
        }
        return lb;
}

static int ioctl_medium_changed(void)
{
        dk_cd_read_disc_info_t cd_read_disc_info;
        CDDiscInfo new_disc_info;
        int result;
        int ioctl_fd;
        
        ioctl_fd = cd_open();
        if (ioctl_fd <= 0)
                return 1;
        
        bzero(&cd_read_disc_info, sizeof(cd_read_disc_info));
        cd_read_disc_info.buffer = &new_disc_info;
        cd_read_disc_info.bufferLength = sizeof(new_disc_info);
        result = ioctl(ioctl_fd, DKIOCCDREADDISCINFO, &cd_read_disc_info);
	if (result < 0)
	{
		//pclog("medium_changed: DKIOCCDREADDISCINFO failed\n");
                ioctl_cd_state = CD_STOPPED;
                tocvalid = 0;
                cd_close(ioctl_fd);
		return 1;
	}
        
        if (memcmp(&disc_info, &new_disc_info, sizeof(CDDiscInfo)))
        {
                //pclog("medium_changed: disc changed\n");
                ioctl_cd_state = CD_STOPPED;
                tocvalid = read_toc(ioctl_fd);
                cd_close(ioctl_fd);
                return 1;
        }
        
        cd_close(ioctl_fd);
        return 0;
}

static uint8_t ioctl_getcurrentsubchannel(uint8_t *b, int msf)
{
        uint32_t cdpos = ioctl_cd_pos;
        int track = get_track_nr(cdpos);
        uint32_t track_address = track_addr[track];
	long size;
	int pos=0;
	int err;
	uint8_t ret;
        

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

        b[pos++] = (toc->descriptors[track].adr << 4) | toc->descriptors[track].control;
        b[pos++] = toc->descriptors[track].point;
        b[pos++] = 0;

        if (msf)
        {
                uint32_t dat = cdpos;
                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 = cdpos - track_address;
                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
        {
                b[pos++] = (cdpos >> 24) & 0xff;
                b[pos++] = (cdpos >> 16) & 0xff;
                b[pos++] = (cdpos >> 8) & 0xff;
                b[pos++] = cdpos & 0xff;
                cdpos -= track_address;
                b[pos++] = (cdpos >> 24) & 0xff;
                b[pos++] = (cdpos >> 16) & 0xff;
                b[pos++] = (cdpos >> 8) & 0xff;
                b[pos++] = cdpos & 0xff;
        }

        //pclog("ioctl_getsubchannel: cdpos=%x track_address=%x track=%i state=%i ret=%02x\n", cdpos, track_address, track, ioctl_cd_state, ret);
        
        return ret;
}

static void ioctl_eject(void)
{
}

static void ioctl_load(void)
{
}

static int ioctl_readsector(uint8_t *b, int sector, int count)
{
        dk_cd_read_t cd_read;
        int res;
        int ioctl_fd;
        
        ioctl_fd = cd_open();
        if (ioctl_fd <= 0)
		return -1;
        //pclog("Read sector %x %i\n", sector, count);
        
        bzero(&cd_read, sizeof(cd_read));
        
        cd_read.offset = sector*2048;
        cd_read.buffer = b;
        cd_read.bufferLength = count*2048;
        cd_read.sectorType = kCDSectorTypeMode1;
        cd_read.sectorArea = kCDSectorAreaUser;
        
        res = ioctl(ioctl_fd, DKIOCCDREAD, &cd_read);
        //pclog("read res = %i %i %i\n", res, cd_read.bufferLength, errno);

        cd_close(ioctl_fd);

	return 0;
}

static void ioctl_readsector_raw(uint8_t *b, int sector)
{
        dk_cd_read_t cd_read;
        int res;
        int ioctl_fd;
        
        ioctl_fd = cd_open();
        if (ioctl_fd <= 0)
		return;
        //pclog("Read sector %x %i\n", sector, count);
        
        bzero(&cd_read, sizeof(cd_read));
        
        cd_read.offset = sector*kCDSectorSizeWhole;
        cd_read.buffer = b;
        cd_read.bufferLength = kCDSectorSizeWhole;
        cd_read.sectorType = kCDSectorTypeUnknown;
        cd_read.sectorArea = kCDSectorAreaUser;
        
        res = ioctl(ioctl_fd, DKIOCCDREAD, &cd_read);
        //pclog("read res = %i %i %i\n", res, cd_read.bufferLength, errno);

        cd_close(ioctl_fd);
}

static int ioctl_readtoc(unsigned char *b, unsigned char starttrack, int msf, int maxlen, int single)
{
        int len=4;
        int c,d;
        uint32_t temp;
	uint32_t last_address = 0;
        int seenAA = 0;
        
        pclog("ioctl_readtoc\n");
        //ioctl_cd_state = CD_STOPPED;

	if (!tocvalid)
        {
                int ioctl_fd = cd_open();
                
                if (ioctl_fd <= 0)
                        return 4;
                
		tocvalid = read_toc(ioctl_fd);
                cd_close(ioctl_fd);
        }
	if (!tocvalid)
		return 4;

                //pclog("Read TOC done! %i\n",single);
        b[2] = first_track;
        b[3] = last_track;
        d = 0;
        //pclog("Read TOC starttrack=%i\n", starttrack);
        for (c = 0; c <= toc_tracks; c++)
        {
                if (toc->descriptors[c].point && toc->descriptors[c].point >= starttrack && toc->descriptors[c].point <= 99)
                {
                        d = c;
                        break;
                }
        }
        //pclog("Start from track %i\n",d);
        b[2] = toc->descriptors[c].point;
        //last_block = 0;

        for (c = d; c <= toc_tracks; c++)
        {
                uint32_t address;
                if ((len + 8) > maxlen)
			break;
                //pclog("Len %i max %i Track %02X - %02X %02X %02i:%02i:%02i %08X\n",len,maxlen,toc->descriptors[c].point,toc->descriptors[c].adr,toc->descriptors[c].control,toc->descriptors[c].p.minute, toc->descriptors[c].p.second, toc->descriptors[c].p.frame,track_addr[c]);
                address = track_addr[c];
		if (address < last_address)
			continue;
                if (toc->descriptors[c].point == 0xaa)
                        seenAA = 1;
		last_address = address;
                b[len++] = 0; /*Reserved*/
                b[len++] = (toc->descriptors[c].adr << 4) | toc->descriptors[c].control;
                b[len++] = toc->descriptors[c].point;
                b[len++] = 0; /*Reserved*/
                //if (address > last_block)
                //        last_block = address;

                if (msf)
                {
                        b[len++] = 0;
                        b[len++] = toc->descriptors[c].p.minute;
                        b[len++] = toc->descriptors[c].p.second;
                        b[len++] = toc->descriptors[c].p.frame;
                }
                else
                {
                        temp = track_addr[c];
                        b[len++] = temp >> 24;
                        b[len++] = temp >> 16;
                        b[len++] = temp >> 8;
                        b[len++] = temp;
                }
                if (single)
			break;
        }
        
        if (!seenAA && !single)
        {
                uint32_t lb = 0;
                        
                for (c = 0; c <= toc_tracks; c++)
                {
                        if (track_addr[c] > lb)
                                lb = track_addr[c];
                }
                //pclog("Track AA - %08x %i\n", lb, len);
                b[len++] = 0; /*Reserved*/
                b[len++] = (1 << 4) | 1;
                b[len++] = 0xaa;
                b[len++] = 0; /*Reserved*/

                if (msf)
                {
                        b[len++] = 0;
                        b[len++] = (lb / 60) / 75;
                        b[len++] = (lb / 75) % 60;
                        b[len++] = lb % 75;
                }
                else
                {
                        temp = track_addr[c];
                        b[len++] = lb >> 24;
                        b[len++] = lb >> 16;
                        b[len++] = lb >> 8;
                        b[len++] = lb;
                }
        }

        b[0] = (uint8_t)(((len-2) >> 8) & 0xff);
        b[1] = (uint8_t)((len-2) & 0xff);
/*        pclog("Table of Contents (%i bytes) : \n", size);
        pclog("First track - %02X\n", first_track);
        pclog("Last  track - %02X\n", last_track);
        for (c = 0; c <= last_track; c++)
            pclog("Track %02X - number %02X control %02X adr %02X address %02X %02X %02X %02X\n", c, toc[c].cdte_track, toc[c].cdte_ctrl, toc[c].cdte_adr, 0, toc[c].cdte_addr.msf.minute, toc[c].cdte_addr.msf.second, toc[c].cdte_addr.msf.frame);
        for (c = 0;c <= last_track; c++)
            pclog("Track %02X - number %02X control %02X adr %02X address %06X\n", c, toc[c].cdte_track, toc[c].cdte_ctrl, toc[c].cdte_adr, MSFtoLBA(toc[c].cdte_addr.msf.minute, toc[c].cdte_addr.msf.second, toc[c].cdte_addr.msf.frame));*/
        return len;
}

static int ioctl_readtoc_session(unsigned char *b, int msf, int maxlen)
{
        int len = 4;
        int session = 0, first_track_nr = 0;
        int c, d;

	if (!tocvalid)
        {
                int ioctl_fd = cd_open();
                
                if (ioctl_fd <= 0)
                        return 4;
                
		tocvalid = read_toc(ioctl_fd);
                cd_close(ioctl_fd);
        }

	if (!tocvalid)
		return 4;
        
        for (c = 0; c <= toc_tracks; c++)
        {
                if (toc->descriptors[c].point && toc->descriptors[c].point <= 99)
                {
                        if (toc->descriptors[c].session > session || (toc->descriptors[c].session == session && toc->descriptors[c].point < first_track_nr))
                        {
                                session = toc->descriptors[c].session;
                                first_track_nr = toc->descriptors[c].point;
                                d = c;
                        }
                }
        }
        
        b[2] = toc->sessionFirst;
        b[3] = toc->sessionLast;
        b[len++] = 0; /*Reserved*/
        b[len++] = (toc->descriptors[d].adr << 4) | toc->descriptors[d].control;
        b[len++] = toc->descriptors[d].point;
        b[len++] = 0; /*Reserved*/
        if (msf)
        {
                b[len++] = 0;
                b[len++] = toc->descriptors[d].p.minute;
                b[len++] = toc->descriptors[d].p.second;
                b[len++] = toc->descriptors[d].p.frame;
        }
        else
        {
                uint32_t temp = track_addr[d];
                b[len++] = temp >> 24;
                b[len++] = temp >> 16;
                b[len++] = temp >> 8;
                b[len++] = temp;
        }

	return len;
}

static int ioctl_readtoc_raw(unsigned char *b, int maxlen)
{
	int len = 4;
        int ioctl_fd;
        int track;

//pclog("read_toc\n");
        if (!tocvalid)
                return 0;
        
        ioctl_fd = cd_open();
	if (ioctl_fd <= 0)
		return 0;
        cd_close(ioctl_fd);

	b[2] = toc->sessionFirst;
	b[3] = toc->sessionLast;

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


//		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++] = toc->descriptors[track].session;
		b[len++] = (toc->descriptors[track].adr << 4) | toc->descriptors[track].control;
		b[len++] = toc->descriptors[track].tno;
		b[len++] = toc->descriptors[track].point;
		b[len++] = toc->descriptors[track].address.minute;
		b[len++] = toc->descriptors[track].address.second;
		b[len++] = toc->descriptors[track].address.frame;
		b[len++] = toc->descriptors[track].zero;
		b[len++] = toc->descriptors[track].p.minute;
		b[len++] = toc->descriptors[track].p.second;
		b[len++] = toc->descriptors[track].p.frame;
	}

	return len;
}

static uint32_t ioctl_size()
{
	return cdrom_capacity;
}

static int ioctl_status()
{
        if (!ioctl_ready)
                return CD_STATUS_EMPTY;
	return CD_STATUS_STOPPED;
}
void ioctl_reset()
{
        int ioctl_fd;
        
	tocvalid = 0;

        ioctl_fd = cd_open();
	if (ioctl_fd <= 0)
		return;

	tocvalid = read_toc(ioctl_fd);
        cd_close(ioctl_fd);
}

void ioctl_set_drive(char d)
{
	ioctl_close();
	atapi=&ioctl_atapi;
	ioctl_open(d);
}

int ioctl_open(char d)
{
	atapi = &ioctl_atapi;

        toc = malloc(2048);

        cd_drive = d;
        return 0;
}

void ioctl_close(void)
{
        if (toc)
        {
                free(toc);
                toc = NULL;
        }
}

static void ioctl_exit(void)
{

}

static ATAPI ioctl_atapi=
{
        ioctl_ready,
	ioctl_medium_changed,
        ioctl_readtoc,
        ioctl_readtoc_session,
	ioctl_readtoc_raw,
        ioctl_getcurrentsubchannel,
        ioctl_readsector,
	ioctl_readsector_raw,
        ioctl_playaudio,
        ioctl_seek,
        ioctl_load,
        ioctl_eject,
        ioctl_pause,
        ioctl_resume,
        ioctl_size,
	ioctl_status,
	ioctl_is_track_audio,
        ioctl_stop,
        ioctl_exit
};
