/* (PD) 2001 The Bitzi Corporation
 * Please see file COPYING or http://bitzi.com/publicdomain 
 * for more info.
 *
 * This code is based on id3v1.cpp and id3v2.cpp from FreeAmp. EMusic.com
 * has released this code into the Public Domain. 
 * (Thanks goes to Brett Thomas, VP Engineering Emusic.com)
 *
 * $Id: id3.c,v 1.3 2001/04/12 21:01:16 robert Exp $
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#ifdef WIN32
#include <winsock.h>
#else
#include <netinet/in.h>
#endif

#include "id3.h"

const int supportedVersion = 3;
const unsigned frameHeaderSize = 10;

ID3Info *read_ID3v1_tag(const char* fileName, ID3Info *info);
ID3Info *read_ID3v2_tag(const char* fileName);

typedef struct _ID3Header
{
   char          tag[3];
   unsigned char versionMajor;
   unsigned char versionRevision;
   unsigned char flags;
   unsigned char size[4];
} ID3Header;
typedef struct _FrameHeader
{
   char           tag[4];
   unsigned int   size;
   unsigned short flags;
} FrameHeader;

typedef struct id3v1_0 
{
    char id[3];
    char title[30];
    char artist[30];
    char album[30];
    char year[4];
    char comment[30];
    unsigned char genre;

} id3v1_0;

typedef struct id3v1_1 
{
    char id[3];
    char title[30];
    char artist[30];
    char album[30];
    char year[4];
    char comment[28];
    char zero;
    char track;
    unsigned char genre;
} id3v1_1;

typedef struct id3v1 {
    union {
        struct id3v1_0 v1_0;
        struct id3v1_1 v1_1;
    } id3;
} id3v1;

#define v1_0 id3.v1_0
#define v1_1 id3.v1_1

void delete_ID3_tag(ID3Info *info)
{
    if (!info)
       return;

    if (info->artist)
       free(info->artist);
    if (info->album)
       free(info->album);
    if (info->title)
       free(info->title);
    if (info->genre)
       free(info->genre);
    if (info->year)
       free(info->year);
    if (info->encoder)
       free(info->encoder);
    if (info->tracknumber)
       free(info->tracknumber);

    free(info);
}

void handle_frame(char *tag, char *frameData, ID3Info *info)
{
    char tagName[5];

    if (frameData == NULL || strlen(frameData) == 0)
        return;

    strncpy(tagName, tag, 4);
    tagName[4] = 0;

    if (strcmp(tagName, "TIT2") == 0)
        info->title = strdup(frameData);

    if (strcmp(tagName, "TALB") == 0)
        info->album = strdup(frameData);

    if (strcmp(tagName, "TPE1") == 0)
        info->artist = strdup(frameData);

    if (strcmp(tagName, "TYER") == 0)
        info->year = strdup(frameData);

    if (strcmp(tagName, "TSIZ") == 0)
        info->genre = strdup(frameData);

    if (strcmp(tagName, "TRCK") == 0)
        info->tracknumber = strdup(frameData);

    if (strcmp(tagName, "TSSE") == 0)
        info->encoder = strdup(frameData);
}

ID3Info *read_ID3_tag(const char *fileName)
{
    return read_ID3v1_tag(fileName, read_ID3v2_tag(fileName));
}

ID3Info *read_ID3v2_tag(const char* fileName)
{
    FILE       *inFile;
    char        buffer[1024], *frameData;
    ID3Header   head;
    FrameHeader frame;
    ID3Info    *info = NULL;
    int         ret;
    int         size;

    inFile = fopen(fileName, "rb");
    if (inFile == NULL)
        return NULL;

    ret = fread(&head, 1, sizeof(ID3Header), inFile);
    if (ret != sizeof(ID3Header))
    {
        fclose(inFile);
        return NULL;
    }

    if (strncmp(head.tag, "ID3", 3))
    {
        fclose(inFile);
        return NULL;
    }

    if (head.versionMajor != supportedVersion)
    {
        fclose(inFile);
        return NULL;
    }
    size = ( head.size[3] & 0x7F       ) |
           ((head.size[2] & 0x7F) << 7 ) |
           ((head.size[1] & 0x7F) << 14) |
           ((head.size[0] & 0x7F) << 21);
    if (head.flags & (1 << 6))
    {
        unsigned extHeaderSize;

        if (fread(&extHeaderSize, 1, sizeof(int), inFile) != sizeof(int))
        {
            fclose(inFile);
            return NULL;
        }
        if (fread(buffer, 1, extHeaderSize, inFile) != extHeaderSize)
        {
            fclose(inFile);
            return NULL;
        }
    }

    info = malloc(sizeof(ID3Info));
    memset(info, 0, sizeof(ID3Info));
    for(; size > 0;)
    {
        if (fread(&frame, 1, frameHeaderSize, inFile) != frameHeaderSize)
        {
            free(info);
            fclose(inFile);
            return NULL;
        }
        frame.size = ntohl(frame.size);
        frameData = malloc(frame.size + 1);
        if (fread(frameData, 1, frame.size, inFile) != frame.size)
        {
            free(info);
            free(frameData);
            fclose(inFile);
            return NULL;
        }
        frameData[frame.size] = 0;
        handle_frame(frame.tag, &frameData[1], info);

        free(frameData);
        size -= sizeof(FrameHeader) + frame.size;
    }

    fclose(inFile);

    return info;
}

void remove_trailing_spaces(char* string)
{
	char* cp = &(string[strlen(string)]);

	do 
    {
	    *cp = '\0';
	    cp--;
	}while ((*cp == ' ') && (cp >= string));
}

ID3Info *read_ID3v1_tag(const char* fileName, ID3Info *info)
{
    id3v1  id3;
    FILE  *fp;
    char   buffer[31];

    fp = fopen(fileName, "rb");
    if (fp == NULL)
        return info;

    if (fseek(fp, -128, SEEK_END))
    {
        fclose(fp);
        return info;
    }
                    
    if (fread(&id3, 1, 128, fp) != 128)
    {
        fclose(fp);
        return info;
    }

    if(strncmp(id3.v1_0.id, "TAG", 3))
    {
        fclose(fp);
        return info;
    }
         
    if (info == NULL)
    {
        info = malloc(sizeof(ID3Info));
        memset(info, 0, sizeof(ID3Info));
    }

    strncpy(buffer, id3.v1_0.artist, 30);
    buffer[30] = 0;
    remove_trailing_spaces(buffer);
    if (strlen(buffer) && info->artist == NULL)
        info->artist = strdup(buffer);

    strncpy(buffer, id3.v1_0.album, 30);
    buffer[30] = 0;
    remove_trailing_spaces(buffer);
    if (strlen(buffer) && info->album == NULL)
        info->album = strdup(buffer);

    strncpy(buffer, id3.v1_0.title, 30);
    buffer[30] = 0;
    remove_trailing_spaces(buffer);
    if (strlen(buffer) && info->title == NULL)
        info->title = strdup(buffer);

    strncpy(buffer, id3.v1_0.year,4);
    buffer[4] = 0;
    remove_trailing_spaces(buffer);
    if (strlen(buffer) && info->year == NULL)
    {
        int check;

        if (sscanf(buffer, "%d", &check) == 1 && check >= 1000 && check < 3000)
           info->year = strdup(buffer);
    }

    if( id3.v1_1.zero == 0x00 && id3.v1_1.track != 0x00)
    {
        sprintf(buffer, "%d", id3.v1_1.track);
        if (strlen(buffer) && info->tracknumber == NULL)
            info->tracknumber = strdup(buffer);
    }

    if (id3.v1_0.genre != 255)
    {
        sprintf(buffer, "%u", (unsigned int)id3.v1_0.genre);
        if (strlen(buffer) && info->genre == NULL)
           info->genre = strdup(buffer);
    }

    fclose(fp);
    return info;
}

