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


/* RWAV is a bit simpler, while FWAV and CWAV are basically identical except for endianness.
 * From SDK info, typically .xxxx is the original NintendoWare format (usually a .xml) and .bxxxx is a
 * binary/external form (like .rseq=xml then .brseq=binary, or .wav > brwav). */
typedef enum { RWAV, FWAV, CWAV } bxwav_type_t;

static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type);


/* RWAV - NintendoWare binary Revolution wave (Wii games) */
VGMSTREAM* init_vgmstream_brwav(STREAMFILE* sf) {

    /* checks */
    if (!is_id32be(0x00, sf, "RWAV"))
        goto fail;

    /* .brwav: from tools (no games known)
     * .rwav: header id */
    if (!check_extensions(sf, "brwav,rwav"))
        goto fail;

    return init_vgmstream_bxwav(sf, RWAV);
fail:
    return NULL;
}

/* FWAV - NintendoWare binary caFe wave (WiiU and Switch games) */
VGMSTREAM* init_vgmstream_bfwav(STREAMFILE* sf) {

    /* checks */
    if (!is_id32be(0x00, sf, "FWAV"))
        goto fail;

    /* .bfwav: used?
     * .fwav: header id */
    if (!check_extensions(sf, "bfwav,fwav"))
        goto fail;

    return init_vgmstream_bxwav(sf, FWAV);
fail:
    return NULL;
}

/* CWAV - NintendoWare binary CTR wave (3DS games) */
VGMSTREAM* init_vgmstream_bcwav(STREAMFILE* sf) {

    /* checks */
    if (!is_id32be(0x00, sf, "CWAV"))
        goto fail;

    /* .bcwav: standard 3DS (though rare as usually found in .bcsar) [Adventure Bar Story (3DS), LBX (3DS)]
     * .adpcm: 80's Overdrive (3DS)
     * .bms: 3D Classics Kirby's Adventure (3DS)
     * .sfx: Wizdom (3DS)
     * .str: Pac-Man and the Ghostly Adventures 2 (3DS)
     * .zic: Wizdom (3DS) */
    if (!check_extensions(sf, "bcwav,adpcm,bms,sfx,str,zic"))
        goto fail;

    return init_vgmstream_bxwav(sf, CWAV);
fail:
    return NULL;
}


static VGMSTREAM* init_vgmstream_bxwav(STREAMFILE* sf, bxwav_type_t type) {
    VGMSTREAM* vgmstream = NULL;

    uint32_t info_offset, data_offset, chtb_offset, file_size;
    int channels, loop_flag, codec, sample_rate;
    int big_endian;
    int32_t num_samples, loop_start;

    read_u32_t read_u32;
    read_s32_t read_s32;
    read_u16_t read_u16;
    read_s16_t read_s16;

    /* BOM check */
    if (read_u16be(0x04, sf) == 0xFEFF) { /* Wii, WiiU */
        big_endian = 1;
        read_u32 = read_u32be;
        read_s32 = read_s32be;
        read_u16 = read_u16be;
        read_s16 = read_s16be;
    }
    else if (read_u16le(0x04, sf) == 0xFEFF) { /* 3DS, Switch */
        big_endian = 0;
        read_u32 = read_u32le;
        read_s32 = read_s32le;
        read_u16 = read_u16le;
        read_s16 = read_s16le;
    }
    else {
        goto fail;
    }

    /* header */
    switch(type) {
        case RWAV:
            /* 0x06(2): version (usually 0102, 0103=Let's Tap, no diffs) */
            file_size = read_u32(0x08, sf);
            /* 0x0c(2): header size (0x20) */
            /* 0x0e(2): sections (2) */

            info_offset = read_u32(0x10, sf);
            /* 0x14: info size */

            data_offset = read_u32(0x18, sf);
            /* 0x1c: data size */

            break;

        case FWAV:
        case CWAV:
            /* 0x06(2): header size (0x40) */
            /* 0x08: version */
            /* - FWAV: 0x00010200 */
            /* - CWAV: 0x00000002 (Kirby's Adventure), 0x00000102 (common), 0x00010102 (FE Fates, Hyrule Warriors Legends) */
            file_size = read_u32(0x0c, sf);
            /* 0x10(2): sections (2) */

            /* 0x14(2): info mark (0x7000) */
            info_offset = read_u32(0x18, sf);
            /* 0x1c: info size */

            /* 0x20(2): data mark (0x7001) */
            data_offset = read_u32(0x24, sf);
            /* 0x28: data size */
            /* rest: padding */
            break;

        default:
            goto fail;        
    }

    if (file_size != get_streamfile_size(sf)) {
        vgm_logi("BXWAV: wrong size %x vs %x\n", file_size, (uint32_t)get_streamfile_size(sf));
        goto fail;
    }

    /* INFO section */
    if (!is_id32be(info_offset + 0x00, sf, "INFO"))
        goto fail;
    /* 0x04: size */

    switch(type) {
        case RWAV:
            codec       =  read_u8(info_offset + 0x08, sf);
            loop_flag   =  read_u8(info_offset + 0x09, sf);
            channels    =  read_u8(info_offset + 0x0a, sf);
            /* 0x0b:  part of rate? */
            sample_rate = read_u16(info_offset + 0x0c, sf);
            /* 0x0e(2): padding */
            loop_start  = read_s32(info_offset + 0x10, sf);
            num_samples = read_s32(info_offset + 0x14, sf);
            chtb_offset = read_u32(info_offset + 0x18, sf) + info_offset + 0x08;
            /* 0x1c: channel table size */

            loop_start  = dsp_nibbles_to_samples(loop_start);
            num_samples = dsp_nibbles_to_samples(num_samples);

            break;

        case FWAV:
        case CWAV:
            codec       =  read_u8(info_offset + 0x08, sf);
            loop_flag   =  read_u8(info_offset + 0x09, sf);
            /* 0x0a: padding */
            sample_rate = read_u32(info_offset + 0x0C, sf);
            loop_start  = read_s32(info_offset + 0x10, sf);
            num_samples = read_s32(info_offset + 0x14, sf);
            /* 0x18: original loop start? (slightly lower) */
            chtb_offset = info_offset + 0x1C;
            channels    = read_u32(chtb_offset + 0x00, sf);
            break;

        default:
            goto fail;        
    }

    /* channel table is parsed at the end */

    /* DATA section */
    if (!is_id32be(data_offset + 0x00, sf, "DATA"))
        goto fail;
    /* 0x04: size */


    /* build the VGMSTREAM */
    vgmstream = allocate_vgmstream(channels, loop_flag);
    if (!vgmstream) goto fail;

    switch(type) {
        case RWAV: vgmstream->meta_type = meta_RWAV; break;
        case FWAV: vgmstream->meta_type = meta_FWAV; break;
        case CWAV: vgmstream->meta_type = meta_CWAV; break;
        default: goto fail;
    }
    vgmstream->sample_rate = sample_rate;

    vgmstream->num_samples = num_samples;
    vgmstream->loop_start_sample = loop_start;
    vgmstream->loop_end_sample = num_samples;
    if (type == CWAV)
        vgmstream->allow_dual_stereo = 1; /* LEGO 3DS games */
    
    vgmstream->layout_type = layout_none;

    /* only 0x02 is known, others can be made with SDK tools */
    switch (codec) {
        case 0x00:
            vgmstream->coding_type = coding_PCM8;
            break;

        case 0x01:
            vgmstream->coding_type = big_endian ? coding_PCM16BE : coding_PCM16LE;
            break;

        case 0x02: /* common */
            vgmstream->coding_type = coding_NGC_DSP;
            /* coefs are read below */
            break;

        case 0x03:
            vgmstream->coding_type = coding_3DS_IMA;
            /* hist is read below */
            break;

        default:
            goto fail;
    }


    if (!vgmstream_open_stream_bf(vgmstream, sf, data_offset, 1))
        goto fail;

    /* parse channel table and offsets
     * (usually the interleave/distance is fixed, but in theory could be non-standard, so assign manually) */
    {
        int ch, i;
        for (ch = 0; ch < channels; ch++) {
            uint32_t chnf_offset, chdt_offset, coef_offset;
            switch(type) {
                case RWAV:
                    /* channel entry: */
                    /* - 0x04: channel info offset (from INFO offset after size) */

                    chnf_offset = read_u32(chtb_offset + ch * 0x04 + 0x00, sf) + info_offset + 0x08;

                    /* channel info: */
                    /* 0x00: offset to channel data (from DATA offset after size) */
                    /* 0x04: ADPCM offset (from INFO offset after size), 0xFFFFFFFF otherwise? */
                    /* 0x08: volumes x4? */
                    /* 0x18: padding */

                    chdt_offset = read_u32(chnf_offset + 0x00, sf) + data_offset + 0x08;
                    coef_offset = read_u32(chnf_offset + 0x04, sf) + info_offset + 0x08;
                    break;

                case FWAV:
                case CWAV:
                    /* channel entry: */
                    /* - 0x00: mark (0x7100) */
                    /* - 0x02: padding */
                    /* - 0x04: channel info offset (from channel table offset) */

                    chnf_offset = read_u32(chtb_offset + 0x04 + ch * 0x08 + 0x04, sf) + chtb_offset;

                    /* channel info: */
                    /* 0x00: mark (0x1F00) */
                    /* 0x02: padding */
                    /* 0x04: offset to channel data (from DATA offset after size) */
                    /* 0x08: ADPCM mark (0x0300=DSP, 0x0301=IMA, 0x0000=none) */
                    /* 0x0a: padding */
                    /* 0x0c: ADPCM offset (from channel info offset), 0xFFFFFFFF otherwise */
                    /* 0x10: padding */

                    if (read_u16(chnf_offset + 0x00, sf) != 0x1F00)
                        goto fail;
                    chdt_offset = read_u32(chnf_offset + 0x04, sf) + data_offset + 0x08;
                    coef_offset = read_u32(chnf_offset + 0x0c, sf) + chnf_offset;
                    break;

                default:
                    goto fail;        
            }

            vgmstream->ch[ch].channel_start_offset = chdt_offset;
            vgmstream->ch[ch].offset = chdt_offset;

            switch(codec) {
                case 0x02: {
                    /* standard DSP coef + predictor + hists + loop predictor + loop hists */
                    for (i = 0; i < 16; i++) {
                        vgmstream->ch[ch].adpcm_coef[i] = read_s16(coef_offset + 0x00 + i*0x02, sf);
                    }
                    //vgmstream->ch[ch].adpcm_history1_16 = read_s16(coef_offset + 0x22, sf);
                    //vgmstream->ch[ch].adpcm_history2_16 = read_s16(coef_offset + 0x24, sf);
                    break;
                }

                case 0x03: {
                    /* hist + step */
                    vgmstream->ch[ch].adpcm_history1_16 = read_s16(coef_offset + 0x00, sf);
                    vgmstream->ch[ch].adpcm_step_index = read_s16(coef_offset + 0x02, sf);
                    break;
                }

                default:
                    break;
            }
        }
    }

    return vgmstream;

fail:
    close_vgmstream(vgmstream);
    return NULL;
}

/* ************************************************************************* */

/* RWAR - NintendoWare container [BIT.TRIP BEAT (Wii), Dance Dance Revolution Hottest Party 2 (Wii)] */
VGMSTREAM* init_vgmstream_brwar(STREAMFILE* sf) {
    VGMSTREAM* vgmstream = NULL;
    STREAMFILE* temp_sf = NULL;
    uint32_t tabl_offset, data_offset;
    uint32_t subfile_offset, subfile_size;
    int total_subsongs, target_subsong = sf->stream_index;


    /* checks */
    if (!is_id32be(0x00, sf, "RWAR"))
        goto fail;

    if (!check_extensions(sf,"rwar"))
        goto fail;

    /* simple container of .rwavs (inside .brsar), rarely used with single siles (DDR) */

    /* abridged, see RWAV (same header) */
    /* 0x04(2): BOM */
    /* 0x06(2): version (usually 0100) */
    /* 0x08: file size */
    /* 0x0c(2): header size (0x20) */
    /* 0x0e(2): sections (2) */

    tabl_offset = read_u32be(0x10, sf);
    /* 0x14: tabl size */

    data_offset = read_u32be(0x18, sf);
    /* 0x1c: data size */

    /* TABL section */
    if (!is_id32be(tabl_offset + 0x00, sf, "TABL"))
        goto fail;
    /* 0x04: size */

    total_subsongs = read_u32be(tabl_offset + 0x08,sf);
    if (target_subsong == 0) target_subsong = 1;
    if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail;

    /* 0x00: always 0x01000000? */
    subfile_offset = read_u32be(tabl_offset + 0x0c + (target_subsong-1) * 0x0c + 0x04,sf) + data_offset;
    subfile_size   = read_u32be(tabl_offset + 0x0c + (target_subsong-1) * 0x0c + 0x08,sf);


    /* DATA section */
    if (!is_id32be(data_offset + 0x00, sf, "DATA"))
        goto fail;
    /* 0x04: size */

    //VGM_LOG("BRWAR: of=%x, sz=%x\n", subfile_offset, subfile_size);

    temp_sf = setup_subfile_streamfile(sf, subfile_offset, subfile_size, "rwav");
    if (!temp_sf) goto fail;

    vgmstream = init_vgmstream_brwav(temp_sf);
    if (!vgmstream) goto fail;

    vgmstream->num_streams = total_subsongs;
    close_streamfile(temp_sf);
    return vgmstream;

fail:
    close_streamfile(temp_sf);
    close_vgmstream(vgmstream);
    return NULL;
}
