/*
 * $Id: helper.c,v 1.29 2002/12/08 14:10:34 alphix Exp $
 *
 * This file is part of Ample.
 *
 * Ample is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Ample is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ample; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <string.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdarg.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include "ample.h"
#include "entries.h"
#include "client.h"
#include "helper.h"

/* Used to signal errors in hostname lookups */
extern int h_errno;

/* Some utility macros used in getsonglength() */
#define CHECKSYNC(x) (((x >> 21) & 0x07FF) == 0x7FF)
#define BYTE0(x) ((x >> 24) & 0xFF)
#define BYTE1(x) ((x >> 16) & 0xFF)
#define BYTE2(x) ((x >> 8)  & 0xFF)
#define BYTE3(x) ((x >> 0)  & 0xFF)

/* Table of bitrates for MP3 files, all values in kilo.
 * Indexed by version, layer and value of bit 15-12 in header.
 */
const int bitrate_table[2][3][16] =
{
	{
		{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0},
		{0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0},
		{0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0}
	},
	{
		{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0},
		{0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0},
		{0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}
	}
};

/* Table of samples per frame for MP3 files.
 * Indexed by layer.
 */
const int bs[4] = {0, 384, 1152, 1152};


/* Table of sample frequency for MP3 files.
 * Indexed by version and layer.
 */
const int freqtab[2][4] =
{
	{44100, 48000, 32000, 0},
	{22050, 24000, 16000, 0},
};


/*
 * Allows children to send status reports to the parent process.
 *
 * Argument: to - the udp socket to send the message to
 *           fmt - the format string of the message
 *           argp - the list of arguments to complement the format string
 *
 * Returns: void
 */
void 
sendstatusmsg(int to, char *fmt, ...)
{
	char buffer[1000];
	int length;
	va_list argp;

	if(to >= 0) {
		va_start(argp, fmt);
		length = vsnprintf(buffer, 1000, fmt, argp);
		sprintf(&buffer[992], "<TRUNC>");
		send(to, buffer, strlen(buffer) + 1, 0);
		va_end(argp);
	}
}


/*
 * Generic function to deal with log, debug and death messages.
 *
 * Argument: type - the type of the message (see header)
 *           fmt - the format string of the message
 *           argp - the list of arguments to complement the format string
 *
 * Returns: void
 */
static void 
printlogmsg(int type, char *fmt, va_list argp)
{
	char buffer[1000];
	int length;
	
	length = vsnprintf(buffer, 1000, fmt, argp);
	sprintf(&buffer[992], "<TRUNC>");

#ifdef HAVE_SYSLOG_H
	if(!gconf.trace) {
		if(type == TYPE_LOG) {
			syslog(LOG_INFO, "%s", buffer);
		} else if(type == TYPE_DEBUG) {
			syslog(LOG_DEBUG, "%s", buffer);
		} else if(type == TYPE_DIE) {
			if(errno != 0)
				syslog(LOG_ERR, "died - %s, errno: %s", buffer, 
				       strerror(errno));
			else
				syslog(LOG_ERR, "died - %s", buffer);
			exit(EXIT_FAILURE);
		}
		return;
	}
#endif

	if(type == TYPE_LOG) {
		printf("LOG[%d]: %s", getpid(), buffer);
	} else if(type == TYPE_DEBUG) {
		printf("DBG[%d]: %s", getpid(), buffer);
	} else if(type == TYPE_DIE) {
		if(errno != 0)
			printf("DIE[%d]: (errno: %s) %s", getpid(), 
			       strerror(errno), buffer);
		else
			printf("DIE[%d]: %s", getpid(), buffer);
		exit(EXIT_FAILURE);
	}
	fflush(stdout);
}


/*
 * Wrapper function to printlogmsg that is used to print ordinary log messages.
 *
 * Arguments: fmt - the format string of the message
 *            ... - the variables to complement the format string
 *
 * Returns: void
 */
void 
logmsg(char *fmt, ...)
{
	va_list argp;

	va_start(argp, fmt);
	printlogmsg(TYPE_LOG, fmt, argp);
	va_end(argp);
}


/*
 * Wrapper function to printlogmsg that is used to print debug messages if
 * the debug level config option is high enough.
 *
 * Arguments: priority - the level of the debug message, higher number
 *                       generally means more verbose debugging, if this
 *                       number is higher than the debuglevel config option,
 *                       nothing is printed
 *            fmt - the format string of the message
 *            ... - the variables to complement the format string
 *
 * Returns: void
 */
void 
debug(int priority, char *fmt, ...) 
{
	va_list argp;

	if(priority > gconf.debuglevel)
		return;

	va_start(argp, fmt);
	printlogmsg(TYPE_DEBUG, fmt, argp);
	va_end(argp);
}


/*
 * Wrapper function to printlogmsg that is used to print death messages 
 * and then exit.
 *
 * Arguments: fmt - the format string of the message
 *            ... - the variables to complement the format string
 *
 * Returns: void
 */
void 
die(char *fmt, ...) 
{
	va_list argp;
	
	va_start(argp, fmt);
	printlogmsg(TYPE_DIE, fmt, argp);
	va_end(argp);
}


static void 
expandmalloc(void **old, size_t *size, int line) {
	*size = ((*size) * 2);
	debug(4, "called from %i, gonna realloc with size %i and old %p\n", 
	      line, *size, *old);
	*old = realloc(*old, *size);
	if(*old == NULL)
		die("realloc failed\n");
}


/*
 * Implements the behaviour of GNU getcwd using standard getcwd
 * (GNU cwd auto-malloc's a buffer to hold the result)
 *
 * Argument: none
 *
 * Returns: A pointer to the newly allocated buffer
 */
char * 
mgetcwd()
{
	size_t size = 100;
	char *buffer = (char *)malloc(size);

	while (TRUE) {
		if(getcwd (buffer, size) == buffer)
			return buffer;
		if(errno != ERANGE)
			die("getcwd");
		expandmalloc((void **)&buffer, &size, __LINE__);
	}
}


/*
 * The opposite of atoi, converts an integer to an ASCII string.
 *
 * Arguments: integer - the integer to convert
 *
 * Returns: an ASCII string stored in malloc:ed memory
 */
char * 
itoa(int integer) {
	int tmp = integer;
	int size;
	char *result;

	if(integer == 0)
		size = 2;
	else if(integer < 0)
		size = 2;
	else if(integer > 0)
		size = 1;

	while(tmp) {
		tmp /= 10;
		size++;
	}

	result = malloc(size);
	snprintf(result, size, "%i", integer);
	return result;
}


/*
 * Encode function common for both HTML and URL encodings.
 *
 * Arguments: toencode - string to convert
 *            url - if TRUE, urlencode the string, otherwise htmlencode it
 *
 * Returns: an encoded ASCII string stored in malloc:ed memory
 */
static char *
commonencode(char *toencode, bool url) {
	char *validchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	char *urlprefix = "%";
	char *htmlprefix = "&#x";
	char *urlsuffix = "";
	char *htmlsuffix = ";";
	char *prefix;
	char *suffix;
	char *tmp;
	char *tmp2;
	char *retval;
	char buffer[3];
	size_t size = 100;
	size_t used = 1;
	
	if(url) {
		prefix = urlprefix;
		suffix = urlsuffix;
	} else {
		prefix = htmlprefix;
		suffix = htmlsuffix;
	}

	retval = malloc(size);
	if(retval == NULL)
		die("malloc failed\n");
	*retval = '\0';

	for(tmp = toencode; *tmp != '\0'; tmp++) {
		if((size - used) < (strlen(prefix) + 2 + strlen(suffix)))
			expandmalloc((void **)&retval, &size, __LINE__);
		for(tmp2 = validchars; *tmp2 != '\0'; tmp2++) {
			if((*tmp) == (*tmp2))
				break;
		}
		
		if(*tmp2 != '\0') {
			buffer[0] = *tmp2;
			buffer[1] = '\0';
			strcat(retval, buffer);
			used++;
		} else {
			snprintf(buffer, 3, "%.2x", (int)(unsigned char)*tmp);
			strcat(retval, prefix);
			strcat(retval, buffer);
			strcat(retval, suffix);
			used += (strlen(prefix) + 2 + strlen(suffix));
		}
	}
	
	return(retval);
}


/*
 * urlencodes a string.
 *
 * Arguments: toencode - string to convert
 *
 * Returns: an encoded ASCII string stored in malloc:ed memory
 */
char *
urlencode(char *toencode) {
	return(commonencode(toencode, TRUE));
}


/*
 * htmlencodes a string.
 *
 * Arguments: toencode - string to convert
 *
 * Returns: an encoded ASCII string stored in malloc:ed memory
 */
char *
htmlencode(char *toencode) {
	return(commonencode(toencode, FALSE));
}


/*
 * Takes a string and replaces variables with their values, then returns
 * the adress of the new string to the client. This string will be
 * valid until replacevariables is called again and doesn't need to
 * be free:d. Used to create dynamic HTML pages.
 *
 * Arguments: input - the line to use as a base for the conversion
 *            cconf - session information
 *            entry - an (optional) entry to retrieve some of the
 *                    variable values from
 *
 * Returns: void
 */
char * 
replacevariables(char *input, struct client_config *cconf, mp3entry *entry)
{
	static char *result = NULL;

	char *str_nbsp = "&nbsp;";
	char *str_err = "Variable not allowed here";
	char *str_dir = "DIR";
	char *str_file = "FILE";
	char *str_sep = "@";

	char *start = input;
	char *end;
	char *variable;
	char *encvariable;

	/*
	 * VARIABLES USED:
	 * 
	 * Static: (Can be used anywhere)
	 * @SERVERNAME@
	 * @PORT@
	 * @PATH@
	 *
	 * Non-Static: (Can only be used if tmp defined)
	 * FILE:                           DIR:
	 * =====                           ====
	 * @NAME@   = tmp->name            tmp->name
	 * @URL@    = tmp->name            tmp->name + /index.html
	 * @LENGTH@ = tmp->length          "&nbsp;"
	 * @TITLE@  = tmp->title           "DIR"
	 * @SIZE@   = tmp->filesize        "&nbsp;"
	 * @TYPE@   = "FILE"               "DIR"
	 * @FPATH@  = tmp->path            "&nbsp;"
	 */
	char *variables[][2] = {
		{"@SERVERNAME@", gconf.servername},
		{"@PORT@", NULL},
		{"@PATH@", cconf->requestpath},
		{"@NAME@", str_err},
		{"@URL@", str_err},
		{"@LENGTH@", str_err},
		{"@TITLE@", str_err},
		{"@SIZE@", str_err},
		{"@TYPE@", str_err},
		{"@FPATH@", str_err},
		{NULL, NULL}
	};

	int i;
	size_t size = 100;
	int used = 1;

	/* Clean up after earlier invocations */
	if(result != NULL) {
		free(result);
		result = NULL;
	}

	result = malloc(size);
	if(result == NULL)
		die("malloc failed\n");
	*result = '\0';

	variables[1][1] = itoa(gconf.port);
	if(!entry) {
		/* Do nothing */
	} else if(IS_DIR(entry)) {
		variables[3][1] = entry->name;
		variables[4][1] = malloc(strlen(entry->name) + 
					 strlen("/index.html") + 1);
		sprintf(variables[4][1], "%s/index.html", entry->name);
		variables[5][1] = str_nbsp;
		variables[6][1] = str_dir;
		variables[7][1] = str_nbsp;
		variables[8][1] = str_dir;
		variables[9][1] = str_nbsp;
	} else {
		variables[3][1] = entry->name;
		variables[4][1] = entry->name;
		variables[5][1] = itoa(entry->length);
		variables[6][1] = entry->title;
		variables[7][1] = itoa(entry->filesize);
		variables[8][1] = str_file;
		variables[9][1] = entry->path;
	}

	while((end = strchr(start, '@')) != NULL) {
		debug(1, "end is %s\n", end);
		debug(1, "end is %p, start is %p, diff %i, used is %i, size is %i\n", end, start, (int)(end - start), used, size);
		while((used + end - start) > size) {
			expandmalloc((void **)&result, &size, __LINE__);
		}
		strncat(result, start, (end - start));
		used += (end - start);
		variable = NULL;
		
		for(i = 0; variables[i][0] != NULL; i++) {
			if(!strncmp(end, variables[i][0], 
				    strlen(variables[i][0]))) {
				start = end + strlen(variables[i][0]);
				variable = variables[i][1];
				if(!strncmp(variables[i][0], "@URL@", strlen(variables[i][0])))
					encvariable = urlencode(variable);
				else
					encvariable = htmlencode(variable);
			}
		}

		if(!variable) {
			start = end + 1;
			variable = str_sep;
			encvariable = str_sep;
		}
		
		while((used + strlen(encvariable)) > size)
			expandmalloc((void **)&result, &size, __LINE__);
		strcat(result, encvariable);
		used += strlen(encvariable);

		if(encvariable != str_sep)
			free(encvariable);
	}

	while((used + strlen(start)) > size)
		expandmalloc((void **)&result, &size, __LINE__);
	strcat(result, start);
	used += strlen(start);

	free(variables[1][1]);
	if(!entry) {
		/* Do nothing */
	} else if(IS_DIR(entry)) {
		free(variables[4][1]);
	} else {
		free(variables[5][1]);
		free(variables[7][1]);
	}

	return(result);
}


/*
 * Given a hostentry, finds the FQDN (Fully Qualified Domain Name)
 *
 * Arguments: host - the hostentry to base the search on
 *
 * Returns: the FQDN as a string stored in malloc:ed memory
 */
static char * 
findfqdn(struct hostent *host)
{
	int i;

	if (strchr(host->h_name, '.'))
		return strdup(host->h_name);
	
	for (i=0; host->h_aliases[i] != NULL; i++) {
		if (!strchr(host->h_aliases[i], '.'))
			continue;
		else if (!strncasecmp(host->h_aliases[i], host->h_name, strlen(host->h_name)))
			return strdup(host->h_aliases[i]);
	}
	
        return NULL;
}


/*
 * Finds the name (or address) which clients should use when connecting to
 * the server.
 *
 * Arguments: none
 *
 * Returns: the name or address as a string stored in malloc:ed memory
 */
char * 
getservername() 
{
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif

	char buf[MAXHOSTNAMELEN + 1];
	char *hostname = NULL;
	struct hostent *host;

	if (gethostname(buf, sizeof(buf) - 1) == 0) {
		buf[sizeof(buf) - 1] = '\0';
		
		if ((host = gethostbyname(buf)) && 
		    (hostname = findfqdn(host))) {
			return hostname;
		}
		
		if (host && host->h_addr_list[0] != NULL)
			return strdup(inet_ntoa(*(struct in_addr *)host->h_addr_list[0]));
	}
	
	logmsg("Failed to get hostname, please set serveraddress in configfile\n");
	return NULL;
}


/*
 * Writes ShoutCast type metadata to the client (for format information, see
 * the developer docs at the Ample homepage).
 *
 * Arguments: to - the stream to write the data to
 *            entry - the entry that is currently playing
 *            metaflag - has information about this particular entry
 *                       been written before?
 *
 * Returns: TRUE if the operation was successful, else FALSE
 */
bool 
writemetadata(FILE *to, mp3entry *entry, bool *metaflag) 
{
	int msglen;
	int padding;
	int towrite;
	int written;
	char *buf;

	if(*metaflag) {
		*metaflag = FALSE;

		msglen = strlen(entry->title) + 28;
		padding = 16 - msglen % 16;
		towrite = msglen + padding + 1;
		
		buf = malloc(towrite);
		memset(buf, 0, towrite);
		sprintf(buf, "%cStreamTitle='%s';StreamUrl='';", 
			(msglen + padding)/16, entry->title);
	} else {
		towrite = 1;
		buf = malloc(towrite);
		memset(buf, 0, towrite);
	}

	written = fwrite(buf, sizeof(char), towrite, to);
	free(buf);
	if(written == towrite)
		return(TRUE);
	else
		return(FALSE);
}


/*
 * Removes trailing spaces from a string.
 *
 * Arguments: buffer - the string to process
 *
 * Returns: void
 */
static void 
stripspaces(char *buffer) 
{
	int i = 0;
	
	while(*(buffer + i) != '\0')
		i++;
	
	for(;i >= 0; i--) {
		if(*(buffer + i) == ' ')
			*(buffer + i) = '\0';
		else if(*(buffer + i) == '\0')
			continue;
		else
			break;
	}
}


/*
 * Sets the title of an MP3 entry based on its ID3v1 tag.
 *
 * Arguments: file - the MP3 file to scen for a ID3v1 tag
 *            entry - the entry to set the title in
 *
 * Returns: TRUE if a title was found and created, else FALSE
 */
static bool 
setid3v1title(FILE *file, mp3entry *entry) 
{
	char buffer[31];
	int offsets[3] = {-95,-65,-125};
	int i;
	char *result;

	result = (char *)malloc(3*30 + 2*3 + 1);
	*result = '\0';
	
	for(i=0;i<3;i++) {
		if(fseek(file, offsets[i], SEEK_END) != 0) {
			free(result);
			return FALSE;
		}
		
		
		fgets(buffer, 31, file);
		stripspaces(buffer);
		if(buffer[0] != '\0' && *result != '\0')
			strcat(result, " - ");
		strcat(result, buffer);
	}

	if(*result == '\0') {
		free(result);
		return(FALSE);
	} else {
		entry->title = (char *)realloc(result, strlen(result) + 1);
		return(TRUE);
	}
}


/*
 * Sets the title of an MP3 entry based on its ID3v2 tag.
 *
 * Arguments: file - the MP3 file to scen for a ID3v2 tag
 *            entry - the entry to set the title in
 *
 * Returns: TRUE if a title was found and created, else FALSE
 */
static bool 
setid3v2title(FILE *file, mp3entry *entry) 
{
	char *buffer;
	int minframesize;
	int size, readsize = 0, headerlen;
	char *title = NULL;
	char *artist = NULL;
	char header[10];
	unsigned short int version;
	
	/* 10 = headerlength */
	if(entry->id3v2len < 10)
		return(FALSE);

	/* Check version */
	fseek(file, 0, SEEK_SET);
	fread(header, sizeof(char), 10, file);
	version = (unsigned short int)header[3];
	
	/* Read all frames in the tag */
	size = entry->id3v2len - 10;
	buffer = malloc(size + 1);
	if(size != (int)fread(buffer, sizeof(char), size, file)) {
		free(buffer);
		return(FALSE);
	}
	*(buffer + size) = '\0';

	/* Set minimun frame size according to ID3v2 version */
	if(version > 2)
		minframesize = 12;
	else
		minframesize = 8;

	/* 
	 * We must have at least minframesize bytes left for the 
	 * remaining frames to be interesting 
	 */
	while(size - readsize > minframesize) {
		
                /* Read frame header and check length */
		if(version > 2) {
			memcpy(header, (buffer + readsize), 10);
			readsize += 10;
			headerlen = UNSYNC(header[4], header[5], 
					   header[6], header[7]);
		} else {
			memcpy(header, (buffer + readsize), 6);			
			readsize += 6;
			headerlen = (header[3] << 16) + 
				    (header[4] << 8) + 
				    (header[5]);
		}
		if(headerlen < 1)
			continue;

		/* Check for certain frame headers */
		if(!strncmp(header, "TPE1", strlen("TPE1")) || 
		   !strncmp(header, "TP1", strlen("TP1"))) {
			readsize++;
			headerlen--;
			if(headerlen > (size - readsize))
				headerlen = (size - readsize);
			artist = malloc(headerlen + 1);
			snprintf(artist, headerlen + 1, "%s", 
				 (buffer + readsize));
			readsize += headerlen;
		} else if(!strncmp(header, "TIT2", strlen("TIT2")) || 
			  !strncmp(header, "TT2", strlen("TT2"))) {
			readsize++;
			headerlen--;
			if(headerlen > (size - readsize))
				headerlen = (size - readsize);
			title = malloc(headerlen + 1);
			snprintf(title, headerlen + 1, "%s", 
				 (buffer + readsize));
			readsize += headerlen;
		}
	}

	/* Done, let's clean up */
	if(artist && title) {
		entry->title = malloc(strlen(artist) + strlen(title) + 4);
		snprintf(entry->title, strlen(artist) + strlen(title) + 4, 
			 "%s - %s", artist, title);
		free(artist);
		free(title);
	} else if(artist) {
		entry->title = artist;
	} else if(title) {
		entry->title = title;
	}
	
	free(buffer);
	return(entry->title != NULL);
}


/*
 * Calculates the size of the ID3v2 tag.
 *
 * Arguments: file - the file to search for a tag.
 *
 * Returns: the size of the tag or 0 if none was found
 */
static int 
getid3v2len(FILE *file) 
{
	char buf[6];
	int offset;
	
	/* Make sure file has a ID3 tag */
	if((fseek(file, 0, SEEK_SET) != 0) ||
	   (fread(buf, sizeof(char), 6, file) != 6) ||
	   (strncmp(buf, "ID3", strlen("ID3")) != 0))
		offset = 0;
	/* Now check what the ID3v2 size field says */
	else if(fread(buf, sizeof(char), 4, file) != 4)
		offset = 0;
	else
		offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
	
	return(offset);
}


/*
 * Calculates the size of the ID3v1 tag.
 *
 * Arguments: file - the file to search for a tag.
 *
 * Returns: the size of the tag or 0 if none was found
 */
static int 
getid3v1len(FILE *file) 
{
	char buf[3];
	int offset;

	/* Check if we find "TAG" 128 bytes from EOF */
	if((fseek(file, -128, SEEK_END) != 0) ||
	   (fread(buf, sizeof(char), 3, file) != 3) ||
	   (strncmp(buf, "TAG", 3) != 0))
		offset = 0;
	else
		offset = 128;
	
	return offset;
}


/*
 * Calculates the length (in seconds) of an MP3 file. Currently this code 
 * doesn't care about VBR (Variable BitRate) files since it would have to
 * scan through the entire file but this should become a config option
 * in the future.
 *
 * Arguments: file - the file to calculate the length upon
 *            entry - the entry to update with the length
 *
 * Returns: the song length in seconds, 
 *          -1 means that it couldn't be calculated
 */
static int 
getsonglength(FILE *file, mp3entry *entry) {

#ifdef HAVE_STDINT_H
	uint32_t header;
#else
	long header;
#endif
	int version;
	int layer;
	int bitindex;
	int bitrate;
	int freqindex;
	int frequency;

	double bpf;
	double tpf;
	int i;

	/* Start searching after ID3v2 header */ 
	if(fseek(file, entry->id3v2len, SEEK_SET))
		return -1;
	
	/* Fill up header with first 24 bits */
	for(version = 0; version < 3; version++) {
		header <<= 8;
		if(!fread(&header, 1, 1, file))
			return -1;
	}
	
	/* Loop trough file until we find a frame header */
 restart:
	do {
		header <<= 8;
		if(!fread(&header, 1, 1, file))
			return -1;
	} while(!CHECKSYNC(header));
	
	
	/* 
	 * Some files are filled with garbage in the beginning, 
	 * if the bitrate index of the header is binary 1111 
	 * that is a good indicator
	 */
	if((header & 0xF000) == 0xF000)
		goto restart;

	debug(5, "We found %x-%x-%x-%x and checksync %i and test %x\n", 
	      BYTE0(header), BYTE1(header), BYTE2(header), BYTE3(header), 
	      CHECKSYNC(header), (header & 0xF000) == 0xF000);
	
	/* MPEG Audio Version */
	switch((header & 0x180000) >> 19) {
	case 2:
		version = 2;
		break;
	case 3:
		version = 1;
		break;
	default:
		return -1;
	}

	/* Layer */
	switch((header & 0x060000) >> 17) {
	case 1:
		layer = 3;
		break;
	case 2:
		layer = 2;
		break;
	case 3:
		layer = 1;
		break;
	default:
		return -1;
	}

	/* Bitrate */
	bitindex = (header & 0xF000) >> 12;
	bitrate = bitrate_table[version-1][layer-1][bitindex];
	if(bitrate == 0)
		return -1;

	/* Sampling frequency */
	freqindex = (header & 0x0C00) >> 10;
	frequency = freqtab[version-1][freqindex];
	if(frequency == 0)
		return -1;

	debug(2, "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i\n", 
	      version, layer, bitindex, bitrate, freqindex, frequency);
	
	/* Calculate bytes per frame, calculation depends on layer */
        switch(layer) {
	case 1:
		bpf = bitrate_table[version - 1][layer - 1][bitindex];
		bpf *= 12000.0 * 4.0;
		bpf /= freqtab[version-1][freqindex] << (version - 1);
		break;
	case 2:
	case 3:
		bpf = bitrate_table[version - 1][layer - 1][bitindex];
		bpf *= 144000;
		bpf /= freqtab[version-1][freqindex] << (version - 1);
		break;
	default:
		bpf = 1.0;
        }
	
	/* Calculate time per frame */
        tpf = bs[layer];
        tpf /= freqtab[version-1][freqindex] << (version - 1);

	debug(3, "BitRate is %i, FileLength is %i, TPF is %f and BPF is %f, we have %f frames in one second\n", bitrate, entry->filesize, tpf, bpf, 1/tpf);
	
	/* 
	 * Now song length is 
	 * ((filesize)/(bytes per frame))*(time per frame) 
	 */
	return (int)(((float)entry->filesize/bpf)*tpf);
}


/*
 * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc)
 * about an MP3 file and updates it's entry accordingly.
 *
 * Arguments: entry - the entry to check and update with the new information
 *
 * Returns: void
 */
void 
checkmp3info(mp3entry *entry) 
{
	FILE *file;
	char *copy;
	char *title;

	if((file = fopen(entry->path, "r")) == NULL)
		return;

	entry->id3v2len = getid3v2len(file);
	entry->id3v1len = getid3v1len(file);
	entry->length = getsonglength(file, entry);
	entry->title = NULL;

	if(HASID3V2(entry))
		setid3v2title(file, entry);

	if(HASID3V1(entry) && !entry->title)
		setid3v1title(file, entry);
	
	if(!entry->title) {
		copy = strdup(entry->path);
		title = basename(copy);
		entry->title = malloc(strlen(title) - 3);
		snprintf(entry->title, strlen(title) - 3, "%s", title);
		free(copy);
	}

	fclose(file);
}
