#include "meta.h"
#include "../util.h"

/* Apple Core Audio Format */

VGMSTREAM * init_vgmstream_apple_caff(STREAMFILE *streamFile) {
    VGMSTREAM * vgmstream = NULL;
    char filename[PATH_LIMIT];

    off_t start_offset = 0;
    off_t data_size = 0;
    off_t sample_count = 0;
    off_t interleave = 0;
    int sample_rate = -1, unused_frames = 0;
    int channel_count = 0;
    
    off_t file_length;
    off_t chunk_offset = 8;
    int found_desc = 0, found_pakt = 0, found_data = 0;

    /* check extension, case insensitive */
    streamFile->get_name(streamFile,filename,sizeof(filename));
    if (strcasecmp("caf",filename_extension(filename))) goto fail;

    /* check "caff" id */
    if (read_32bitBE(0,streamFile)!=0x63616666) goto fail;
    /* check version, flags */
    if (read_32bitBE(4,streamFile)!=0x00010000) goto fail;

    file_length = (off_t)get_streamfile_size(streamFile);

    while (chunk_offset < file_length)
    {
        /* high half of size (expect 0s) */
        if (read_32bitBE(chunk_offset+4,streamFile) != 0) goto fail;

        /* handle chunk type */
        switch (read_32bitBE(chunk_offset,streamFile))
        {
            case 0x64657363:    /* desc */
                found_desc = 1;
                {
                    /* rather than put a convoluted conversion here for
                       portability, just look it up */
                    uint32_t sratefloat = read_32bitBE(chunk_offset+0x0c, streamFile);
                    if (read_32bitBE(chunk_offset+0x10, streamFile) != 0) goto fail;
                    switch (sratefloat)
                    {
					    case 0x40D19400:
							sample_rate = 18000;
							break;
                        case 0x40D58880:
                            sample_rate = 22050;
                            break;
                        case 0x40DF4000:
                            sample_rate = 32000;
                            break;
                        case 0x40E58880:
                            sample_rate = 44100;
                            break;
                        case 0x40E77000:
                            sample_rate = 48000;
                            break;
                        default:
                            goto fail;
                    }
                }

                {
                    uint32_t bytes_per_packet, frames_per_packet, channels_per_frame, bits_per_channel;
                    uint32_t codec_4cc = read_32bitBE(chunk_offset+0x14, streamFile);
                    /* only supporting ima4 for now */
                    if (codec_4cc != 0x696d6134) goto fail;

                    /* format flags */
                    if (read_32bitBE(chunk_offset+0x18, streamFile) != 0) goto fail;
                    bytes_per_packet = read_32bitBE(chunk_offset+0x1c, streamFile);
                    frames_per_packet = read_32bitBE(chunk_offset+0x20, streamFile);
                    channels_per_frame = read_32bitBE(chunk_offset+0x24, streamFile);
                    bits_per_channel = read_32bitBE(chunk_offset+0x28, streamFile);

                    interleave = bytes_per_packet / channels_per_frame;
                    channel_count = channels_per_frame;
                    if (channels_per_frame != 1 && channels_per_frame != 2)
                        goto fail;
                    /* ima4-specific */
                    if (frames_per_packet != 64) goto fail;
                    if ((frames_per_packet / 2 + 2) * channels_per_frame !=
                        bytes_per_packet) goto fail;
                    if (bits_per_channel != 0) goto fail;
                }
                break;
            case 0x70616b74:    /* pakt */
                found_pakt = 1;
                /* 64-bit packet table size, 0 for constant bitrate */
                if (
                    read_32bitBE(chunk_offset+0x0c,streamFile) != 0 ||
                    read_32bitBE(chunk_offset+0x10,streamFile) != 0) goto fail;
                /* high half of valid frames count */
                if (read_32bitBE(chunk_offset+0x14,streamFile) != 0) goto fail;
                /* frame count */
                sample_count = read_32bitBE(chunk_offset+0x18,streamFile);
                /* priming frames */
                if (read_32bitBE(chunk_offset+0x1c,streamFile) != 0) goto fail;
                /* remainder (unused) frames */
                unused_frames = read_32bitBE(chunk_offset+0x20,streamFile);
                break;
            case 0x66726565:    /* free */
                /* padding, ignore */
                break;
            case 0x64617461:    /* data */
                if (read_32bitBE(chunk_offset+12,streamFile) != 1) goto fail;
                found_data = 1;
                start_offset = chunk_offset + 16;
                data_size = read_32bitBE(chunk_offset+8,streamFile) - 4;
                break;
            default:
                goto fail;
        }

        /* done with chunk */
        chunk_offset += 12 + read_32bitBE(chunk_offset+8,streamFile);
    }

    if (!found_pakt || !found_desc || !found_data) goto fail;
    if (start_offset == 0 || data_size == 0 || sample_count == 0 ||
        sample_rate == -1 || channel_count == 0) goto fail;

    /* ima4-specific */
    /* check for full packets */
    if (data_size % (interleave*channel_count) != 0) goto fail;
    if ((sample_count+unused_frames)%((interleave-2)*2) != 0) goto fail;
    /* check that all packets are accounted for */
    if (data_size/interleave/channel_count !=
        (sample_count+unused_frames)/((interleave-2)*2)) goto fail;

    vgmstream = allocate_vgmstream(channel_count,0);
    if (!vgmstream) goto fail;

    vgmstream->channels = channel_count;
    vgmstream->sample_rate = sample_rate;
    vgmstream->num_samples = sample_count;
    /* ima4-specific */
    vgmstream->coding_type = coding_APPLE_IMA4;
    if (channel_count == 2)
        vgmstream->layout_type = layout_interleave;
    else
        vgmstream->layout_type = layout_none;
    vgmstream->interleave_block_size = interleave;
    vgmstream->meta_type = meta_CAFF;

    /* open the file for reading by each channel */
    {
        int i;
        for (i=0;i<channel_count;i++)
        {
            vgmstream->ch[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
            if (!vgmstream->ch[i].streamfile) goto fail;

            vgmstream->ch[i].offset =
                vgmstream->ch[i].channel_start_offset =
                start_offset + interleave * i;
        }
    }

    return vgmstream;
fail:
    if (vgmstream) close_vgmstream(vgmstream);
    return NULL;
}
