/*
 *   libmetatag - A media file tag-reader library
 *   Copyright (C) 2004  Pipian
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef MAKE_BMP
#include "include/bmp_vfs.h"
#endif
#include "include/wma.h"
#include "include/tags.h"
#include "include/endian.h"
#include "../fmt.h"
#include "../config.h"
#include "include/unicode.h"
#define WMA_GUID	(unsigned char [16]) \
		      {	0x30, 0x26, 0xB2, 0x75, \
			0x8E, 0x66, \
			0xCF, 0x11, \
			0xA6, 0xD9, \
			0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }
#define CONTENT_GUID	(unsigned char [16]) \
		      {	0x33, 0x26, 0xB2, 0x75, \
			0x8E, 0x66, \
			0xCF, 0x11, \
			0xA6, 0xD9, \
			0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }
#define EXTENDED_GUID	(unsigned char [16]) \
		      {	0x40, 0xA4, 0xD0, 0xD2, \
			0x07, 0xE3, \
			0xD2, 0x11, \
			0x97, 0xF0, \
			0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 }
#define BUFFER_SIZE 4096

static wma_t *readAttributes(FILE *fp, int pos)
{
	wma_t *wma = calloc(sizeof(wma_t), 1);
	attribute_t *attribute;
	unsigned char *tag_buffer = NULL, *bp, cToInt[8], *data = NULL;
	int title_size, author_size, copyright_size, desc_size, rating_size,
		size, primary_items, i;

	fseek(fp, pos, SEEK_SET);
	fread(cToInt, 1, 8, fp);
	/* Yes, it's 64 bits in size, but I'm lazy and don't want to adjust. */
	size = le2int(cToInt);
	tag_buffer = malloc(size - 24);
	fread(tag_buffer, 1, size - 24, fp);
	bp = tag_buffer;
	title_size = le2short(bp);
	bp += 2;
	author_size = le2short(bp);
	bp += 2;
	copyright_size = le2short(bp);
	bp += 2;
	desc_size = le2short(bp);
	bp += 2;
	rating_size = le2short(bp);
	bp += 2;
	if(title_size > 0)
	{
		attribute = calloc(sizeof(attribute_t), 1);
		wma->items = realloc(wma->items,
				(wma->numitems + 1) * sizeof(attribute_t *));
		attribute->name = malloc(6);
		strcpy((char *)attribute->name, "Title");
		data = malloc(title_size);
		memcpy(data, bp, title_size);
		bp += title_size;
		utf16le_to_utf8(data, title_size, &attribute->data);
		attribute->type = 0;
		wma->items[wma->numitems++] = attribute;
		free(data);
	}
	if(author_size > 0)
	{
		attribute = calloc(sizeof(attribute_t), 1);
		wma->items = realloc(wma->items,
				(wma->numitems + 1) * sizeof(attribute_t *));
		attribute->name = malloc(7);
		strcpy((char *)attribute->name, "Author");
		data = malloc(author_size);
		memcpy(data, bp, author_size);
		bp += author_size;
		utf16le_to_utf8(data, author_size, &attribute->data);
		attribute->type = 0;
		wma->items[wma->numitems++] = attribute;
		free(data);
	}
	if(copyright_size > 0)
	{
		attribute = calloc(sizeof(attribute_t), 1);
		wma->items = realloc(wma->items,
				(wma->numitems + 1) * sizeof(attribute_t *));
		attribute->name = malloc(10);
		strcpy((char *)attribute->name, "Copyright");
		data = malloc(copyright_size);
		memcpy(data, bp, copyright_size);
		bp += copyright_size;
		utf16le_to_utf8(data, copyright_size, &attribute->data);
		attribute->type = 0;
		wma->items[wma->numitems++] = attribute;
		free(data);
	}
	if(desc_size > 0)
	{
		attribute = calloc(sizeof(attribute_t), 1);
		wma->items = realloc(wma->items,
				(wma->numitems + 1) * sizeof(attribute_t *));
		attribute->name = malloc(12);
		strcpy((char *)attribute->name, "Description");
		data = malloc(desc_size);
		memcpy(data, bp, desc_size);
		bp += desc_size;
		utf16le_to_utf8(data, desc_size, &attribute->data);
		attribute->type = 0;
		wma->items[wma->numitems++] = attribute;
		free(data);
	}
	if(rating_size > 0)
	{
		attribute = calloc(sizeof(attribute_t), 1);
		wma->items = realloc(wma->items,
				(wma->numitems + 1) * sizeof(attribute_t *));
		attribute->name = malloc(7);
		strcpy((char *)attribute->name, "Rating");
		data = malloc(rating_size);
		memcpy(data, bp, rating_size);
		bp += rating_size;
		utf16le_to_utf8(data, desc_size, &attribute->data);
		attribute->type = 0;
		wma->items[wma->numitems++] = attribute;
		free(data);
	}
	primary_items = wma->numitems;

	fread(tag_buffer, 16, 1, fp);
	if(memcmp(tag_buffer, EXTENDED_GUID, 16))
	{
		free(tag_buffer);
		return wma;
	}
	fread(cToInt, 8, 1, fp);
	/*
	 * Another 64-bit breakage. If you've got that large an amount of
	 * metadata, you've got a problem.
	 */
	size = le2int(cToInt);
	tag_buffer = realloc(tag_buffer, size);
	fread(tag_buffer, size, 1, fp);
	bp = tag_buffer;
	memcpy(cToInt, bp, 2);
	wma->numitems += le2short(cToInt);
	wma->items = realloc(wma->items,
			wma->numitems * sizeof(attribute_t *));
	bp += 2;
	for(i = primary_items; i < wma->numitems; i++)
	{
		int type;

		attribute = calloc(sizeof(attribute_t), 1);

		memcpy(cToInt, bp, 2);
		size = le2short(cToInt);
		bp += 2;
		data = malloc(size);
		memcpy(data, bp, size);
		utf16le_to_utf8(data, size, &attribute->name);
		bp += size;
		memcpy(cToInt, bp, 2);
		type = le2short(cToInt);
		attribute->type = type;
		bp += 2;
		memcpy(cToInt, bp, 2);
		size = le2short(cToInt);
		bp += 2;
		data = realloc(data, size);
		memcpy(data, bp, size);
		switch(type)
		{
			/* Type 0 is Little-endian UTF16 */
			case 0:
				utf16le_to_utf8(data, size, &attribute->data);
				break;
			/*
			 * Type 1 is binary
			 * Type 2 is boolean
			 * Type 3 is 32-bit integer (signed?)
			 * Type 4 is double
			 */
			case 1:
			case 2:
			case 3:
			case 4:
			default:
				attribute->data = malloc(size);
				memcpy(attribute->data, data, size);
		}
		bp += size;
		
		wma->items[i] = attribute;		
	}

	free(tag_buffer);
	
	return wma;
}

int findWMA(char *filename) {
	
	FILE *fp;
	fp = fopen(filename, "r");

	if (!fp) {
		return 0;
	}

	fseek(fp, 0, SEEK_SET);

	unsigned char *tag_buffer, *bp;
	
	tag_buffer = malloc(BUFFER_SIZE);
	fread(tag_buffer, 1, BUFFER_SIZE, fp);
	bp = tag_buffer;
	if(memcmp(bp, WMA_GUID, 16))
	{
		free(tag_buffer);
		return 0;
	}
	bp += 30;
	if(memcmp(bp, CONTENT_GUID, 16))
	{
		free(tag_buffer);
		return 0;
	}

	free(tag_buffer);
	return 1;
}

int findWMAPos(FILE *fp) {
	unsigned char *tag_buffer, *bp;

	tag_buffer = malloc(BUFFER_SIZE);
	fread(tag_buffer, 1, BUFFER_SIZE, fp);
	bp = tag_buffer;
	if (memcmp(bp, WMA_GUID, 16)) {
		free(tag_buffer);
		return -1;
	}
	bp +=30;
	if (memcmp(bp, CONTENT_GUID, 16)) {
		free(tag_buffer);
		return -1;
	}

	free(tag_buffer);
	return bp - tag_buffer + 16;
}

wma_t *readWMA(char *filename)
{
	FILE *fp;
	wma_t *wma;
	int status;
	
	fp = fopen(filename, "r");

	if(!fp)
	{
		pdebug("Couldn't open file!", META_DEBUG);
		return NULL;
	}
	
	fseek(fp, 0, SEEK_SET);
	
	pdebug("Searching for tag...", META_DEBUG);
	status = findWMAPos(fp);
	if(status == 0)
	{
		fclose(fp);
		return NULL;
	}
	wma = readAttributes(fp, status);
	
	fclose(fp);
	
	return wma;
}

void freeWMA(wma_t *wma)
{
	int i;
	
	for(i = 0; i < wma->numitems; i++)
	{
		attribute_t *attribute;
		
		attribute = wma->items[i];
		free(attribute->name);
		free(attribute->data);
		free(attribute);
	}
	free(wma->items);
	free(wma);
}

int fetchWMA(char *filename, metatag_t *meta) {

	wma_t *wma;
	int i = 0;
	if (findWMA(filename)) {
		wma = readWMA(filename);

		for (i = 0; i < wma->numitems; i++) {
			attribute_t *attribute = wma->items[i];
			if (!strcmp((char *)attribute->name, "Title")) {
				metatag_set_title(meta, (char *)attribute->data);
			}
			else if (!strcmp((char *)attribute->name, "Author")) {
				metatag_set_artist(meta, (char *)attribute->data);
			}
			else if (!strcmp((char *)attribute->name, "WM/AlbumTitle")) {
				metatag_set_album(meta, (char *)attribute->data);
			}
		}
		freeWMA(wma);
		return 1;
	}

	return 0;
}
