#include <windows.h>
#include <stdio.h>
#include "vbrinfo.h"
#ifdef _DEBUG
#include <assert.h>
#endif

static inline DWORD bswap32(DWORD x)
{
    return ((x << 24) & 0xff000000 ) |
           ((x <<  8) & 0x00ff0000 ) |
           ((x >>  8) & 0x0000ff00 ) |
           ((x >> 24) & 0x000000ff );
    //x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0x00FF00FF);
    //return (x >> 16) | (x << 16);
}
static inline WORD bswap16(WORD x)
{
    return ((x << 8) & 0xFF00) | ((x >> 8) & 0x00FF);
}
static inline int ExtractI4(unsigned char *buf)
{
    return bswap32(*(DWORD*)buf);
}
static inline short ExtractI2(unsigned char *buf)
{
    return bswap16(*(WORD*)buf);
}

const char VBRTagXing[] = "Xing";
const char VBRTagInfo[] = "Info";
const char VBRTagVBRI[] = "VBRI";

////////////////////////////////////////////////////////
int GetVbrInfo(VBRTAGINFO *pTagInfo,  unsigned char *buf)
{
    int  head_flags;
    int  h_bitrate, h_id, h_mode, h_sr_index;
    int  enc_delay, enc_padding;
    static const int br_table[3][16] = {
        { 0,  8, 16, 24, 32, 40, 48, 56,  64,  80,  96, 112, 128, 144, 160, -1 },
        { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 },
        { 0,  8, 16, 24, 32, 40, 48, 56,  64,  80,  96, 112, 128, 144, 160, -1 },
    };
    ZeroMemory(pTagInfo, sizeof(VBRTAGINFO));
    h_id       = (buf[1] >> 3) & 1;
    h_sr_index = (buf[2] >> 2) & 3;
    h_mode     = (buf[3] >> 6) & 3;

    if( h_id )
    {
        if( h_mode != 3 )   buf+=(32+4);
        else                buf+=(17+4);
    }
    else
    {
        if( h_mode != 3 ) buf+=(17+4);
        else              buf+=(9+4);
    }
    h_bitrate  = ((buf[2]>>4)&0xf);
    h_bitrate  = br_table[h_id][h_bitrate];

    if(*(DWORD*)buf != DWORD_Xing &&
       *(DWORD*)buf != DWORD_Info &&
       *(DWORD*)buf != DWORD_VBRI ){
        return 0;
    }
    pTagInfo->header_dword = *(DWORD*)buf;
    pTagInfo->enc_delay = pTagInfo->enc_padding = -1;
    if(*(DWORD*)buf == DWORD_VBRI){
        //VBRI
        pTagInfo->vbri_version = ExtractI2(&buf[4]);
        pTagInfo->vbri_delay = ExtractI2(&buf[6]);
        pTagInfo->vbri_quality =  ExtractI2(&buf[8]);
        pTagInfo->bytes =    ExtractI4(&buf[10]);
        pTagInfo->frames =   ExtractI4(&buf[14]);
        return 1;
    }

    buf+=4;

    head_flags = pTagInfo->flags = ExtractI4(buf); buf+=4;

    if( head_flags & FRAMES_FLAG )
    {
        pTagInfo->frames   = ExtractI4(buf); buf+=4;
    }

    if( head_flags & BYTES_FLAG )
    {
        pTagInfo->bytes = ExtractI4(buf); buf+=4;
    }

    if( head_flags & TOC_FLAG )
    {
        //int i;
        //for(i=0;i<NUMTOCENTRIES;i++)
        //    pTagInfo->toc[i] = buf[i];
        //}
        buf+=NUMTOCENTRIES;
    }

    pTagInfo->quality = -1;

    if( head_flags & VBR_SCALE_FLAG )
    {
        pTagInfo->quality = ExtractI4(buf); buf+=4;
    }
    memcpy(pTagInfo->lame_str, buf, 20);
    if(memcmp(buf, "LAME", 4) != 0){//Xing ヘッダがついているが LAME タグではない
        pTagInfo->lame_str[0] = 0;
        return 1;
    }
    //バージョンを調べる。3.90 より古いとエンコーダ名の文字列は
    //20 バイトまで有効だが文字列以降のデータは無効
    //バージョン文字列は 4 バイト目から X.XX のようになっていることが前提
    if((buf[4] < '3') || //3.0 未満
       (buf[4] > '9' || buf[5] != '.' || !isdigit(buf[6]) || !isdigit(buf[7])) || //バージョン文字列が不正
       (buf[4] == '3' && buf[6] < '9' && !isdigit(buf[8])) ){//3.0 以上だが 3.90 より古い(3.100 は 3.90 より新しい)
        //3.90 未満 or バージョン文字列が不正
        pTagInfo->lame_str[20] = 0;
        pTagInfo->rev_method = 0xFF;
        return 1;
    }
    //3.90 以降
    pTagInfo->lame_str[9] = 0;//文字列は 9 バイトまで有効
    //revision / method
    //pTagInfo->revision = (buf[9] >> 4);  //上位4bit
    //pTagInfo->method =   (buf[9] & 0x0F);//下位4bit
    pTagInfo->rev_method = buf[9];
    //lowpass filter
    pTagInfo->lowpass = buf[10];
    //replaygain
    pTagInfo->track_peak = ExtractI4(&buf[11]);
    pTagInfo->track_gain = ExtractI2(&buf[15]);
    pTagInfo->album_gain = ExtractI2(&buf[17]);
    //encoding flag / ath type
    //pTagInfo->enc_flags = (buf[19] >> 4);  //上位4bit
    //pTagInfo->ath_type =  (buf[19] & 0x0F);//下位4bit
    pTagInfo->enc_flags_ath_type = buf[19];
    //bitrate
    pTagInfo->bitrate = buf[20];
    //enc_delay/enc_padding
    buf+=21;
    enc_delay = buf[0] << 4;
    enc_delay += buf[1] >> 4;
    enc_padding= (buf[1] & 0x0F)<<8;
    enc_padding += buf[2];
    // check for reasonable values (this may be an old Xing header,
    // not a INFO tag)
    if (enc_delay<0 || enc_delay > 3000) enc_delay=-1;
    if (enc_padding<0 || enc_padding > 3000) enc_padding=-1;
    pTagInfo->enc_delay=enc_delay;
    pTagInfo->enc_padding=enc_padding;
    //misc
    buf += 3;
    pTagInfo->misc = buf[0];
    //mp3 gain
    pTagInfo->mp3gain = buf[1];
    //surround_preset
    pTagInfo->sur_preset = ExtractI2(&buf[2]);
    //music_length
    pTagInfo->music_length = ExtractI4(&buf[4]);
    //crc
    pTagInfo->music_crc = ExtractI2(&buf[8]);
    pTagInfo->tag_crc = ExtractI2(&buf[10]);

    return 1;
}
