////////////////////////////////
// WAVE Parser: Functions

// low level wave format

MR4TH_SYMBOL_STATIC WAVE_SubChunkList
wave_sub_chunks_from_data(Arena *arena, String8 data){
    // read master header
    WAVE_RiffMasterChunkHeader header = {0};
    if (sizeof(header) <= data.size){
        MemoryCopy(&header, data.str, sizeof(header));
    }
    
    // check master header
    B32 looks_like_wave_file = (header.riff_id == WAVE_RIFF_ID &&
                                header.sub_id == WAVE_ID_WAVE);
    
    // get clamped size
    U64 clamped_size = 0;
    if (looks_like_wave_file){
        // 8 = 4 bytes for riff_id + 4 bytes for size
        U64 size_according_to_header = header.size + 8;
        clamped_size = ClampTop(data.size, size_according_to_header);
    }
    
    // start of main data
    U64 main_data_off = sizeof(header);
    
    // scan chunks
    WAVE_SubChunkList result = {0};
    for (U64 off = main_data_off;
         off < clamped_size;){
        // read sub header
        WAVE_RiffSubChunkHeader sub_header = {0};
        B32 got_sub_header = 0;
        if (off + sizeof(sub_header) <= data.size){
            got_sub_header = 1;
            MemoryCopy(&sub_header, data.str + off, sizeof(sub_header));
        }
        if (!got_sub_header){
            break;
        }
        
        // get range of chunk data
        U64 data_off = off + sizeof(sub_header);
        U64 end_of_data_off = data_off + sub_header.size;
        U64 end_of_data_off_clamped = ClampTop(end_of_data_off, clamped_size);
        U64 clamped_size = end_of_data_off - data_off;
        
        // emit chunk node
        WAVE_SubChunkNode *new_node = push_array(arena, WAVE_SubChunkNode, 1);
        SLLQueuePush(result.first, result.last, new_node);
        result.count += 1;
        new_node->id = sub_header.id;
        new_node->size = clamped_size;
        new_node->off = data_off;
        
        // increment cursor
        U64 aligned_next_off = AlignUpPow2(end_of_data_off, wave_riff_chunk_align);
        off = aligned_next_off;
    }
    
    return(result);
}

MR4TH_SYMBOL_STATIC WAVE_SubChunkNode*
wave_chunk_from_id(WAVE_SubChunkList *list, U32 id){
    Assert(list != 0);
    WAVE_SubChunkNode *result = 0;
    for (WAVE_SubChunkNode *node = list->first;
         node != 0;
         node = node->next){
        if (node->id == id){
            result = node;
            break;
        }
    }
    return(result);
}

MR4TH_SYMBOL_STATIC WAVE_FormatData
wave_format_data_from_fmt_chunk(WAVE_SubChunkNode *node, String8 data){
    WAVE_FormatData result = {0};
    
    {
        U64 opl_clamped = ClampTop(node->off + node->size, data.size);
        U64 first_clamped = ClampTop(node->off, opl_clamped);
        String8 node_data = CLiteral(String8){data.str + first_clamped, opl_clamped - first_clamped};
        
        // try to read fmt info
        U64 fmt_off = 0;
        WAVE_ChunkFmt fmt = {0};
        B32 got_fmt = 0;
        if (fmt_off + sizeof(fmt) <= node_data.size){
            got_fmt = 1;
            MemoryCopy(&fmt, node_data.str + fmt_off, sizeof(fmt));
        }
        
        if (got_fmt){
            
            // try to read extension info
            WAVE_ChunkFmtExt1 fmt_ext1 = {0};
            fmt_ext1.valid_bits_per_value = max_U16;
            {
                U64 extheader_off = fmt_off + sizeof(fmt);
                WAVE_ChunkFmtExtSize fmt_extheader = {0};
                B32 got_fmt_extheader = 0;
                if (extheader_off + sizeof(fmt_extheader) <= node_data.size){
                    got_fmt_extheader = 1;
                    MemoryCopy(&fmt_extheader, node_data.str + extheader_off, sizeof(fmt_extheader));
                }
                
                if (got_fmt_extheader){
                    U64 ext_size = fmt_extheader.extension_size;
                    U64 ext_size_clamped = ClampTop(ext_size, sizeof(fmt_ext1));
                    U64 ext_off = extheader_off + sizeof(U16);
                    
                    if (ext_off + sizeof(fmt_ext1) <= node_data.size){
                        MemoryCopy(&fmt_ext1, node_data.str + ext_off, sizeof(fmt_ext1));
                    }
                }
            }
            
            // Expecting:
            //  (fmt.bytes_per_second == fmt.bytes_per_sample*fmt.samples_per_second)
            //  ignoring  bytes_per_second in parser right now.
            
            // sample size calculations
            U32 bit_stride = AlignUpPow2(fmt.bits_per_value, 8);
            U32 byte_stride = bit_stride/8;
            U32 bits = Min(fmt.bits_per_value, fmt_ext1.valid_bits_per_value);
            
            // fill output
            result.format_tag = fmt.format_tag;
            result.channel_count = fmt.channel_count;
            result.samples_per_second = fmt.samples_per_second;
            result.bytes_per_sample = fmt.bytes_per_sample;
            result.byte_stride_per_value = byte_stride;
            result.bits_per_value = bits;
            result.channel_mask = fmt_ext1.channel_mask;
            MemoryCopyArray(result.sub_format, fmt_ext1.sub_format);
        }
    }
    
    return(result);
}

// high level representation

MR4TH_SYMBOL_STATIC SOUND_Data*
wave_sound_data(Arena *arena, String8 data){
    B32 failed_to_unpack = 0;
    
    ArenaTemp scratch = arena_get_scratch(&arena, 1);
    
    WAVE_SubChunkList chunks = wave_sub_chunks_from_data(scratch.arena, data);
    
    // read format data
    WAVE_FormatData fmt = {0};
    if (!failed_to_unpack){
        WAVE_SubChunkNode *fmt_chunk = wave_chunk_from_id(&chunks, WAVE_ID_fmt);
        if (fmt_chunk == 0){
            failed_to_unpack = 1;
        }
        if (fmt_chunk != 0){
            fmt = wave_format_data_from_fmt_chunk(fmt_chunk, data);
        }
    }
    
    // determine value kind
    SOUND_ValueKind value_kind = SOUND_ValueKind_Signed0;
    if (!failed_to_unpack){
        switch (fmt.format_tag){
            case WAVE_FormatTag_PCM:
            {
                value_kind = fmt.bits_per_value;
            }break;
            
            case WAVE_FormatTag_IEEE_FLOAT:
            {
                switch (fmt.bits_per_value){
                    case 16: value_kind = SOUND_ValueKind_Float16; break;
                    case 32: value_kind = SOUND_ValueKind_Float32; break;
                    case 64: value_kind = SOUND_ValueKind_Float64; break;
                }
            }break;
        }
        if (value_kind == SOUND_ValueKind_Signed0){
            failed_to_unpack = 1;
        }
    }
    
    // setup buffer of raw samples
    BOP_Data *sample_buffer = 0;
    U64 sample_count = 0;
    if (!failed_to_unpack){
        WAVE_SubChunkNode *data_chunk = wave_chunk_from_id(&chunks, WAVE_ID_data);
        if (data_chunk == 0){
            failed_to_unpack = 1;
        }
        if (data_chunk != 0){
            sample_count = data_chunk->size/fmt.bytes_per_sample;
            
            sample_buffer =
                bop_ptr(scratch.arena, fmt.byte_stride_per_value,
                        sample_count*fmt.channel_count, data.str + data_chunk->off);
        }
    }
    
    // allocate and fill sound data
    SOUND_Data *sound = 0;
    if (!failed_to_unpack){
        
        // allocate
        sound = push_array(arena, SOUND_Data, 1);
        void **channels = push_array(arena, void*, fmt.channel_count);
        
        // uninterleave raw data into channels
        BOP_Data **channel_buffers = push_array(scratch.arena, BOP_Data*, fmt.channel_count);
        for (U32 i = 0; i < fmt.channel_count; i += 1){
            
            // TODO(allen): we don't actually need to allocate the
            // buffer object itself onto the arena - bop_ptr would work better.
            channel_buffers[i] = bop_alloc(arena, fmt.byte_stride_per_value, sample_count);
        }
        bop_uninterleave(channel_buffers, sample_buffer, fmt.channel_count);
        for (U32 i = 0; i < fmt.channel_count; i += 1){
            channels[i] = channel_buffers[i]->data;
        }
        
        // fill sound data
        sound->channels = channels;
        sound->channel_count = fmt.channel_count;
        sound->sample_count = sample_count;
        sound->samples_per_second = fmt.samples_per_second;
        sound->bytes_per_value = fmt.byte_stride_per_value;
        sound->value_kind = value_kind;
    }
    
    arena_release_scratch(&scratch);
    
    return(sound);
}

////////////////////////////////
// WAVE Render: Functions

MR4TH_SYMBOL_STATIC String8
wave_render(Arena *arena, WAVE_RenderParams *params, void *sample_data, U64 size){
    // interpret the wav render kind
    WAVE_FormatTag tag = 0;
    U32 valid_bits_per_value = 0;
    if (params->kind <= 0xFFFF){
        valid_bits_per_value = params->kind;
        tag = WAVE_FormatTag_PCM;
    }
    else{
        switch (params->kind){
            case SOUND_ValueKind_Float16:
            {
                valid_bits_per_value = 16; tag = WAVE_FormatTag_IEEE_FLOAT;
            }break;
            case SOUND_ValueKind_Float32:
            {
                valid_bits_per_value = 32; tag = WAVE_FormatTag_IEEE_FLOAT;
            }break;
            case SOUND_ValueKind_Float64:
            {
                valid_bits_per_value = 64; tag = WAVE_FormatTag_IEEE_FLOAT;
            }break;
        }
    }
    B32 good_kind = (0 < valid_bits_per_value && valid_bits_per_value <= 64);
    
    // proceed with serialization if validated
    String8 result = {0};
    if (good_kind){
        
        // compute additional rates from params
        U32 channel_count = params->channel_count;
        U32 bits_per_value_rounded = AlignUpPow2(valid_bits_per_value, 8);
        U32 bytes_per_sample_value = bits_per_value_rounded >> 3;
        U32 bytes_per_sample = bytes_per_sample_value*channel_count;
        U32 bytes_per_second = bytes_per_sample*params->samples_per_second;
        
        // determine if extension is required
        B32 required_extension = 0;
        if (channel_count > 2 || tag != WAVE_FormatTag_PCM){
            required_extension = 1;
        }
        
        // determine how to report bits_per_value
        U32 report_bits_per_value = valid_bits_per_value;
        if (required_extension){
            report_bits_per_value = bits_per_value_rounded;
        }
        
        // auto-mask
        U32 auto_mask = max_U32;
        if (required_extension){
            if (channel_count < 32){
                auto_mask = (1 << channel_count) - 1;
            }
        }
        
        // setup serialization
        ArenaTemp scratch = arena_get_scratch(&arena, 1);
        String8List list = {0};
        
        // master header
        WAVE_RiffMasterChunkHeader *master = push_array(scratch.arena, WAVE_RiffMasterChunkHeader, 1);
        str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)master, sizeof(*master)});
        master->riff_id = WAVE_RIFF_ID;
        master->sub_id = WAVE_ID_WAVE;
        
        // fmt chunk
        {
            WAVE_RiffSubChunkHeader *fmt_header = push_array(scratch.arena, WAVE_RiffSubChunkHeader, 1);
            str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)fmt_header, sizeof(fmt_header)});
            fmt_header->id = WAVE_ID_fmt;
            
            // save position of fmt chunk
            U64 fmt_chunk_off = list.total_size;
            
            // fill fmt data
            WAVE_ChunkFmt *fmt_data = push_array(scratch.arena, WAVE_ChunkFmt, 1);
            str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)fmt_data, sizeof(*fmt_data)});
            
            fmt_data->format_tag = tag;
            fmt_data->channel_count = channel_count;
            fmt_data->samples_per_second = params->samples_per_second;
            fmt_data->bytes_per_second = bytes_per_second;
            fmt_data->bytes_per_sample = bytes_per_second/params->samples_per_second;
            fmt_data->bits_per_value  = report_bits_per_value;
            
            if (required_extension){
                // fill fmt ext header data
                WAVE_ChunkFmtExtSize *fmt_extheader = push_array(scratch.arena, WAVE_ChunkFmtExtSize, 1);
                str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)fmt_extheader, sizeof(*fmt_extheader)});
                fmt_extheader->extension_size = sizeof(WAVE_ChunkFmtExt1);
                
                // fill fmt ext data
                WAVE_ChunkFmtExt1 *fmt_ext = push_array(scratch.arena, WAVE_ChunkFmtExt1, 1);
                str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)fmt_ext, sizeof(*fmt_ext)});
                fmt_ext->valid_bits_per_value = valid_bits_per_value;
                fmt_ext->channel_mask = auto_mask;
                MemoryCopy(fmt_ext->sub_format, &tag, 2);
                MemoryCopy(fmt_ext->sub_format + 2,
                           "\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71",
                           14);
            }
            
            // fill fmt header size
            fmt_header->size = list.total_size - fmt_chunk_off;
        }
        
        // data chunk
        {
            WAVE_RiffSubChunkHeader *data_header = push_array(scratch.arena, WAVE_RiffSubChunkHeader, 1);
            str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)data_header, sizeof(*data_header)});
            data_header->id = WAVE_ID_data;
            
            // save position of data chunk
            U64 data_chunk_off = list.total_size;
            
            // push the sample data
            str8_list_push(scratch.arena, &list, CLiteral(String8){(U8*)sample_data, size});
            
            // fill data header size
            data_header->size = list.total_size - data_chunk_off;
        }
        
        // fill master size
        master->size = list.total_size - 8;
        
        // combine output
        result = str8_join(arena, &list, 0);
        
        arena_release_scratch(&scratch);
    }
    
    return(result);
}

////////////////////////////////
// WAVE Debug: Functions

MR4TH_SYMBOL_STATIC void
wave_dump(Arena *arena, String8List *out, String8 data){
    ArenaTemp scratch = arena_get_scratch(&arena, 1);
    WAVE_SubChunkList chunks = wave_sub_chunks_from_data(scratch.arena, data);
    for (WAVE_SubChunkNode *node = chunks.first;
         node != 0;
         node = node->next){
        str8_list_pushf(arena, out, "chunk: %.4s; size=%u; off=0x%08llx\n",
                        (char*)&node->id, node->size, node->off);
        switch (node->id){
            case WAVE_ID_fmt:
            {
                WAVE_FormatData fmt_data = wave_format_data_from_fmt_chunk(node, data);
                str8_list_pushf(arena, out,
                                " tag:              %u\n"
                                " channel_count:    %u\n"
                                " bytes_per_sample: %u\n"
                                " samples_per_second: %u\n"
                                " byte_stride_per_value: %u\n"
                                " bits_per_value:   %u\n"
                                " channel_mask:     %04x\n"
                                " sub_format:       %.16s\n",
                                fmt_data.format_tag,
                                fmt_data.channel_count,
                                fmt_data.bytes_per_sample,
                                fmt_data.samples_per_second,
                                fmt_data.byte_stride_per_value,
                                fmt_data.bits_per_value,
                                fmt_data.channel_mask,
                                fmt_data.sub_format);
            }break;
        }
    }
    arena_release_scratch(&scratch);
}
