#include "meta.h"

// Guitar Hero III Mobile .bar

enum {BAR_KEY_LENGTH = 16};

// don't know if this is unique, but seems accurate
static const uint8_t bar_key[BAR_KEY_LENGTH] =
   {0xbd,0x14,0x0e,0x0a,0x91,0xeb,0xaa,0xf6,
    0x11,0x44,0x17,0xc2,0x1c,0xe4,0x66,0x80};

typedef struct _BARSTREAM
{
    STREAMFILE sf;
    STREAMFILE *real_file;
} BARSTREAM;

STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file);

VGMSTREAM * init_vgmstream_gh3_bar(STREAMFILE *streamFile) {
    VGMSTREAM * vgmstream = NULL;
    // don't close, this is just the source streamFile wrapped
    STREAMFILE* streamFileBAR = NULL;
    char filename[PATH_LIMIT];
    off_t start_offset;
    off_t ch2_start_offset;
    int loop_flag;
	int channel_count;
    long file_size;


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

    /* decryption wrapper for header reading */
    streamFileBAR = wrap_bar_STREAMFILE(streamFile);
    if (!streamFileBAR) goto fail;

    file_size = get_streamfile_size(streamFileBAR);

    /* check header */
    if (read_32bitBE(0x00,streamFileBAR) != 0x11000100 ||
        read_32bitBE(0x04,streamFileBAR) != 0x01000200) goto fail;
    if (read_32bitLE(0x50,streamFileBAR) != file_size) goto fail;

    start_offset = read_32bitLE(0x18,streamFileBAR);
    if (0x54 != start_offset) goto fail;
    ch2_start_offset = read_32bitLE(0x48,streamFileBAR);
    if (ch2_start_offset >= file_size) goto fail;

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

    /* fill in the vital statistics */
    vgmstream->channels = channel_count;
    vgmstream->sample_rate = 11025;
    vgmstream->coding_type = coding_IMA;
    vgmstream->num_samples = (file_size-ch2_start_offset)*2;
    vgmstream->layout_type = layout_none;
    vgmstream->meta_type = meta_GH3_BAR;

    {
        STREAMFILE *file1, *file2;
        file1 = streamFileBAR->open(streamFileBAR,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
        if (!file1) goto fail;
        file2 = streamFileBAR->open(streamFileBAR,filename,STREAMFILE_DEFAULT_BUFFER_SIZE);
        if (!file2)
        {
            close_streamfile(file1);
            goto fail;
        }
        vgmstream->ch[0].streamfile = file1;
        vgmstream->ch[1].streamfile = file2;
        vgmstream->ch[0].channel_start_offset=
            vgmstream->ch[0].offset=start_offset;
        vgmstream->ch[1].channel_start_offset=
            vgmstream->ch[1].offset=ch2_start_offset;
    }

    // discard our decrypt wrapper, without closing the original streamfile
    free(streamFileBAR);

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

static size_t read_bar(BARSTREAM *streamFile, uint8_t *dest, off_t offset, size_t length)
{
    off_t i;
    size_t read_length =
        streamFile->real_file->read(streamFile->real_file, dest, offset, length);

    for (i = 0; i < read_length; i++)
    {
        dest[i] = dest[i] ^ bar_key[(i+offset)%BAR_KEY_LENGTH];
    }

    return read_length;
}

static size_t get_size_bar(BARSTREAM *streamFile)
{
    return streamFile->real_file->get_size(streamFile->real_file);
}

static size_t get_offset_bar(BARSTREAM *streamFile)
{
    return streamFile->real_file->get_offset(streamFile->real_file);
}

static void get_name_bar(BARSTREAM *streamFile, char *name, size_t length)
{
    return streamFile->real_file->get_name(streamFile->real_file, name, length);
}

static void get_realname_bar(BARSTREAM *streamFile, char *name, size_t length)
{
    return streamFile->real_file->get_realname(streamFile->real_file, name, length);
}

STREAMFILE *open_bar(BARSTREAM *streamFile, const char * const filename, size_t buffersize)
{
    STREAMFILE *newfile = streamFile->real_file->open(
            streamFile->real_file,filename,buffersize);
    if (!newfile)
        return NULL;

    return wrap_bar_STREAMFILE(newfile);
}

static void close_bar(BARSTREAM *streamFile)
{
    streamFile->real_file->close(streamFile->real_file);
    free(streamFile);
    return;
}

#ifdef PROFILE_STREAMFILE
size_t get_bytes_read_bar(BARSTREAM *streamFile)
{
    return streamFile->real_file->get_bytes_read(streamFile->real_file);
}

int (*get_error_count)(BARSTREAM *streamFile)
{
    return streamFile->real_file->get_error_count(streamFile->real_file);
}
#endif

STREAMFILE *wrap_bar_STREAMFILE(STREAMFILE *file)
{
    BARSTREAM *streamfile = malloc(sizeof(BARSTREAM));

    if (!streamfile)
        return NULL;
    
    memset(streamfile, 0, sizeof(BARSTREAM));

    streamfile->sf.read = (void*)read_bar;
    streamfile->sf.get_size = (void*)get_size_bar;
    streamfile->sf.get_offset = (void*)get_offset_bar;
    streamfile->sf.get_name = (void*)get_name_bar;
    streamfile->sf.get_realname = (void*)get_realname_bar;
    streamfile->sf.open = (void*)open_bar;
    streamfile->sf.close = (void*)close_bar;
#ifdef PROFILE_STREAMFILE
    streamfile->sf.get_bytes_read = get_bytes_read_bar;
    streamfile->sf.get_error_count = get_error_count_bar;
#endif

    streamfile->real_file = file;

    return &streamfile->sf;
}
