#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};
