/*
 * Copyright 2008-2013 Various Authors
 * Copyright 2005 Timo Hirvonen
 *
 * This program 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.
 *
 * 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "ip.h"
#include "file.h"
#include "xmalloc.h"
#include "comment.h"
#ifdef HAVE_CONFIG
#include "config/modplug.h"
#endif

#include <libmodplug/modplug.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

struct mod_private {
	ModPlugFile *file;
};

static int mod_open(struct input_plugin_data *ip_data)
{
	struct mod_private *priv;
	char *contents;
	off_t size;
	ssize_t rc;
	ModPlugFile *file;
	ModPlug_Settings settings;

	size = lseek(ip_data->fd, 0, SEEK_END);
	if (size == -1)
		return -IP_ERROR_ERRNO;
	if (lseek(ip_data->fd, 0, SEEK_SET) == -1)
		return -IP_ERROR_ERRNO;

	contents = xnew(char, size);
	rc = read_all(ip_data->fd, contents, size);
	if (rc == -1) {
		int save = errno;

		free(contents);
		errno = save;
		return -IP_ERROR_ERRNO;
	}
	if (rc != size) {
		free(contents);
		return -IP_ERROR_FILE_FORMAT;
	}
	errno = 0;
	file = ModPlug_Load(contents, size);
	if (file == NULL) {
		int save = errno;

		free(contents);
		errno = save;
		if (errno == 0) {
			/* libmodplug never sets errno? */
			return -IP_ERROR_FILE_FORMAT;
		}
		return -IP_ERROR_ERRNO;
	}
	free(contents);

	priv = xnew(struct mod_private, 1);
	priv->file = file;

	ModPlug_GetSettings(&settings);
	settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
/* 	settings.mFlags |= MODPLUG_ENABLE_REVERB; */
/* 	settings.mFlags |= MODPLUG_ENABLE_MEGABASS; */
/* 	settings.mFlags |= MODPLUG_ENABLE_SURROUND; */
	settings.mChannels = 2;
	settings.mBits = 16;
	settings.mFrequency = 44100;
	settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
	ModPlug_SetSettings(&settings);

	ip_data->private = priv;
	ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
	ip_data->sf |= sf_host_endian();
	channel_map_init_stereo(ip_data->channel_map);
	return 0;
}

static int mod_close(struct input_plugin_data *ip_data)
{
	struct mod_private *priv = ip_data->private;

	ModPlug_Unload(priv->file);
	free(priv);
	ip_data->private = NULL;
	return 0;
}

static int mod_read(struct input_plugin_data *ip_data, char *buffer, int count)
{
	struct mod_private *priv = ip_data->private;
	int rc;

	errno = 0;
	rc = ModPlug_Read(priv->file, buffer, count);
	if (rc < 0) {
		if (errno == 0)
			return -IP_ERROR_INTERNAL;
		return -IP_ERROR_ERRNO;
	}
	return rc;
}

static int mod_seek(struct input_plugin_data *ip_data, double offset)
{
	struct mod_private *priv = ip_data->private;
	int ms = (int)(offset * 1000.0 + 0.5);

	/* void */
	ModPlug_Seek(priv->file, ms);
	return 0;
}

static int mod_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
{
	struct mod_private *priv = ip_data->private;
	GROWING_KEYVALS(c);
	const char *val;

	val = ModPlug_GetName(priv->file);
	if (val && val[0])
		comments_add_const(&c, "title", val);

#if MODPLUG_API_8
	val = ModPlug_GetMessage(priv->file);
	if (val && val[0])
		comments_add_const(&c, "comment", val);
#endif

	keyvals_terminate(&c);
	*comments = c.keyvals;
	return 0;
}

static int mod_duration(struct input_plugin_data *ip_data)
{
	struct mod_private *priv = ip_data->private;

	return (ModPlug_GetLength(priv->file) + 500) / 1000;
}

static long mod_bitrate(struct input_plugin_data *ip_data)
{
	return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
}

#if MODPLUG_API_8
static const char *mod_type_to_string(int type)
{
	/* from <libmodplug/sndfile.h>, which is C++ */
	switch (type) {
	case 0x01:	return "mod";
	case 0x02:	return "s3m";
	case 0x04:	return "xm";
	case 0x08:	return "med";
	case 0x10:	return "mtm";
	case 0x20:	return "it";
	case 0x40:	return "699";
	case 0x80:	return "ult";
	case 0x100:	return "stm";
	case 0x200:	return "far";
	case 0x800:	return "amf";
	case 0x1000:	return "ams";
	case 0x2000:	return "dsm";
	case 0x4000:	return "mdl";
	case 0x8000:	return "okt";
	case 0x10000:	return "midi";
	case 0x20000:	return "dmf";
	case 0x40000:	return "ptm";
	case 0x80000:	return "dbm";
	case 0x100000:	return "mt2";
	case 0x200000:	return "amf0";
	case 0x400000:	return "psm";
	case 0x80000000:return "umx";
	}
	return NULL;
}
#endif

static char *mod_codec(struct input_plugin_data *ip_data)
{
#if MODPLUG_API_8
	struct mod_private *priv = ip_data->private;
	const char *codec;
	int type;

	type = ModPlug_GetModuleType(priv->file);
	codec = mod_type_to_string(type);

	return codec ? xstrdup(codec) : NULL;
#else
	return NULL;
#endif
}

static char *mod_codec_profile(struct input_plugin_data *ip_data)
{
	return NULL;
}

const struct input_plugin_ops ip_ops = {
	.open = mod_open,
	.close = mod_close,
	.read = mod_read,
	.seek = mod_seek,
	.read_comments = mod_read_comments,
	.duration = mod_duration,
	.bitrate = mod_bitrate,
	.bitrate_current = mod_bitrate,
	.codec = mod_codec,
	.codec_profile = mod_codec_profile
};

const int ip_priority = 50;
const char * const ip_extensions[] = {
	"mod", "s3m", "xm", "it", "669", "amf", "ams", "dbm", "dmf", "dsm",
	"far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
	"psm", NULL
};
const char * const ip_mime_types[] = { NULL };
const struct input_plugin_opt ip_options[] = { { NULL } };
const unsigned ip_abi_version = IP_ABI_VERSION;
