/*
 * libkarma/mp3.c
 *
 * Copyright (c) Cedric Tefft <cedric@earthling.net> 2000-2001
 *           (c) Frank Zschockelt <libkarma@freakysoft.de> 2004
 *
 * You may distribute and modify this program under the terms of 
 * the GNU GPL, version 2 or later.
 *    
 ***************************************************************************
 * This file is based in part on:
 * MP3Info 0.5 by Ricardo Cerqueira <rmc@rccn.net>
 * MP3Stat 0.9 by Ed Sweetman <safemode@voicenet.com> and 
 *                Johannes Overmann <overmann@iname.com>
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include "mp3.h"


int layer_tab[4]= {0, 3, 2, 1};

int frequencies[3][4] = {
    {22050,24000,16000,50000},  /* MPEG 2.0 */
    {44100,48000,32000,50000},  /* MPEG 1.0 */
    {11025,12000,8000,50000}    /* MPEG 2.5 */
};

int bitrate[2][3][14] = { 
    { /* MPEG 2.0 */
        {32,48,56,64,80,96,112,128,144,160,176,192,224,256},  /* layer 1 */
        {8,16,24,32,40,48,56,64,80,96,112,128,144,160},       /* layer 2 */
        {8,16,24,32,40,48,56,64,80,96,112,128,144,160}        /* layer 3 */
    },

    { /* MPEG 1.0 */
        {32,64,96,128,160,192,224,256,288,320,352,384,416,448}, /* layer 1 */
        {32,48,56,64,80,96,112,128,160,192,224,256,320,384},    /* layer 2 */
        {32,40,48,56,64,80,96,112,128,160,192,224,256,320}      /* layer 3 */
    }
};

int frame_size_index[] = {24000, 72000, 72000};

static int frame_length(mp3header *header);
static int get_header(FILE *file,mp3header *header);
static int sameConstant(mp3header *h1, mp3header *h2);
static int get_id3(mp3info *mp3);
static char *pad(char *string, int length);
static char *unpad(char *string);
static int get_first_header(mp3info *mp3,long startpos);

int get_mp3_info(mp3info *mp3)
{
    int l;
    int bitrate,lastrate;
    int counter=0;
    struct stat filestat;
    off_t sample_pos;


    stat(mp3->filename,&filestat);
    mp3->datasize=filestat.st_size;
    get_id3(mp3);

    if(get_first_header(mp3,0L)) {
        mp3->offset=ftell(mp3->file);
        lastrate=15-mp3->header.bitrate;
        while((counter < NUM_SAMPLES) && lastrate) {
            sample_pos=(counter*(mp3->datasize/NUM_SAMPLES+1))+mp3->offset;
            if(get_first_header(mp3,sample_pos)) {
                bitrate=15-mp3->header.bitrate;
            } else {
                bitrate=-1;
            }

            if(bitrate != lastrate) {
                mp3->vbr=1;
                counter=NUM_SAMPLES;
            }
            lastrate=bitrate;
            counter++;

        }
        mp3->frames=(mp3->datasize-mp3->offset)/(l=frame_length(&mp3->header));
        mp3->ms = (int)(1000*(float)(frame_length(&mp3->header)*mp3->frames)/
                  (float)(header_bitrate(&mp3->header)*125)+0.5);
        mp3->vbr_average = header_bitrate(&mp3->header);
    }

    return 0;
}


static int get_first_header(mp3info *mp3, long startpos) 
{
    int k, l=0,c;
    mp3header h, h2;
    long valid_start=0;

    fseek(mp3->file,startpos,SEEK_SET);
    while (1) {
        while((c=fgetc(mp3->file)) != 255 && (c != EOF));
        if(c == 255) {
            ungetc(c,mp3->file);
            valid_start=ftell(mp3->file);
            if((l=get_header(mp3->file,&h))) {
                fseek(mp3->file,l-FRAME_HEADER_SIZE,SEEK_CUR);
                for(k=1; (k < MIN_CONSEC_GOOD_FRAMES) &&
                         (mp3->datasize-ftell(mp3->file) >= FRAME_HEADER_SIZE);
                         k++) {
                    if(!(l=get_header(mp3->file,&h2))) break;
                    if(!sameConstant(&h,&h2)) break;
                    fseek(mp3->file,l-FRAME_HEADER_SIZE,SEEK_CUR);
                }
                if(k == MIN_CONSEC_GOOD_FRAMES) {
                    fseek(mp3->file,valid_start,SEEK_SET);
                    memcpy(&(mp3->header),&h2,sizeof(mp3header));
                    mp3->header_isvalid=1;
                    return 1;
                } 
            }
        } else {
            return 0;
        }
    }

    return 0;  
}

/* Get next MP3 frame header.
     Return codes:
     positive value = Frame Length of this header
     0 = No, we did not retrieve a valid frame header
 */

static int get_header(FILE *file,mp3header *header)
{
    unsigned char buffer[FRAME_HEADER_SIZE];
    int fl;

    if(fread(&buffer,FRAME_HEADER_SIZE,1,file)<1) {
        header->sync=0;
        return 0;
    }
    header->sync=(((int)buffer[0]<<4) | ((int)(buffer[1]&0xE0)>>4));
    if(buffer[1] & 0x10) header->version=(buffer[1] >> 3) & 1;
    else header->version=2;
    header->layer=(buffer[1] >> 1) & 3;
    if((header->sync != 0xFFE) || (header->layer != 1)) {
        header->sync=0;
        return 0;
    }
    header->crc=buffer[1] & 1;
    header->bitrate=(buffer[2] >> 4) & 0x0F;
    header->freq=(buffer[2] >> 2) & 0x3;
    header->padding=(buffer[2] >>1) & 0x1;

    return ((fl=frame_length(header)) >= MIN_FRAME_SIZE ? fl : 0); 
}

static int frame_length(mp3header *header) {
    return header->sync == 0xFFE ? 
        (frame_size_index[3-header->layer]*((header->version&1)+1)*
        header_bitrate(header)/header_frequency(header))+
        header->padding : 1;
}

int header_layer(mp3header *h) {return layer_tab[h->layer];}

int header_bitrate(mp3header *h) {
    return bitrate[h->version & 1][3-h->layer][h->bitrate>0 ? h->bitrate-1: 0];
}

int header_frequency(mp3header *h) {
    return frequencies[h->version][h->freq];
}

static int sameConstant(mp3header *h1, mp3header *h2) {
    if((*(uint*)h1) == (*(uint*)h2)) return 1;

    if((h1->version == h2->version) &&
       (h1->layer   == h2->layer  ) &&
       (h1->crc     == h2->crc    ) &&
       (h1->freq    == h2->freq   ))
        return 1;
    else return 0;
}


static int get_id3(mp3info *mp3) {
    int retcode=0;
    char fbuf[4];

    if(mp3->datasize >= 128) {
        if(fseek(mp3->file, -128, SEEK_END )) {
            fprintf(stderr, "ERROR: Couldn't read last 128 bytes of %s!!\n",
                    mp3->filename);
            retcode |= 4;
        } else {
            fread(fbuf,1,3,mp3->file); fbuf[3] = '\0';
            mp3->id3.genre[0]=255;

            if (!strcmp((const char *)"TAG",(const char *)fbuf)) {
                mp3->id3_isvalid=1;
                mp3->datasize -= 128;
                fseek(mp3->file, -125, SEEK_END);
                fread(mp3->id3.title,1,30,mp3->file);
                mp3->id3.title[30] = '\0';
                fread(mp3->id3.artist,1,30,mp3->file);
                mp3->id3.artist[30] = '\0';
                fread(mp3->id3.album,1,30,mp3->file);
                mp3->id3.album[30] = '\0';
                fread(mp3->id3.year,1,4,mp3->file);
                mp3->id3.year[4] = '\0';
                fread(mp3->id3.comment,1,30,mp3->file);
                mp3->id3.comment[30] = '\0';
                if(mp3->id3.comment[28] == '\0') {
                    mp3->id3.track[0] = mp3->id3.comment[29];
                }
                fread(mp3->id3.genre,1,1,mp3->file);
                unpad(mp3->id3.title);
                unpad(mp3->id3.artist);
                unpad(mp3->id3.album);
                unpad(mp3->id3.year);
                unpad(mp3->id3.comment);
            }
        }
    }
    return retcode;
}

static char *pad(char *string, int length) {
    int l;

    l=strlen(string);
    while(l<length) {
        string[l] = ' ';
        l++;
    }

    string[l]='\0';
    return string;
}

/* Remove trailing whitespace from the end of a string */

static char *unpad(char *string) {
    char *pos=string+strlen(string)-1;
    while(isspace(pos[0])) (pos--)[0]=0;
    return string;
}

/*
 * Build an ID3 tag and write it to the file
 * Returns positive int on success, 0 on failure
 */
#define TEXT_FIELD_LEN 30
#define INT_FIELD_LEN 4
int write_tag(mp3info *mp3) {

    char buf[129];

    strcpy(buf,"TAG");
    pad(mp3->id3.title,TEXT_FIELD_LEN);
    strncat(buf,mp3->id3.title,TEXT_FIELD_LEN);
    pad(mp3->id3.artist,TEXT_FIELD_LEN);
    strncat(buf,mp3->id3.artist,TEXT_FIELD_LEN);
    pad(mp3->id3.album,TEXT_FIELD_LEN);
    strncat(buf,mp3->id3.album,TEXT_FIELD_LEN);
    pad(mp3->id3.year,INT_FIELD_LEN);
    strncat(buf,mp3->id3.year,INT_FIELD_LEN);
    pad(mp3->id3.comment,TEXT_FIELD_LEN);
    strncat(buf,mp3->id3.comment,TEXT_FIELD_LEN);
    strncat(buf,(char*)mp3->id3.genre,1);
    if (mp3->id3.track[0] != '\0') {
        buf[125]='\0';
        buf[126]=mp3->id3.track[0];
    }
    fseek(mp3->file,-128*mp3->id3_isvalid,SEEK_END);
    return (int)fwrite(buf,1,128,mp3->file);
}
