/*
** Copyright (C) 2008-2019 Erik de Castro Lopo <erikd@mega-nerd.com>
** Copyright (C) 2018 Arthur Taylor <art@ified.ca>
**
** This program 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 program 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 program ; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "sfconfig.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "sndfile.h"
#include "sfendian.h"
#include "common.h"

#if HAVE_EXTERNAL_XIPH_LIBS

#include <ogg/ogg.h>

#include "ogg_vcomment.h"

typedef struct
{	int id ;
	const char *name ;
} STR_PAIR ;

/* See https://xiph.org/vorbis/doc/v-comment.html */
static STR_PAIR vorbiscomment_mapping [] =
{	{	SF_STR_TITLE,		"TITLE"			},
	{	SF_STR_COPYRIGHT,	"COPYRIGHT",	},
	{	SF_STR_SOFTWARE,	"ENCODER",		},
	{	SF_STR_ARTIST,		"ARTIST"		},
	{	SF_STR_COMMENT,		"COMMENT"		},
	{	SF_STR_DATE,		"DATE",			},
	{	SF_STR_ALBUM,		"ALBUM"			},
	{	SF_STR_LICENSE,		"LICENSE",		},
	{	SF_STR_TRACKNUMBER,	"TRACKNUMBER",	},
	{	SF_STR_GENRE,		"GENRE",		},
	{	0,					NULL,			},
} ;

/*-----------------------------------------------------------------------------------------------
** Private function prototypes.
*/

static int	vorbiscomment_lookup_id (const char *name) ;
static const char *	vorbiscomment_lookup_name (int id) ;

static inline size_t read_32bit_size_t (const unsigned char * ptr)
{	/* Read a 32 bit positive value from the provided pointer. */
	return LE2H_32_PTR (ptr) & 0x7fffffff ;
} /* read_32bit_size_t */

/*-----------------------------------------------------------------------------------------------
** Exported functions.
*/

int
vorbiscomment_read_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident)
{	unsigned char *p, *ep ;
	char *tag, *c ;
	size_t tag_size, tag_len = 0 ;
	unsigned int ntags, i = 0 ;
	int id, ret = 0 ;

	/*
	** The smallest possible header is the ident string length plus two 4-byte
	** integers, (vendor string length, tags count.)
	*/
	if (packet->bytes < (ident ? ident->length : 0) + 4 + 4)
		return SFE_MALFORMED_FILE ;

	/* Our working pointer. */
	p = packet->packet ;
	/* Our end pointer for bound checking. */
	ep = p + packet->bytes ;

	if (ident)
	{	if (memcmp (p, ident->ident, ident->length) != 0)
		{	psf_log_printf (psf, "Expected comment packet identifier missing.\n") ;
			return SFE_MALFORMED_FILE ;
			} ;
		p += ident->length ;
		} ;

	tag_size = 1024 ;
	tag = malloc (tag_size) ;
	/* Unlikely */
	if (!tag)
		return SFE_MALLOC_FAILED ;

	psf_log_printf (psf, "VorbisComment Metadata\n") ;

	/*
	** Vendor tag, manditory, no field name.
	*/
	tag_len = read_32bit_size_t (p) ;
	p += 4 ;
	if (tag_len > 0)
	{	/* Bound checking. 4 bytes for remaining manditory fields. */
		if (p + tag_len + 4 > ep)
		{	ret = SFE_MALFORMED_FILE ;
			goto free_tag_out ;
			} ;
		if (tag_len > tag_size - 1)
		{	free (tag) ;
			tag_size = tag_len + 1 ;
			tag = malloc (tag_size) ;
			/* Unlikely */
			if (!tag)
				return SFE_MALLOC_FAILED ;
			} ;
		memcpy (tag, p, tag_len) ; p += tag_len ;
		tag [tag_len] = '\0' ;
		psf_log_printf (psf, "  Vendor: %s\n", tag) ;
		} ;

	/*
	** List of tags of the form NAME=value
	** Allowable characters for NAME are the same as shell variable names.
	*/
	ntags = read_32bit_size_t (p) ;
	p += 4 ;
	for (i = 0 ; i < ntags ; i++)
	{	if (p + 4 > ep)
		{	ret = SFE_MALFORMED_FILE ;
			goto free_tag_out ;
			} ;
		tag_len = read_32bit_size_t (p) ;
		p += 4 ;
		if (p + tag_len > ep)
		{	ret = SFE_MALFORMED_FILE ;
			goto free_tag_out ;
			} ;
		if (tag_len > tag_size - 1)
		{	free (tag) ;
			tag_size = tag_len + 1 ;
			tag = malloc (tag_size) ;
			/* Unlikely */
			if (!tag)
				return SFE_MALLOC_FAILED ;
			} ;
		memcpy (tag, p, tag_len) ; p += tag_len ;
		tag [tag_len] = '\0' ;
		psf_log_printf (psf, "  %s\n", tag) ;
		for (c = tag ; *c ; c++)
		{	if (*c == '=')
				break ;
			*c = toupper (*c) ;
			} ;
		if (!c)
		{	psf_log_printf (psf, "Malformed Vorbis comment, no '=' found.\n") ;
			continue ;
			} ;
		*c = '\0' ;
		if ((id = vorbiscomment_lookup_id (tag)) != 0)
			psf_store_string (psf, id, c + 1) ;
		} ;

free_tag_out:
	if (tag != NULL)
		free (tag) ;
	return ret ;
} /* vorbiscomment_read_tags */

int
vorbiscomment_write_tags (SF_PRIVATE *psf, ogg_packet *packet, vorbiscomment_ident *ident, const char *vendor, int targetsize)
{	int i, ntags ;
	int tags_start ;
	const char *tag_name ;
	int tag_name_len, tag_body_len ;

	psf->header.ptr [0] = 0 ;
	psf->header.indx = 0 ;

	/* Packet identifier */
	if (ident)
		psf_binheader_writef (psf, "eb", BHWv (ident->ident), BHWz (ident->length)) ;

	/* Manditory Vendor Tag */
	tag_name_len = vendor ? strlen (vendor) : 0 ;
	psf_binheader_writef (psf, "e4b", BHW4 (tag_name_len), BHWv (vendor), BHWz (tag_name_len)) ;

	/* Tags Count. Skip for now, write after. */
	tags_start = psf->header.indx ;
	psf_binheader_writef (psf, "j", BHWj (4)) ;

	ntags = 0 ;
	/* Write each tag */
	for (i = 0 ; i < SF_MAX_STRINGS ; i++)
	{	if (psf->strings.data [i].type == 0)
			continue ;

		tag_name = vorbiscomment_lookup_name (psf->strings.data [i].type) ;
		if (tag_name == NULL)
			continue ;

		tag_name_len = strlen (tag_name) ;
		tag_body_len = strlen (psf->strings.storage + psf->strings.data [i].offset) ;
		if (targetsize > 0 && tag_name_len + tag_body_len + psf->header.indx > targetsize)
		{	/* If we are out of space, stop now. */
			return SFE_STR_MAX_DATA ;
			}
		psf_binheader_writef (psf, "e4b1b",
			BHW4 (tag_name_len + 1 + tag_body_len),
			BHWv (tag_name), BHWz (tag_name_len),
			BHW1 ('='),
			BHWv (psf->strings.storage + psf->strings.data [i].offset), BHWz (tag_body_len)) ;
		ntags++ ;
		} ;

	if (targetsize < 0)
	{	/*
		** Padding.
		**
		** Pad to a minimum of -targetsize, but make sure length % 255
		** = 254 so that we get the most out of the ogg segment lacing.
		*/
		psf_binheader_writef (psf, "z", BHWz ((psf->header.indx + -targetsize + 255) / 255 * 255 - 1)) ;
		}
	else if (targetsize > 0)
		psf_binheader_writef (psf, "z", BHWz (targetsize - psf->header.indx)) ;

	packet->packet = psf->header.ptr ;
	packet->bytes = psf->header.indx ;
	packet->b_o_s = 0 ;
	packet->e_o_s = 0 ;

	/* Seek back and write the tag count. */
	psf_binheader_writef (psf, "eo4", BHWo (tags_start), BHW4 (ntags)) ;

	return 0 ;
} /* vorbiscomment_write_tags */

/*==============================================================================
** Private functions.
*/

static int
vorbiscomment_lookup_id (const char * name)
{	STR_PAIR *p ;

	for (p = vorbiscomment_mapping ; p->id ; p++)
	{	if (!strcmp (name, p->name))
			return p->id ;
		} ;

	return 0 ;
} /* vorbiscomment_lookup_id */

static const char *
vorbiscomment_lookup_name (int id)
{	STR_PAIR *p ;

	for (p = vorbiscomment_mapping ; p->id ; p++)
	{	if (p->id == id)
			return p->name ;
		} ;

	return NULL ;
} /* vorbiscomment_lookup_name */

#endif /* HAVE_EXTERNAL_XIPH_LIBS */
