/* 
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthmct@yahoo.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * Anthony Minessale II <anthmct@yahoo.com>
 *
 *
 * mod_sndfile.c -- Framework Demo Module
 *
 */
#include <switch.h>
#include <sndfile.h>

static const char modname[] = "mod_sndfile";

struct sndfile_context {
	SF_INFO sfinfo;
	SNDFILE *handle;
};

typedef struct sndfile_context sndfile_context;

static switch_status_t sndfile_file_open(switch_file_handle_t *handle, char *path)
{
	sndfile_context *context;
	int mode = 0;
	char *ext;
	int ready = 1;
	
	if ((ext = strrchr(path, '.')) == 0) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Format\n");
		return SWITCH_STATUS_GENERR;
	}
	ext++;


	if (switch_test_flag(handle, SWITCH_FILE_FLAG_READ)) {
		mode += SFM_READ;
	}

	if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
		mode += SFM_WRITE;
	}

	if (!mode) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Mode!\n");
		return SWITCH_STATUS_GENERR;
	}

	if ((context = switch_core_alloc(handle->memory_pool, sizeof(*context))) == 0) {
		return SWITCH_STATUS_MEMERR;
	}

	if (mode & SFM_WRITE) {
		sf_count_t  frames = 0 ;

		context->sfinfo.channels = handle->channels;
		context->sfinfo.samplerate = handle->samplerate;
		if (handle->samplerate == 8000 || handle->samplerate == 16000) {
			context->sfinfo.format |= SF_FORMAT_PCM_16;
		} else if (handle->samplerate == 24000) {
			context->sfinfo.format |= SF_FORMAT_PCM_24;
		} else if (handle->samplerate == 32000) {
			context->sfinfo.format |= SF_FORMAT_PCM_32;
		}

        sf_command (context->handle, SFC_FILE_TRUNCATE, &frames, sizeof (frames)) ;

		/* Could add more else if() but i am too lazy atm.. */
		if (!strcasecmp(ext, "wav")) {
			context->sfinfo.format |= SF_FORMAT_WAV;
		} else {
			ready = 0;
		}
	} else {
		ready = 0;
	}


	if (!ready) {
		if (!strcmp(ext, "r8") || !strcmp(ext, "raw")) {
			context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
			context->sfinfo.channels = 1;
			context->sfinfo.samplerate = 8000;
		} else if (!strcmp(ext, "r16")) {
			context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_16;
			context->sfinfo.channels = 1;
			context->sfinfo.samplerate = 16000;
		} else if (!strcmp(ext, "r24")) {
			context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_24;
			context->sfinfo.channels = 1;
			context->sfinfo.samplerate = 24000;
		} else if (!strcmp(ext, "r32")) {
			context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_PCM_32;
			context->sfinfo.channels = 1;
			context->sfinfo.samplerate = 32000;
		} else if (!strcmp(ext, "gsm")) {
			context->sfinfo.format = SF_FORMAT_RAW | SF_FORMAT_GSM610;
			context->sfinfo.channels = 1;
			context->sfinfo.samplerate = 8000;
		}
	}


	if ((mode & SFM_WRITE) && sf_format_check (&context->sfinfo) == 0) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error : file format is invalid (0x%08X).\n", context->sfinfo.format);
		return SWITCH_STATUS_GENERR;
	};


	if ((context->handle = sf_open(path, mode, &context->sfinfo)) == 0) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening File [%s] [%s]\n", path,
							  sf_strerror(context->handle));
		return SWITCH_STATUS_GENERR;
	}

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Opening File [%s] %dhz\n", path, context->sfinfo.samplerate);
	handle->samples = (unsigned int) context->sfinfo.frames;
	handle->samplerate = context->sfinfo.samplerate;
	handle->channels = (uint8_t)context->sfinfo.channels;
	handle->format = context->sfinfo.format;
	handle->sections = context->sfinfo.sections;
	handle->seekable = context->sfinfo.seekable;
	handle->speed = 0;
	handle->private_info = context;

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t sndfile_file_close(switch_file_handle_t *handle)
{
	sndfile_context *context = handle->private_info;

	sf_close(context->handle);

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t sndfile_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
{
	sndfile_context *context = handle->private_info;

	if (!handle->seekable) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "File is not seekable\n");
		return SWITCH_STATUS_NOTIMPL;
	}

	*cur_sample = (unsigned int) sf_seek(context->handle, samples, whence);
	handle->pos = *cur_sample;

	return SWITCH_STATUS_SUCCESS;

}

static switch_status_t sndfile_file_read(switch_file_handle_t *handle, void *data, size_t *len)
{
	size_t inlen = *len;
	sndfile_context *context = handle->private_info;

	if (switch_test_flag(handle, SWITCH_FILE_DATA_RAW)) {
		*len = (size_t) sf_read_raw(context->handle, data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_INT)) {
		*len = (size_t) sf_readf_int(context->handle, (int *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_SHORT)) {
		*len = (size_t) sf_readf_short(context->handle, (short *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_FLOAT)) {
		*len = (size_t) sf_readf_float(context->handle, (float *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_DOUBLE)) {
		*len = (size_t) sf_readf_double(context->handle, (double *) data, inlen);
	} else {
		*len = (size_t) sf_readf_int(context->handle, (int *) data, inlen);
	}

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t sndfile_file_write(switch_file_handle_t *handle, void *data, size_t *len)
{
	size_t inlen = *len;
	sndfile_context *context = handle->private_info;

	if (switch_test_flag(handle, SWITCH_FILE_DATA_RAW)) {
		*len = (size_t) sf_write_raw(context->handle, data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_INT)) {
		*len = (size_t) sf_writef_int(context->handle, (int *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_SHORT)) {
		*len = (size_t) sf_writef_short(context->handle, (short *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_FLOAT)) {
		*len = (size_t) sf_writef_float(context->handle, (float *) data, inlen);
	} else if (switch_test_flag(handle, SWITCH_FILE_DATA_DOUBLE)) {
		*len = (size_t) sf_writef_double(context->handle, (double *) data, inlen);
	} else {
		*len = (size_t) sf_writef_int(context->handle, (int *) data, inlen);
	}

	return SWITCH_STATUS_SUCCESS;
}

static switch_status_t sndfile_file_set_string(switch_file_handle_t *handle, switch_audio_col_t col, const char *string)
{
	sndfile_context *context = handle->private_info;

	return sf_set_string(context->handle, (int)col, string) ? SWITCH_STATUS_FALSE : SWITCH_STATUS_SUCCESS;
}

static switch_status_t sndfile_file_get_string(switch_file_handle_t *handle, switch_audio_col_t col, const char **string)
{
	sndfile_context *context = handle->private_info;
	const char *s;

	if ((s = sf_get_string(context->handle, (int)col))) {
		*string = s;
		return SWITCH_STATUS_SUCCESS;
	}

	return SWITCH_STATUS_FALSE;
}

/* Registration */

static char **supported_formats;

static switch_file_interface_t sndfile_file_interface = {
	/*.interface_name */ modname,
	/*.file_open */ sndfile_file_open,
	/*.file_close */ sndfile_file_close,
	/*.file_read */ sndfile_file_read,
	/*.file_write */ sndfile_file_write,
	/*.file_seek */ sndfile_file_seek,
	/*.file_set_string */ sndfile_file_set_string,
	/*.file_get_string */ sndfile_file_get_string,
	/*.extens */ NULL,
	/*.next */ NULL,
};

static switch_loadable_module_interface_t sndfile_module_interface = {
	/*.module_name */ modname,
	/*.endpoint_interface */ NULL,
	/*.timer_interface */ NULL,
	/*.dialplan_interface */ NULL,
	/*.codec_interface */ NULL,
	/*.application_interface */ NULL,
	/*.api_interface */ NULL,
	/*.file_interface */ &sndfile_file_interface
};

static switch_status_t setup_formats(void)
{
	SF_FORMAT_INFO info;
	SF_INFO sfinfo;
	char buffer[128];
	int format, major_count, subtype_count, m, s;
	int len, x, skip;
	char *extras[] = { "r8", "r16", "r24", "r32", "gsm", NULL };
	int exlen = (sizeof(extras) / sizeof(extras[0]));
	buffer[0] = 0;
	
	sf_command(NULL, SFC_GET_LIB_VERSION, buffer, sizeof(buffer));
	if (strlen(buffer) < 1) {
		switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_ERROR, "Line %d: could not retrieve lib version.\n", __LINE__);
		return SWITCH_STATUS_FALSE;
	}


	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "\nLibSndFile Version : %s Supported Formats\n", buffer);
	switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_INFO,
						  "================================================================================\n");
	sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof(int));
	sf_command(NULL, SFC_GET_FORMAT_SUBTYPE_COUNT, &subtype_count, sizeof(int));

	sfinfo.channels = 1;
	len = ((major_count + (exlen + 2)) * sizeof(char *));
	supported_formats = switch_core_permanent_alloc(len);

	len = 0;
	for (m = 0; m < major_count; m++) {
		skip = 0;
		info.format = m;
		sf_command(NULL, SFC_GET_FORMAT_MAJOR, &info, sizeof(info));
		switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_INFO, "%s  (extension \"%s\")\n", info.name, info.extension);
		for (x = 0; x < len; x++) {
			if (supported_formats[x] == info.extension) {
				skip++;
				break;
			}
		}
		if (!skip) {
			supported_formats[len++] = (char *) info.extension;
		}
		format = info.format;

		for (s = 0; s < subtype_count; s++) {
			info.format = s;
			sf_command(NULL, SFC_GET_FORMAT_SUBTYPE, &info, sizeof(info));
			format = (format & SF_FORMAT_TYPEMASK) | info.format;
			sfinfo.format = format;
			/*
			if (sf_format_check(&sfinfo)) {
				switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_DEBUG, "   %s\n", info.name);
			}
			*/
		}

	}
	for (m = 0; m < exlen; m++) {
		supported_formats[len++] = extras[m];
	}



	switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, SWITCH_LOG_NOTICE,
						  "================================================================================\n");
	
	return SWITCH_STATUS_SUCCESS;
}

SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename)
{


	if (setup_formats() != SWITCH_STATUS_SUCCESS) {
		return SWITCH_STATUS_FALSE;
	}

	/* connect my internal structure to the blank pointer passed to me */
	sndfile_file_interface.extens = supported_formats;
	*module_interface = &sndfile_module_interface;

	/* indicate that the module should continue to be loaded */
	return SWITCH_STATUS_SUCCESS;
}
