/*
*			GPAC - Multimedia Framework C SDK
*
*			Authors: Jean Le Feuvre
*			Copyright (c) Telecom ParisTech 2000-2012
*					All rights reserved
*
*  This file is part of GPAC / common tools sub-project
*
*  GPAC 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, or (at your option)
*  any later version.
*
*  GPAC 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; see the file COPYING.  If not, write to
*  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/

#include <gpac/tools.h>

#if defined(WIN32) && !defined(GPAC_CONFIG_WIN32)
#include <windows.h>
#include <wincon.h>
static HANDLE console = NULL;
static WORD console_attr_ori = 0;
#endif


static char szTYPE[5];


GF_EXPORT
const char *gf_4cc_to_str(u32 type)
{
	u32 ch, i;
	char *ptr, *name = (char *)szTYPE;
	ptr = name;
	for (i = 0; i < 4; i++, name++) {
		ch = type >> (8 * (3 - i)) & 0xff;
		if (ch >= 0x20 && ch <= 0x7E) {
			*name = ch;
		}
		else {
			*name = '.';
		}
	}
	*name = 0;
	return (const char *)ptr;
}


static const char *szProg[] =
{
	"                    ",
	"=                   ",
	"==                  ",
	"===                 ",
	"====                ",
	"=====               ",
	"======              ",
	"=======             ",
	"========            ",
	"=========           ",
	"==========          ",
	"===========         ",
	"============        ",
	"=============       ",
	"==============      ",
	"===============     ",
	"================    ",
	"=================   ",
	"==================  ",
	"=================== ",
	"====================",
};

static u64 prev_pos = 0;
static u64 prev_pc = 0;
static void gf_on_progress_std(const char *_title, u64 done, u64 total)
{
	Double prog;
	u32 pos, pc;
	const char *szT = _title ? (char *)_title : (char *) "";
	prog = (double)done;
	prog /= total;
	pos = MIN((u32)(20 * prog), 20);

	if (pos>prev_pos) {
		prev_pos = 0;
		prev_pc = 0;
	}
	pc = (u32)(100 * prog);
	if ((pos != prev_pos) || (pc != prev_pc)) {
		prev_pos = pos;
		prev_pc = pc;
		fprintf(stderr, "%s: |%s| (%02d/100)\r", szT, szProg[pos], pc);
		fflush(stderr);
	}
	if (done == total) {
		u32 len = (u32)strlen(szT) + 40;
		while (len) {
			fprintf(stderr, " ");
			len--;
		};
		fprintf(stderr, "\r");
	}
}

static gf_on_progress_cbk prog_cbk = NULL;
static void *user_cbk;

GF_EXPORT
void gf_set_progress(const char *title, u64 done, u64 total)
{
	if (prog_cbk) {
		prog_cbk(user_cbk, title, done, total);
	}
#ifndef _WIN32_WCE
	else {
		gf_on_progress_std(title, done, total);
	}
#endif
}

GF_EXPORT
void gf_set_progress_callback(void *_user_cbk, gf_on_progress_cbk _prog_cbk)
{
	prog_cbk = _prog_cbk;
	user_cbk = _user_cbk;
}

/*ENTRIES MUST BE IN THE SAME ORDER AS LOG_TOOL DECLARATION IN <gpac/tools.h>*/
static struct log_tool_info {
	u32 type;
	const char *name;
	GF_LOG_Level level;
} global_log_tools[] =
{
	{ GF_LOG_CORE, "core", GF_LOG_WARNING },
	{ GF_LOG_CODING, "coding", GF_LOG_WARNING },
	{ GF_LOG_CONTAINER, "container", GF_LOG_WARNING },
	{ GF_LOG_NETWORK, "network", GF_LOG_WARNING },
	{ GF_LOG_RTP, "rtp", GF_LOG_WARNING },
	{ GF_LOG_AUTHOR, "author", GF_LOG_WARNING },
	{ GF_LOG_SYNC, "sync", GF_LOG_WARNING },
	{ GF_LOG_CODEC, "codec", GF_LOG_WARNING },
	{ GF_LOG_PARSER, "parser", GF_LOG_WARNING },
	{ GF_LOG_MEDIA, "media", GF_LOG_WARNING },
	{ GF_LOG_SCENE, "scene", GF_LOG_WARNING },
	{ GF_LOG_SCRIPT, "script", GF_LOG_WARNING },
	{ GF_LOG_INTERACT, "interact", GF_LOG_WARNING },
	{ GF_LOG_COMPOSE, "compose", GF_LOG_WARNING },
	{ GF_LOG_CACHE, "cache", GF_LOG_WARNING },
	{ GF_LOG_MMIO, "mmio", GF_LOG_WARNING },
	{ GF_LOG_RTI, "rti", GF_LOG_WARNING },
	{ GF_LOG_SMIL, "smil", GF_LOG_WARNING },
	{ GF_LOG_MEMORY, "mem", GF_LOG_WARNING },
	{ GF_LOG_AUDIO, "audio", GF_LOG_WARNING },
	{ GF_LOG_MODULE, "module", GF_LOG_WARNING },
	{ GF_LOG_MUTEX, "mutex", GF_LOG_WARNING },
	{ GF_LOG_CONDITION, "condition", GF_LOG_WARNING },
	{ GF_LOG_DASH, "dash", GF_LOG_WARNING },
	{ GF_LOG_CONSOLE, "console", GF_LOG_INFO },
	{ GF_LOG_APP, "app", GF_LOG_INFO },
	{ GF_LOG_SCHEDULER, "scheduler", GF_LOG_INFO },
};

GF_EXPORT
GF_Err gf_log_modify_tools_levels(const char *val)
{
#ifndef GPAC_DISABLE_LOG
	u32 level;
	char *sep, *sep_level;
	while (val && strlen(val)) {
		const char *next_val = NULL;
		const char *tools = NULL;
		/*look for log level*/
		sep_level = strchr(val, '@');
		if (!sep_level) {
			fprintf(stderr, "Unrecognized log format %s - expecting logTool@logLevel\n", val);
			return GF_BAD_PARAM;
		}

		if (!strnicmp(sep_level + 1, "error", 5)) {
			level = GF_LOG_ERROR;
			next_val = sep_level + 1 + 5;
		}
		else if (!strnicmp(sep_level + 1, "warning", 7)) {
			level = GF_LOG_WARNING;
			next_val = sep_level + 1 + 7;
		}
		else if (!strnicmp(sep_level + 1, "info", 4)) {
			level = GF_LOG_INFO;
			next_val = sep_level + 1 + 4;
		}
		else if (!strnicmp(sep_level + 1, "debug", 5)) {
			level = GF_LOG_DEBUG;
			next_val = sep_level + 1 + 5;
		}
		else if (!strnicmp(sep_level + 1, "quiet", 5)) {
			level = GF_LOG_QUIET;
			next_val = sep_level + 1 + 5;
		}
		else {
			fprintf(stderr, "Unknown log level specified: %s\n", sep_level + 1);
			return GF_BAD_PARAM;
		}

		sep_level[0] = 0;
		tools = val;
		while (tools) {
			u32 i;

			sep = strchr(tools, ':');
			if (sep) sep[0] = 0;

			if (!stricmp(tools, "all")) {
				for (i = 0; i<GF_LOG_TOOL_MAX; i++)
					global_log_tools[i].level = level;
			}
			else {
				Bool found = GF_FALSE;
				for (i = 0; i<GF_LOG_TOOL_MAX; i++) {
					if (!strcmp(global_log_tools[i].name, tools)) {
						global_log_tools[i].level = level;
						found = GF_TRUE;
					}
				}
				if (!found) {
					sep_level[0] = '@';
					if (sep) sep[0] = ':';
					fprintf(stderr, "Unknown log tool specified: %s\n", val);
					return GF_BAD_PARAM;
				}
			}

			if (!sep) break;
			sep[0] = ':';
			tools = sep + 1;
		}

		sep_level[0] = '@';
		if (!next_val[0]) break;
		val = next_val + 1;
	}
#endif
	return GF_OK;
}

GF_EXPORT
GF_Err gf_log_set_tools_levels(const char *val)
{
#ifndef GPAC_DISABLE_LOG
	u32 i;
	for (i = 0; i<GF_LOG_TOOL_MAX; i++)
		global_log_tools[i].level = GF_LOG_WARNING;

	return gf_log_modify_tools_levels(val);
#else
	return GF_OK;
#endif
}

GF_EXPORT
char *gf_log_get_tools_levels()
{
#ifndef GPAC_DISABLE_LOG
	u32 i, level, len;
	char szLogs[GF_MAX_PATH];
	char szLogTools[GF_MAX_PATH];
	strcpy(szLogTools, "");

	level = GF_LOG_QUIET;
	while (level <= GF_LOG_DEBUG) {
		u32 nb_tools = 0;
		strcpy(szLogs, "");
		for (i = 0; i<GF_LOG_TOOL_MAX; i++) {
			if (global_log_tools[i].level == level) {
				strcat(szLogs, global_log_tools[i].name);
				strcat(szLogs, ":");
				nb_tools++;
			}
		}
		if (nb_tools) {
			char *levelstr = "@warning";
			if (level == GF_LOG_QUIET) levelstr = "@quiet";
			else if (level == GF_LOG_ERROR) levelstr = "@error";
			else if (level == GF_LOG_WARNING) levelstr = "@warning";
			else if (level == GF_LOG_INFO) levelstr = "@info";
			else if (level == GF_LOG_DEBUG) levelstr = "@debug";

			if (nb_tools>GF_LOG_TOOL_MAX / 2) {
				strcpy(szLogs, szLogTools);
				strcpy(szLogTools, "all");
				strcat(szLogTools, levelstr);
				if (strlen(szLogs)) {
					strcat(szLogTools, ":");
					strcat(szLogTools, szLogs);
				}
			}
			else {
				if (strlen(szLogTools)) {
					strcat(szLogTools, ":");
				}
				/*remove last ':' from tool*/
				szLogs[strlen(szLogs) - 1] = 0;
				strcat(szLogTools, szLogs);
				strcat(szLogTools, levelstr);
			}
		}
		level++;
	}
	len = (u32)strlen(szLogTools);
	if (len) {
		/*remove last ':' from level*/
		if (szLogTools[len - 1] == ':') szLogTools[len - 1] = 0;
		return gf_strdup(szLogTools);
	}
#endif
	return gf_strdup("all@quiet");
}

#ifndef GPAC_DISABLE_LOG
u32 call_lev = 0;
u32 call_tool = 0;

GF_EXPORT
Bool gf_log_tool_level_on(GF_LOG_Tool log_tool, GF_LOG_Level log_level)
{
	assert(log_tool<GF_LOG_TOOL_MAX);
	if (global_log_tools[log_tool].level >= log_level) return GF_TRUE;
	return GF_FALSE;
}

#define RED    "\x1b[31m"
#define YELLOW "\x1b[33m"
#define GREEN  "\x1b[32m"
#define CYAN   "\x1b[36m"
#define WHITE  "\x1b[37m"
#define RESET  "\x1b[0m"

void default_log_callback(void *cbck, GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vlist)
{
#if defined(WIN32) && !defined(GPAC_CONFIG_WIN32)
	if (console == NULL) {
		CONSOLE_SCREEN_BUFFER_INFO console_info;
		console = GetStdHandle(STD_ERROR_HANDLE);
		assert(console != INVALID_HANDLE_VALUE);
		if (console != INVALID_HANDLE_VALUE) {
			GetConsoleScreenBufferInfo(console, &console_info);
			console_attr_ori = console_info.wAttributes;
		}
	}
	switch (level) {
	case GF_LOG_ERROR:
		SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_INTENSITY);
		break;
	case GF_LOG_WARNING:
		SetConsoleTextAttribute(console, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
		break;
	case GF_LOG_INFO:
		SetConsoleTextAttribute(console, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
		break;
	case GF_LOG_DEBUG:
		SetConsoleTextAttribute(console, FOREGROUND_GREEN);
		break;
	default:
		SetConsoleTextAttribute(console, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE);
		break;
	}

	vfprintf(stderr, fmt, vlist);
	SetConsoleTextAttribute(console, console_attr_ori);
#elif !defined(_WIN32_WCE)
	switch (level) {
	case GF_LOG_ERROR:
		fprintf(stderr, RED);
		break;
	case GF_LOG_WARNING:
		fprintf(stderr, YELLOW);
		break;
	case GF_LOG_INFO:
		fprintf(stderr, GREEN);
		break;
	case GF_LOG_DEBUG:
		fprintf(stderr, CYAN);
		break;
	default:
		fprintf(stderr, WHITE);
		break;
	}

	vfprintf(stderr, fmt, vlist);
	fprintf(stderr, RESET);
#else /*_WIN32_WCE*/
	/*no log*/
#endif
}

static void *user_log_cbk = NULL;
gf_log_cbk log_cbk = default_log_callback;
static Bool log_exit_on_error = GF_FALSE;

GF_EXPORT
void gf_log(const char *fmt, ...)
{
	va_list vl;
	va_start(vl, fmt);
	log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl);
	va_end(vl);
	if (log_exit_on_error && (call_lev == GF_LOG_ERROR) && (call_tool != GF_LOG_MEMORY)) {
		exit(1);
	}
}

GF_EXPORT
void gf_log_va_list(GF_LOG_Level level, GF_LOG_Tool tool, const char *fmt, va_list vl)
{
	log_cbk(user_log_cbk, call_lev, call_tool, fmt, vl);
	if (log_exit_on_error && (call_lev == GF_LOG_ERROR) && (call_tool != GF_LOG_MEMORY)) {
		exit(1);
	}
}

GF_EXPORT
void gf_log_set_strict_error(Bool strict)
{
	log_exit_on_error = strict;
}

GF_EXPORT
void gf_log_set_tool_level(GF_LOG_Tool tool, GF_LOG_Level level)
{
	assert(tool <= GF_LOG_TOOL_MAX);
	if (tool == GF_LOG_ALL) {
		u32 i;
		for (i = 0; i<GF_LOG_TOOL_MAX; i++)
			global_log_tools[i].level = level;
	}
	else {
		global_log_tools[tool].level = level;
	}
}

GF_EXPORT
void gf_log_lt(GF_LOG_Level ll, GF_LOG_Tool lt)
{
	call_lev = ll;
	call_tool = lt;
}

GF_EXPORT
gf_log_cbk gf_log_set_callback(void *usr_cbk, gf_log_cbk cbk)
{
	gf_log_cbk prev_cbk = log_cbk;
	log_cbk = cbk;
	if (!log_cbk) log_cbk = default_log_callback;
	if (usr_cbk) user_log_cbk = usr_cbk;
	return prev_cbk;
}

#else
GF_EXPORT
void gf_log(const char *fmt, ...)
{
}
GF_EXPORT
void gf_log_lt(GF_LOG_Level ll, GF_LOG_Tool lt)
{
}
GF_EXPORT
void gf_log_set_strict_error(Bool strict)
{
}

GF_EXPORT
gf_log_cbk gf_log_set_callback(void *usr_cbk, gf_log_cbk cbk)
{
	return NULL;
}
#endif

static char szErrMsg[20];

GF_EXPORT
const char *gf_error_to_string(GF_Err e)
{
	switch (e) {
	case GF_EOS:
		return "End Of Stream / File";
	case GF_OK:
		return "No Error";

		/*General errors */
	case GF_BAD_PARAM:
		return "Bad Parameter";
	case GF_OUT_OF_MEM:
		return "Out Of Memory";
	case GF_IO_ERR:
		return "I/O Error";
	case GF_NOT_SUPPORTED:
		return "Feature Not Supported";
	case GF_CORRUPTED_DATA:
		return "Corrupted Data in file/stream";

		/*File Format Errors */
	case GF_ISOM_INVALID_FILE:
		return "Invalid IsoMedia File";
	case GF_ISOM_INCOMPLETE_FILE:
		return "IsoMedia File is truncated";
	case GF_ISOM_INVALID_MEDIA:
		return "Invalid IsoMedia Media";
	case GF_ISOM_INVALID_MODE:
		return "Invalid Mode while accessing the file";
	case GF_ISOM_UNKNOWN_DATA_REF:
		return "Media Data Reference not found";

		/*Object Descriptor Errors */
	case GF_ODF_INVALID_DESCRIPTOR:
		return "Invalid MPEG-4 Descriptor";
	case GF_ODF_FORBIDDEN_DESCRIPTOR:
		return "MPEG-4 Descriptor Not Allowed";
	case GF_ODF_INVALID_COMMAND:
		return "Read OD Command Failed";

		/*BIFS Errors */
	case GF_SG_UNKNOWN_NODE:
		return "Unknown BIFS Node";
	case GF_SG_INVALID_PROTO:
		return "Invalid Proto Interface";
	case GF_BIFS_UNKNOWN_VERSION:
		return "Invalid BIFS version";
	case GF_SCRIPT_ERROR:
		return "Invalid Script";

		/*MPEG-4 Errors */
	case GF_BUFFER_TOO_SMALL:
		return "Bad Buffer size (too small)";
	case GF_NON_COMPLIANT_BITSTREAM:
		return "BitStream Not Compliant";
	case GF_CODEC_NOT_FOUND:
		return "Media Codec not found";

		/*DMIF errors - local and control plane */
	case GF_URL_ERROR:
		return "Requested URL is not valid or cannot be found";

	case GF_SERVICE_ERROR:
		return "Internal Service Error";
	case GF_REMOTE_SERVICE_ERROR:
		return "Dialog Failure with remote peer";

	case GF_STREAM_NOT_FOUND:
		return "Media Channel couldn't be found";

	case GF_IP_ADDRESS_NOT_FOUND:
		return "IP Address Not Found";
	case GF_IP_CONNECTION_FAILURE:
		return "IP Connection Failed";
	case GF_IP_NETWORK_FAILURE:
		return "Network Unreachable";

	case GF_IP_NETWORK_EMPTY:
		return "Network Timeout";
	case GF_IP_SOCK_WOULD_BLOCK:
		return "Socket Would Block";
	case GF_IP_CONNECTION_CLOSED:
		return "Connection to server closed";
	case GF_IP_UDP_TIMEOUT:
		return "UDP traffic timeout";
	case GF_AUTHENTICATION_FAILURE:
		return "Authentication failure";
	case GF_SCRIPT_NOT_READY:
		return "Script not ready for playback";
	case GF_INVALID_CONFIGURATION:
		return "Bad configuration for the current context";
	case GF_NOT_FOUND:
		return "At least one required element has not been found";
	case GF_MISSING_REQUIREMENTS:
		return "The filter is missing at least one requirement";
	case GF_WRONG_DATAFORMAT:
		return "Unexpected data format";
	default:
		sprintf(szErrMsg, "Unknown Error (%d)", e);
		return szErrMsg;
	}
}

static const u32 gf_crc_table[256] = {
	0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
	0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
	0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
	0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
	0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
	0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
	0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
	0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
	0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
	0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
	0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
	0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
	0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4,
	0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
	0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
	0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
	0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
	0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
	0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
	0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
	0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
	0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
	0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
	0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
	0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
	0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
	0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
	0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
	0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
	0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
	0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
	0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
	0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
	0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
	0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
	0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
	0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
	0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
	0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
	0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
	0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
	0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
	0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};

GF_EXPORT
u32 gf_crc_32(const char *data, u32 len)
{
	register u32 i;
	u32 crc = 0xffffffff;
	if (!data) return 0;
	for (i = 0; i<len; i++)
		crc = (crc << 8) ^ gf_crc_table[((crc >> 24) ^ *data++) & 0xff];

	return crc;
}


#define CHECK_MAC(_a) "#_a :" ? (_a) ? "yes":"no"

GF_EXPORT
const char *gpac_features()
{
	const char *features = ""
#ifdef GPAC_64_BITS
		"GPAC_64_BITS "
#endif

#ifdef GPAC_FIXED_POINT
		"GPAC_FIXED_POINT "
#endif
#ifdef GPAC_MEMORY_TRACKING
		"GPAC_MEMORY_TRACKING "
#endif
#ifdef GPAC_BIG_ENDIAN
		"GPAC_BIG_ENDIAN "
#endif
#ifdef GPAC_HAS_SSL
		"GPAC_HAS_SSL "
#endif
#ifdef GPAC_HAS_SPIDERMONKEY
		"GPAC_HAS_SPIDERMONKEY "
#endif
#ifdef GPAC_HAS_JPEG
		"GPAC_HAS_JPEG "
#endif
#ifdef GPAC_HAS_PNG
		"GPAC_HAS_PNG "
#endif
#ifdef GPAC_DISABLE_3D
		"GPAC_DISABLE_3D "
#endif
#ifdef GPAC_USE_TINYGL
		"GPAC_USE_TINYGL "
#endif
#ifdef GPAC_USE_GLES1X
		"GPAC_USE_GLES1X "
#endif
#if defined(_WIN32_WCE)
#ifdef GPAC_USE_IGPP
		"GPAC_USE_IGPP "
#endif
#ifdef GPAC_USE_IGPP_HP
		"GPAC_USE_IGPP_HP "
#endif
#endif
#ifdef GPAC_DISABLE_SVG
		"GPAC_DISABLE_SVG "
#endif
#ifdef GPAC_DISABLE_VRML
		"GPAC_DISABLE_VRML "
#endif
#ifdef GPAC_MINIMAL_ODF
		"GPAC_MINIMAL_ODF "
#endif
#ifdef GPAC_DISABLE_BIFS
		"GPAC_DISABLE_BIFS "
#endif
#ifdef GPAC_DISABLE_QTVR
		"GPAC_DISABLE_QTVR "
#endif
#ifdef GPAC_DISABLE_AVILIB
		"GPAC_DISABLE_AVILIB "
#endif
#ifdef GPAC_DISABLE_OGG
		"GPAC_DISABLE_OGG "
#endif
#ifdef GPAC_DISABLE_MPEG2PS
		"GPAC_DISABLE_MPEG2PS "
#endif
#ifdef GPAC_DISABLE_MPEG2PS
		"GPAC_DISABLE_MPEG2TS "
#endif
#ifdef GPAC_DISABLE_SENG
		"GPAC_DISABLE_SENG "
#endif
#ifdef GPAC_DISABLE_MEDIA_IMPORT
		"GPAC_DISABLE_MEDIA_IMPORT "
#endif
#ifdef GPAC_DISABLE_AV_PARSERS
		"GPAC_DISABLE_AV_PARSERS "
#endif
#ifdef GPAC_DISABLE_MEDIA_EXPORT
		"GPAC_DISABLE_MEDIA_EXPORT "
#endif
#ifdef GPAC_DISABLE_SWF_IMPORT
		"GPAC_DISABLE_SWF_IMPORT "
#endif
#ifdef GPAC_DISABLE_SCENE_STATS
		"GPAC_DISABLE_SCENE_STATS "
#endif
#ifdef GPAC_DISABLE_SCENE_DUMP
		"GPAC_DISABLE_SCENE_DUMP "
#endif
#ifdef GPAC_DISABLE_SCENE_ENCODER
		"GPAC_DISABLE_SCENE_ENCODER "
#endif
#ifdef GPAC_DISABLE_LOADER_ISOM
		"GPAC_DISABLE_LOADER_ISOM "
#endif
#ifdef GPAC_DISABLE_OD_DUMP
		"GPAC_DISABLE_OD_DUMP "
#endif
#ifdef GPAC_DISABLE_MCRYPT
		"GPAC_DISABLE_MCRYPT "
#endif
#ifdef GPAC_DISABLE_ISOM
		"GPAC_DISABLE_MCRYPT "
#endif
#ifdef GPAC_DISABLE_ISOM_HINTING
		"GPAC_DISABLE_ISOM_HINTING "
#endif
#ifdef GPAC_DISABLE_ISOM_WRITE
		"GPAC_DISABLE_ISOM_WRITE "
#endif
#ifdef GPAC_DISABLE_ISOM_FRAGMENTS
		"GPAC_DISABLE_ISOM_FRAGMENTS "
#endif
#ifdef GPAC_DISABLE_LASER
		"GPAC_DISABLE_LASER "
#endif
#ifdef GPAC_DISABLE_STREAMING
		"GPAC_DISABLE_STREAMING "
#endif

		;
	return features;
}


static const struct lang_def {
	const char *name;
	const char *three_char_code;
	const char *two_char_code;
} defined_languages[] =
{
	{ "Abkhazian","abk","ab" } ,
	{ "Achinese","ace","" } ,
	{ "Acoli","ach","" } ,
	{ "Adangme","ada","" } ,
	{ "Adyghe; Adygei","ady","" } ,
	{ "Afar","aar","aa" } ,
	{ "Afrihili","afh","" } ,
	{ "Afrikaans","afr","af" } ,
	{ "Afro-Asiatic languages","afa","" } ,
	{ "Ainu","ain","" } ,
	{ "Akan","aka","ak" } ,
	{ "Akkadian","akk","" } ,
	{ "Albanian","sqi","sq" } ,
	{ "Aleut","ale","" } ,
	{ "Algonquian languages","alg","" } ,
	{ "Altaic languages","tut","" } ,
	{ "Amharic","amh","am" } ,
	{ "Angika","anp","" } ,
	{ "Apache languages","apa","" } ,
	{ "Arabic","ara","ar" } ,
	{ "Aragonese","arg","an" } ,
	{ "Arapaho","arp","" } ,
	{ "Arawak","arw","" } ,
	{ "Armenian","hye","hy" } ,
	{ "Aromanian; Arumanian; Macedo-Romanian","rup","" } ,
	{ "Artificial languages","art","" } ,
	{ "Assamese","asm","as" } ,
	{ "Asturian; Bable; Leonese; Asturleonese","ast","" } ,
	{ "Athapascan languages","ath","" } ,
	{ "Australian languages","aus","" } ,
	{ "Austronesian languages","map","" } ,
	{ "Avaric","ava","av" } ,
	{ "Avestan","ave","ae" } ,
	{ "Awadhi","awa","" } ,
	{ "Aymara","aym","ay" } ,
	{ "Azerbaijani","aze","az" } ,
	{ "Balinese","ban","" } ,
	{ "Baltic languages","bat","" } ,
	{ "Baluchi","bal","" } ,
	{ "Bambara","bam","bm" } ,
	{ "Bamileke languages","bai","" } ,
	{ "Banda languages","bad","" } ,
	{ "Bantu languages","bnt","" } ,
	{ "Basa","bas","" } ,
	{ "Bashkir","bak","ba" } ,
	{ "Basque","eus","eu" } ,
	{ "Batak languages","btk","" } ,
	{ "Beja; Bedawiyet","bej","" } ,
	{ "Belarusian","bel","be" } ,
	{ "Bemba","bem","" } ,
	{ "Bengali","ben","bn" } ,
	{ "Berber languages","ber","" } ,
	{ "Bhojpuri","bho","" } ,
	{ "Bihari languages","bih","bh" } ,
	{ "Bikol","bik","" } ,
	{ "Bini; Edo","bin","" } ,
	{ "Bislama","bis","bi" } ,
	{ "Blin; Bilin","byn","" } ,
	{ "Blissymbols; Blissymbolics; Bliss","zbl","" } ,
	{ "Bokmal, Norwegian; Norwegian Bokmal","nob","nb" } ,
	{ "Bosnian","bos","bs" } ,
	{ "Braj","bra","" } ,
	{ "Breton","bre","br" } ,
	{ "Buginese","bug","" } ,
	{ "Bulgarian","bul","bg" } ,
	{ "Buriat","bua","" } ,
	{ "Burmese","mya","my" } ,
	{ "Caddo","cad","" } ,
	{ "Catalan; Valencian","cat","ca" } ,
	{ "Caucasian languages","cau","" } ,
	{ "Cebuano","ceb","" } ,
	{ "Celtic languages","cel","" } ,
	{ "Central American Indian languages","cai","" } ,
	{ "Central Khmer","khm","km" } ,
	{ "Chagatai","chg","" } ,
	{ "Chamic languages","cmc","" } ,
	{ "Chamorro","cha","ch" } ,
	{ "Chechen","che","ce" } ,
	{ "Cherokee","chr","" } ,
	{ "Cheyenne","chy","" } ,
	{ "Chibcha","chb","" } ,
	{ "Chichewa; Chewa; Nyanja","nya","ny" } ,
	{ "Chinese","zho","zh" } ,
	{ "Chinook jargon","chn","" } ,
	{ "Chipewyan; Dene Suline","chp","" } ,
	{ "Choctaw","cho","" } ,
	{ "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church}  S{ lavonic","chu","cu" } ,
	{ "Chuukese","chk","" } ,
	{ "Chuvash","chv","cv" } ,
	{ "Classical Newari; Old Newari; Classical Nepal Bhasa","nwc","" } ,
	{ "Classical Syriac","syc","" } ,
	{ "Coptic","cop","" } ,
	{ "Cornish","cor","kw" } ,
	{ "Corsican","cos","co" } ,
	{ "Cree","cre","cr" } ,
	{ "Creek","mus","" } ,
	{ "Creoles and pidgins","crp","" } ,
	{ "Creoles and pidgins, English based","cpe","" } ,
	{ "Creoles and pidgins, French-based","cpf","" } ,
	{ "Creoles and pidgins, Portuguese-based","cpp","" } ,
	{ "Crimean Tatar; Crimean Turkish","crh","" } ,
	{ "Croatian","hrv","hr" } ,
	{ "Cushitic languages","cus","" } ,
	{ "Czech","ces","cs" } ,
	{ "Dakota","dak","" } ,
	{ "Danish","dan","da" } ,
	{ "Dargwa","dar","" } ,
	{ "Delaware","del","" } ,
	{ "Dinka","din","" } ,
	{ "Divehi; Dhivehi; Maldivian","div","dv" } ,
	{ "Dogri","doi","" } ,
	{ "Dogrib","dgr","" } ,
	{ "Dravidian languages","dra","" } ,
	{ "Duala","dua","" } ,
	{ "Dutch, Middle (ca.1050-1350)","dum","" } ,
	{ "Dutch; Flemish","nld","nl" } ,
	{ "Dyula","dyu","" } ,
	{ "Dzongkha","dzo","dz" } ,
	{ "Eastern Frisian","frs","" } ,
	{ "Efik","efi","" } ,
	{ "Egyptian (Ancient)","egy","" } ,
	{ "Ekajuk","eka","" } ,
	{ "Elamite","elx","" } ,
	{ "English","eng","en" } ,
	{ "English, Middle (1100-1500)","enm","" } ,
	{ "English, Old (ca.450-1100)","ang","" } ,
	{ "Erzya","myv","" } ,
	{ "Esperanto","epo","eo" } ,
	{ "Estonian","est","et" } ,
	{ "Ewe","ewe","ee" } ,
	{ "Ewondo","ewo","" } ,
	{ "Fang","fan","" } ,
	{ "Fanti","fat","" } ,
	{ "Faroese","fao","fo" } ,
	{ "Fijian","fij","fj" } ,
	{ "Filipino; Pilipino","fil","" } ,
	{ "Finnish","fin","fi" } ,
	{ "Finno-Ugrian languages","fiu","" } ,
	{ "Fon","fon","" } ,
	{ "French","fra","fr" } ,
	{ "French, Middle (ca.1400-1600)","frm","" } ,
	{ "French, Old (842-ca.1400)","fro","" } ,
	{ "Friulian","fur","" } ,
	{ "Fulah","ful","ff" } ,
	{ "Ga","gaa","" } ,
	{ "Gaelic; Scottish Gaelic","gla","gd" } ,
	{ "Galibi Carib","car","" } ,
	{ "Galician","glg","gl" } ,
	{ "Ganda","lug","lg" } ,
	{ "Gayo","gay","" } ,
	{ "Gbaya","gba","" } ,
	{ "Geez","gez","" } ,
	{ "Georgian","kat","ka" } ,
	{ "German","deu","de" } ,
	{ "German, Middle High (ca.1050-1500)","gmh","" } ,
	{ "German, Old High (ca.750-1050)","goh","" } ,
	{ "Germanic languages","gem","" } ,
	{ "Gilbertese","gil","" } ,
	{ "Gondi","gon","" } ,
	{ "Gorontalo","gor","" } ,
	{ "Gothic","got","" } ,
	{ "Grebo","grb","" } ,
	{ "Greek, Ancient (to 1453)","grc","" } ,
	{ "Greek, Modern (1453-)","ell","el" } ,
	{ "Guarani","grn","gn" } ,
	{ "Gujarati","guj","gu" } ,
	{ "Gwich'in","gwi","" } ,
	{ "Haida","hai","" } ,
	{ "Haitian; Haitian Creole","hat","ht" } ,
	{ "Hausa","hau","ha" } ,
	{ "Hawaiian","haw","" } ,
	{ "Hebrew","heb","he" } ,
	{ "Herero","her","hz" } ,
	{ "Hiligaynon","hil","" } ,
	{ "Himachali languages; Western Pahari languages","him","" } ,
	{ "Hindi","hin","hi" } ,
	{ "Hiri Motu","hmo","ho" } ,
	{ "Hittite","hit","" } ,
	{ "Hmong; Mong","hmn","" } ,
	{ "Hungarian","hun","hu" } ,
	{ "Hupa","hup","" } ,
	{ "Iban","iba","" } ,
	{ "Icelandic","isl","is" } ,
	{ "Ido","ido","io" } ,
	{ "Igbo","ibo","ig" } ,
	{ "Ijo languages","ijo","" } ,
	{ "Iloko","ilo","" } ,
	{ "Inari Sami","smn","" } ,
	{ "Indic languages","inc","" } ,
	{ "Indo-European languages","ine","" } ,
	{ "Indonesian","ind","id" } ,
	{ "Ingush","inh","" } ,
	{ "Interlingua (International Auxiliary Language Association)","ina","ia" } ,
	{ "Interlingue; Occidental","ile","ie" } ,
	{ "Inuktitut","iku","iu" } ,
	{ "Inupiaq","ipk","ik" } ,
	{ "Iranian languages","ira","" } ,
	{ "Irish","gle","ga" } ,
	{ "Irish, Middle (900-1200)","mga","" } ,
	{ "Irish, Old (to 900)","sga","" } ,
	{ "Iroquoian languages","iro","" } ,
	{ "Italian","ita","it" } ,
	{ "Japanese","jpn","ja" } ,
	{ "Javanese","jav","jv" } ,
	{ "Judeo-Arabic","jrb","" } ,
	{ "Judeo-Persian","jpr","" } ,
	{ "Kabardian","kbd","" } ,
	{ "Kabyle","kab","" } ,
	{ "Kachin; Jingpho","kac","" } ,
	{ "Kalaallisut; Greenlandic","kal","kl" } ,
	{ "Kalmyk; Oirat","xal","" } ,
	{ "Kamba","kam","" } ,
	{ "Kannada","kan","kn" } ,
	{ "Kanuri","kau","kr" } ,
	{ "Kara-Kalpak","kaa","" } ,
	{ "Karachay-Balkar","krc","" } ,
	{ "Karelian","krl","" } ,
	{ "Karen languages","kar","" } ,
	{ "Kashmiri","kas","ks" } ,
	{ "Kashubian","csb","" } ,
	{ "Kawi","kaw","" } ,
	{ "Kazakh","kaz","kk" } ,
	{ "Khasi","kha","" } ,
	{ "Khoisan languages","khi","" } ,
	{ "Khotanese; Sakan","kho","" } ,
	{ "Kikuyu; Gikuyu","kik","ki" } ,
	{ "Kimbundu","kmb","" } ,
	{ "Kinyarwanda","kin","rw" } ,
	{ "Kirghiz; Kyrgyz","kir","ky" } ,
	{ "Klingon; tlhIngan-Hol","tlh","" } ,
	{ "Komi","kom","kv" } ,
	{ "Kongo","kon","kg" } ,
	{ "Konkani","kok","" } ,
	{ "Korean","kor","ko" } ,
	{ "Kosraean","kos","" } ,
	{ "Kpelle","kpe","" } ,
	{ "Kru languages","kro","" } ,
	{ "Kuanyama; Kwanyama","kua","kj" } ,
	{ "Kumyk","kum","" } ,
	{ "Kurdish","kur","ku" } ,
	{ "Kurukh","kru","" } ,
	{ "Kutenai","kut","" } ,
	{ "Ladino","lad","" } ,
	{ "Lahnda","lah","" } ,
	{ "Lamba","lam","" } ,
	{ "Land Dayak languages","day","" } ,
	{ "Lao","lao","lo" } ,
	{ "Latin","lat","la" } ,
	{ "Latvian","lav","lv" } ,
	{ "Lezghian","lez","" } ,
	{ "Limburgan; Limburger; Limburgish","lim","li" } ,
	{ "Lingala","lin","ln" } ,
	{ "Lithuanian","lit","lt" } ,
	{ "Lojban","jbo","" } ,
	{ "Low German; Low Saxon; German, Low; Saxon, Low","nds","" } ,
	{ "Lower Sorbian","dsb","" } ,
	{ "Lozi","loz","" } ,
	{ "Luba-Katanga","lub","lu" } ,
	{ "Luba-Lulua","lua","" } ,
	{ "Luiseno","lui","" } ,
	{ "Lule Sami","smj","" } ,
	{ "Lunda","lun","" } ,
	{ "Luo (Kenya and Tanzania)","luo","" } ,
	{ "Lushai","lus","" } ,
	{ "Luxembourgish; Letzeburgesch","ltz","lb" } ,
	{ "Macedonian","mkd","mk" } ,
	{ "Madurese","mad","" } ,
	{ "Magahi","mag","" } ,
	{ "Maithili","mai","" } ,
	{ "Makasar","mak","" } ,
	{ "Malagasy","mlg","mg" } ,
	{ "Malay","msa","ms" } ,
	{ "Malayalam","mal","ml" } ,
	{ "Maltese","mlt","mt" } ,
	{ "Manchu","mnc","" } ,
	{ "Mandar","mdr","" } ,
	{ "Mandingo","man","" } ,
	{ "Manipuri","mni","" } ,
	{ "Manobo languages","mno","" } ,
	{ "Manx","glv","gv" } ,
	{ "Maori","mri","mi" } ,
	{ "Mapudungun; Mapuche","arn","" } ,
	{ "Marathi","mar","mr" } ,
	{ "Mari","chm","" } ,
	{ "Marshallese","mah","mh" } ,
	{ "Marwari","mwr","" } ,
	{ "Masai","mas","" } ,
	{ "Mayan languages","myn","" } ,
	{ "Mende","men","" } ,
	{ "Mi'kmaq; Micmac","mic","" } ,
	{ "Minangkabau","min","" } ,
	{ "Mirandese","mwl","" } ,
	{ "Mohawk","moh","" } ,
	{ "Moksha","mdf","" } ,
	{ "Mon-Khmer languages","mkh","" } ,
	{ "Mongo","lol","" } ,
	{ "Mongolian","mon","mn" } ,
	{ "Mossi","mos","" } ,
	{ "Multiple languages","mul","" } ,
	{ "Munda languages","mun","" } ,
	{ "N'Ko","nqo","" } ,
	{ "Nahuatl languages","nah","" } ,
	{ "Nauru","nau","na" } ,
	{ "Navajo; Navaho","nav","nv" } ,
	{ "Ndebele, North; North Ndebele","nde","nd" } ,
	{ "Ndebele, South; South Ndebele","nbl","nr" } ,
	{ "Ndonga","ndo","ng" } ,
	{ "Neapolitan","nap","" } ,
	{ "Nepal Bhasa; Newari","new","" } ,
	{ "Nepali","nep","ne" } ,
	{ "Nias","nia","" } ,
	{ "Niger-Kordofanian languages","nic","" } ,
	{ "Nilo-Saharan languages","ssa","" } ,
	{ "Niuean","niu","" } ,
	{ "No linguistic content; Not applicable","zxx","" } ,
	{ "Nogai","nog","" } ,
	{ "Norse, Old","non","" } ,
	{ "North American Indian languages","nai","" } ,
	{ "Northern Frisian","frr","" } ,
	{ "Northern Sami","sme","se" } ,
	{ "Norwegian","nor","no" } ,
	{ "Norwegian Nynorsk; Nynorsk, Norwegian","nno","nn" } ,
	{ "Nubian languages","nub","" } ,
	{ "Nyamwezi","nym","" } ,
	{ "Nyankole","nyn","" } ,
	{ "Nyoro","nyo","" } ,
	{ "Nzima","nzi","" } ,
	{ "Occitan (post 1500)","oci","oc" } ,
	{ "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)","arc","" } ,
	{ "Ojibwa","oji","oj" } ,
	{ "Oriya","ori","or" } ,
	{ "Oromo","orm","om" } ,
	{ "Osage","osa","" } ,
	{ "Ossetian; Ossetic","oss","os" } ,
	{ "Otomian languages","oto","" } ,
	{ "Pahlavi","pal","" } ,
	{ "Palauan","pau","" } ,
	{ "Pali","pli","pi" } ,
	{ "Pampanga; Kapampangan","pam","" } ,
	{ "Pangasinan","pag","" } ,
	{ "Panjabi; Punjabi","pan","pa" } ,
	{ "Papiamento","pap","" } ,
	{ "Papuan languages","paa","" } ,
	{ "Pedi; Sepedi; Northern Sotho","nso","" } ,
	{ "Persian","fas","fa" } ,
	{ "Persian, Old (ca.600-400 B.C.)","peo","" } ,
	{ "Philippine languages","phi","" } ,
	{ "Phoenician","phn","" } ,
	{ "Pohnpeian","pon","" } ,
	{ "Polish","pol","pl" } ,
	{ "Portuguese","por","pt" } ,
	{ "Prakrit languages","pra","" } ,
	//{ "Provençal, Old (to 1500);Occitan, Old (to 1500)","pro",""} ,
	{ "Pushto; Pashto","pus","ps" } ,
	{ "Quechua","que","qu" } ,
	{ "Rajasthani","raj","" } ,
	{ "Rapanui","rap","" } ,
	{ "Rarotongan; Cook Islands Maori","rar","" } ,
	{ "Reserved for local use","qaa-qtz","" } ,
	{ "Romance languages","roa","" } ,
	{ "Romanian; Moldavian; Moldovan","ron","ro" } ,
	{ "Romansh","roh","rm" } ,
	{ "Romany","rom","" } ,
	{ "Rundi","run","rn" } ,
	{ "Russian","rus","ru" } ,
	{ "Salishan languages","sal","" } ,
	{ "Samaritan Aramaic","sam","" } ,
	{ "Sami languages","smi","" } ,
	{ "Samoan","smo","sm" } ,
	{ "Sandawe","sad","" } ,
	{ "Sango","sag","sg" } ,
	{ "Sanskrit","san","sa" } ,
	{ "Santali","sat","" } ,
	{ "Sardinian","srd","sc" } ,
	{ "Sasak","sas","" } ,
	{ "Scots","sco","" } ,
	{ "Selkup","sel","" } ,
	{ "Semitic languages","sem","" } ,
	{ "Serbian","srp","sr" } ,
	{ "Serer","srr","" } ,
	{ "Shan","shn","" } ,
	{ "Shona","sna","sn" } ,
	{ "Sichuan Yi; Nuosu","iii","ii" } ,
	{ "Sicilian","scn","" } ,
	{ "Sidamo","sid","" } ,
	{ "Sign Languages","sgn","" } ,
	{ "Siksika","bla","" } ,
	{ "Sindhi","snd","sd" } ,
	{ "Sinhala; Sinhalese","sin","si" } ,
	{ "Sino-Tibetan languages","sit","" } ,
	{ "Siouan languages","sio","" } ,
	{ "Skolt Sami","sms","" } ,
	{ "Slave (Athapascan)","den","" } ,
	{ "Slavic languages","sla","" } ,
	{ "Slovak","slk","sk" } ,
	{ "Slovenian","slv","sl" } ,
	{ "Sogdian","sog","" } ,
	{ "Somali","som","so" } ,
	{ "Songhai languages","son","" } ,
	{ "Soninke","snk","" } ,
	{ "Sorbian languages","wen","" } ,
	{ "Sotho, Southern","sot","st" } ,
	{ "South American Indian languages","sai","" } ,
	{ "Southern Altai","alt","" } ,
	{ "Southern Sami","sma","" } ,
	{ "Spanish; Castilian","spa","es" } ,
	{ "Sranan Tongo","srn","" } ,
	{ "Sukuma","suk","" } ,
	{ "Sumerian","sux","" } ,
	{ "Sundanese","sun","su" } ,
	{ "Susu","sus","" } ,
	{ "Swahili","swa","sw" } ,
	{ "Swati","ssw","ss" } ,
	{ "Swedish","swe","sv" } ,
	{ "Swiss German; Alemannic; Alsatian","gsw","" } ,
	{ "Syriac","syr","" } ,
	{ "Tagalog","tgl","tl" } ,
	{ "Tahitian","tah","ty" } ,
	{ "Tai languages","tai","" } ,
	{ "Tajik","tgk","tg" } ,
	{ "Tamashek","tmh","" } ,
	{ "Tamil","tam","ta" } ,
	{ "Tatar","tat","tt" } ,
	{ "Telugu","tel","te" } ,
	{ "Tereno","ter","" } ,
	{ "Tetum","tet","" } ,
	{ "Thai","tha","th" } ,
	{ "Tibetan","bod","bo" } ,
	{ "Tigre","tig","" } ,
	{ "Tigrinya","tir","ti" } ,
	{ "Timne","tem","" } ,
	{ "Tiv","tiv","" } ,
	{ "Tlingit","tli","" } ,
	{ "Tok Pisin","tpi","" } ,
	{ "Tokelau","tkl","" } ,
	{ "Tonga (Nyasa)","tog","" } ,
	{ "Tonga (Tonga Islands)","ton","to" } ,
	{ "Tsimshian","tsi","" } ,
	{ "Tsonga","tso","ts" } ,
	{ "Tswana","tsn","tn" } ,
	{ "Tumbuka","tum","" } ,
	{ "Tupi languages","tup","" } ,
	{ "Turkish","tur","tr" } ,
	{ "Turkish, Ottoman (1500-1928)","ota","" } ,
	{ "Turkmen","tuk","tk" } ,
	{ "Tuvalu","tvl","" } ,
	{ "Tuvinian","tyv","" } ,
	{ "Twi","twi","tw" } ,
	{ "Udmurt","udm","" } ,
	{ "Ugaritic","uga","" } ,
	{ "Uighur; Uyghur","uig","ug" } ,
	{ "Ukrainian","ukr","uk" } ,
	{ "Umbundu","umb","" } ,
	{ "Uncoded languages","mis","" } ,
	{ "Undetermined","und","" } ,
	{ "Upper Sorbian","hsb","" } ,
	{ "Urdu","urd","ur" } ,
	{ "Uzbek","uzb","uz" } ,
	{ "Vai","vai","" } ,
	{ "Venda","ven","ve" } ,
	{ "Vietnamese","vie","vi" } ,
	//{ "Volapük","vol","vo"} ,
	{ "Votic","vot","" } ,
	{ "Wakashan languages","wak","" } ,
	{ "Walloon","wln","wa" } ,
	{ "Waray","war","" } ,
	{ "Washo","was","" } ,
	{ "Welsh","cym","cy" } ,
	{ "Western Frisian","fry","fy" } ,
	{ "Wolaitta; Wolaytta","wal","" } ,
	{ "Wolof","wol","wo" } ,
	{ "Xhosa","xho","xh" } ,
	{ "Yakut","sah","" } ,
	{ "Yao","yao","" } ,
	{ "Yapese","yap","" } ,
	{ "Yiddish","yid","yi" } ,
	{ "Yoruba","yor","yo" } ,
	{ "Yupik languages","ypk","" } ,
	{ "Zande languages","znd","" } ,
	{ "Zapotec","zap","" } ,
	{ "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki","zza","" } ,
	{ "Zenaga","zen","" } ,
	{ "Zhuang; Chuang","zha","za" } ,
	{ "Zulu","zul","zu" } ,
	{ "Zuni","zun","" } ,
};


GF_EXPORT
u32 gf_lang_get_count()
{
	return sizeof(defined_languages) / sizeof(struct lang_def);
}

GF_EXPORT
s32 gf_lang_find(const char *lang_or_rfc_5646_code)
{
	//find the language ISO639 entry for the given code
	u32 i = 0;
	u32 len = 0;
	char *sep;
	u32 count = sizeof(defined_languages) / sizeof(struct lang_def);

	if (!lang_or_rfc_5646_code) return -1;

	len = (u32)strlen(lang_or_rfc_5646_code);
	sep = (char *)strchr(lang_or_rfc_5646_code, '-');
	if (sep) {
		sep[0] = 0;
		len = (u32)strlen(lang_or_rfc_5646_code);
		sep[0] = '-';
	}

	for (i = 0; i<count; i++) {
		if (!strcmp(defined_languages[i].name, lang_or_rfc_5646_code)) {
			return i;
		}
		if ((len == 3) && !strnicmp(defined_languages[i].three_char_code, lang_or_rfc_5646_code, 3)) {
			return i;
		}
		if ((len == 2) && !strnicmp(defined_languages[i].two_char_code, lang_or_rfc_5646_code, 2)) {
			return i;
		}
	}
	return -1;
}

GF_EXPORT
const char *gf_lang_get_name(u32 idx)
{
	if (idx >= sizeof(defined_languages) / sizeof(struct lang_def)) return NULL;
	return defined_languages[idx].name;
}

GF_EXPORT
const char *gf_lang_get_2cc(u32 idx)
{
	if (idx >= sizeof(defined_languages) / sizeof(struct lang_def)) return NULL;
	return defined_languages[idx].two_char_code;
}

GF_EXPORT
const char *gf_lang_get_3cc(u32 idx)
{
	if (idx >= sizeof(defined_languages) / sizeof(struct lang_def)) return NULL;
	return defined_languages[idx].three_char_code;
}
