/* (c) 2002-2006 by Marcin Wiacek and Michal Cihar */
/* FM stuff by Walek */

#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <locale.h>
#include <signal.h>
#include <ctype.h>
#include <wchar.h>

#include <gammu.h>
#include <gammu-config.h>
#include "gammu.h"
#include "common.h"
#include "memory.h"
#include "message.h"
#include "search.h"
#include "nokia.h"
#include "backup.h"
#include "files.h"
#include "calendar.h"
#include "misc.h"

#include "../helper/locales.h"
#include "../helper/printing.h"
#include "../helper/string.h"
#include "../helper/cmdline.h"

#ifdef DEBUG
#  include "sniff.h"
#endif
#ifdef GSM_ENABLE_NOKIA_DCT3
#  include "depend/nokia/dct3.h"
#  include "depend/nokia/dct3trac/wmx.h"
#endif
#ifdef GSM_ENABLE_NOKIA_DCT4
#  include "depend/nokia/dct4.h"
#endif
#ifdef GSM_ENABLE_ATGEN
#  include "depend/siemens/dsiemens.h"
#endif

#ifdef HAVE_SYS_IOCTL_H
#  include <sys/ioctl.h>
#endif

#ifdef CURL_FOUND
#include <curl/curl.h>
#endif

#define ALL_MEMORY_TYPES "DC|MC|RC|ON|VM|SM|ME|MT|FD|SL"

#define RSS_URL "http://wammu.eu/news/rss/"
#define RSS_STABLE_STRING "Gammu stable version "
#define RSS_TESTING_STRING "Gammu test version "

static void ListNetworks(int argc, char *argv[])
{
	int i = 0;
	const char *country_code = NULL;

	if (argc > 2) {
		for (i = 0; GSM_Countries[i].Code[0] != 0; i++) {
			if (strcmp(GSM_Countries[i].Name, argv[2]) == 0 || strcasecmp(GSM_Countries[i].Code, argv[2]) == 0) {
				country_code = GSM_Countries[i].Code;
				printf(_("Networks for %s:"), GSM_Countries[i].Name);
				printf("\n\n");
				break;
			}
		}
		if (country_code == NULL) {
			printf(_("Unknown country name: %s."), argv[2]);
			printf("\n");
			Terminate(2);
		}
	}
	printf("%-10s %s\n", _("Network"), _("Name"));
	i = 0;
	for (i = 0; GSM_Networks[i].Code[0] != 0; i++) {
		if (country_code == NULL || strncmp(GSM_Networks[i].Code, country_code, 3) == 0) {
			printf("%-10s %s\n", GSM_Networks[i].Code, GSM_Networks[i].Name);
		}
	}
}

static void PrintVersion(void)
{
	printf(_("[Gammu version %s]"), GAMMU_VERSION);
	printf("\n\n");
}

static void Features(int argc UNUSED, char *argv[]UNUSED)
{
	PrintVersion();

	printf("%s\n", _("Compiled in features:"));

	printf(" * %s\n", _("Protocols"));
#ifdef GSM_ENABLE_MBUS2
	printf("  - %s\n", "MBUS2");
#endif
#ifdef GSM_ENABLE_FBUS2
	printf("  - %s\n", "FBUS2");
#endif
#ifdef GSM_ENABLE_FBUS2DLR3
	printf("  - %s\n", "FBUS2DLR3");
#endif
#ifdef GSM_ENABLE_FBUS2PL2303
	printf("  - %s\n", "FBUS2PL2303");
#endif
#ifdef GSM_ENABLE_FBUS2BLUE
	printf("  - %s\n", "FBUS2BLUE");
#endif
#ifdef GSM_ENABLE_FBUS2IRDA
	printf("  - %s\n", "FBUS2IRDA");
#endif
#ifdef GSM_ENABLE_DKU2PHONET
	printf("  - %s\n", "DKU2PHONET");
#endif
#ifdef GSM_ENABLE_DKU2AT
	printf("  - %s\n", "DKU2AT");
#endif
#ifdef GSM_ENABLE_DKU5FBUS2
	printf("  - %s\n", "DKU5FBUS2");
#endif
#ifdef GSM_ENABLE_PHONETBLUE
	printf("  - %s\n", "PHONETBLUE");
#endif
#ifdef GSM_ENABLE_AT
	printf("  - %s\n", "AT");
#endif
#ifdef GSM_ENABLE_ALCABUS
	printf("  - %s\n", "ALCABUS");
#endif
#ifdef GSM_ENABLE_IRDAPHONET
	printf("  - %s\n", "IRDAPHONET");
#endif
#ifdef GSM_ENABLE_IRDAAT
	printf("  - %s\n", "IRDAAT");
#endif
#ifdef GSM_ENABLE_IRDAOBEX
	printf("  - %s\n", "IRDAOBEX");
#endif
#ifdef GSM_ENABLE_IRDAGNAPBUS
	printf("  - %s\n", "IRDAGNAPBUS");
#endif
#ifdef GSM_ENABLE_BLUEGNAPBUS
	printf("  - %s\n", "BLUEGNAPBUS");
#endif
#ifdef GSM_ENABLE_BLUEFBUS2
	printf("  - %s\n", "BLUEFBUS2");
#endif
#ifdef GSM_ENABLE_BLUEPHONET
	printf("  - %s\n", "BLUEPHONET");
#endif
#ifdef GSM_ENABLE_BLUEAT
	printf("  - %s\n", "BLUEAT");
#endif
#ifdef GSM_ENABLE_BLUEOBEX
	printf("  - %s\n", "BLUEOBEX");
#endif
#ifdef LIBUSB_FOUND
	printf("  - %s\n", "FBUSUSB");
#endif
#ifdef GSM_ENABLE_PROXY
	printf("  - %s\n", "PROXY");
#endif

	printf(" * %s\n", _("Phones"));
#ifdef GSM_ENABLE_NOKIA650
	printf("  - %s\n", "NOKIA650");
#endif
#ifdef GSM_ENABLE_NOKIA3320
	printf("  - %s\n", "NOKIA3320");
#endif
#ifdef GSM_ENABLE_NOKIA6110
	printf("  - %s\n", "NOKIA6110");
#endif
#ifdef GSM_ENABLE_NOKIA7110
	printf("  - %s\n", "NOKIA7110");
#endif
#ifdef GSM_ENABLE_NOKIA9210
	printf("  - %s\n", "NOKIA9210");
#endif
#ifdef GSM_ENABLE_NOKIA6510
	printf("  - %s\n", "NOKIA6510");
#endif
#ifdef GSM_ENABLE_NOKIA3650
	printf("  - %s\n", "NOKIA3650");
#endif
#ifdef GSM_ENABLE_NOKIA_DCT3
	printf("  - %s\n", "DCT3");
#endif
#ifdef GSM_ENABLE_NOKIA_DCT4
	printf("  - %s\n", "DCT4");
#endif
#ifdef GSM_ENABLE_ATGEN
	printf("  - %s\n", "ATGEN");
#endif
#ifdef GSM_ENABLE_ALCATEL
	printf("  - %s\n", "ALCATEL");
#endif
#ifdef GSM_ENABLE_SONYERICSSON
	printf("  - %s\n", "SONYERICSSON");
#endif
#ifdef GSM_ENABLE_OBEXGEN
	printf("  - %s\n", "OBEXGEN");
#endif
#ifdef GSM_ENABLE_GNAPGEN
	printf("  - %s\n", "GNAPGEN");
#endif
	printf("  - %s\n", "DUMMY");

	printf(" * %s\n", _("Miscellaneous"));
#ifdef GSM_ENABLE_CELLBROADCAST
	printf("  - %s\n", "CELLBROADCAST");
#endif
#ifdef GSM_ENABLE_BACKUP
	printf("  - %s\n", "BACKUP");
#endif
#ifdef LIBINTL_LIB_FOUND
	printf("  - %s\n", "GETTEXT");
#endif
#ifdef ICONV_FOUND
	printf("  - %s\n", "ICONV");
#endif
#ifdef CURL_FOUND
	printf("  - %s\n", "CURL");
#endif
#ifdef Glib_FOUND
	printf("  - %s\n", "GLIB");
#endif
}

/**
 * Converts version string to integer to allow comparing.
 */
int VersionToInt(const char *Buffer)
{
	size_t retval = 0, pos = 0;

	retval = atoi(Buffer) * 10000;
	while (Buffer[pos] != '.') {
		pos++;
		if (Buffer[pos] == '\0') {
			return retval;
		}
	}
	pos++;
	retval += atoi(Buffer + pos) * 100;
	while (Buffer[pos] != '.') {
		pos++;
		if (Buffer[pos] == '\0') {
			return retval;
		}
	}
	pos++;
	return retval + atoi(Buffer + pos);
}

/**
 * Check whether there is not newer version available online.
 */
static void CheckVersion(int argc, char *argv[])
{
	GSM_File RSS;
	gboolean checkstable = TRUE;
	gboolean checktest = TRUE;
	const char *pos;
	char new_version[20];
	size_t i;
	GSM_Error error;

	if (argc >= 3) {
		if (strcasecmp(argv[2], "STABLE") == 0) {
			checktest = FALSE;
		}
	}

	/* Read file */
	RSS.Buffer = NULL;
	RSS.Used = 0;
	error = GSM_ReadHTTPFile(RSS_URL, &RSS);
	Print_Error(error);

	if (checkstable) {
		pos = strstr(RSS.Buffer, RSS_STABLE_STRING);
		if (pos != NULL) {
			pos += strlen(RSS_STABLE_STRING);

			for (i = 0; i < sizeof(new_version) - 1 && (isdigit((int)pos[i]) || pos[i] == '.'); i++) {
				new_version[i] = pos[i];
			}

			new_version[i] = 0;

			if (VersionToInt(new_version) > VersionToInt(GAMMU_VERSION)) {
				printf_info(_("There is newer stable Gammu version available! (%s instead of %s)\n"),
				       new_version, GAMMU_VERSION);
			}
		}
	}

	if (checktest) {
		pos = strstr(RSS.Buffer, RSS_TESTING_STRING);
		if (pos != NULL) {
			pos += strlen(RSS_TESTING_STRING);

			for (i = 0; i < sizeof(new_version) - 1 && (isdigit((int)pos[i]) || pos[i] == '.'); i++) {
				new_version[i] = pos[i];
			}

			new_version[i] = 0;

			if (VersionToInt(new_version) > VersionToInt(GAMMU_VERSION)) {
				printf_info(_("There is newer testing Gammu version available! (%s instead of %s)\n"),
				       new_version, GAMMU_VERSION);
			}
		}
	}

	free(RSS.Buffer);
}

static void Version(int argc UNUSED, char *argv[]UNUSED)
{
	PrintVersion();

	printf("%s\n", _("Copyright (C) 2003 - 2015 Michal Cihar <michal@cihar.com> and other authors."));

	printf("\n");

	printf("%s\n", _("License GPLv2: GNU GPL version 2 <https://spdx.org/licenses/GPL-2.0>."));

	printf("%s\n", _("This is free software: you are free to change and redistribute it."));

	printf("%s\n", _("There is NO WARRANTY, to the extent permitted by law."));

	printf("\n");

	printf("%s\n", _("Check <http://wammu.eu/gammu/> for updates."));

	printf("\n");
}

int ProcessParameters(int start, int argc, char *argv[]);

/**
 * Reads commands from file (argv[2]) or stdin and executes them
 * sequentially as if they were given on the command line. Also allows
 * recursive calling (nested batches in the batch files).
 */
static void RunBatch(int argc, char *argv[])
{
	FILE *bf;

	/**
	 * @todo Allocate memory dynamically.
	 */
	char ln[2000], token;
	size_t i, len;
	ssize_t pos;
	int j, c = 0, argsc, n;
	char *argsv[20];
	gboolean origbatch;
	char *name;
	char std_name[] = N_("standard input");

	if (argc == 2 || strcmp(argv[2], "-") == 0) {
		bf = stdin;
		name = gettext(std_name);
	} else {
		bf = fopen(argv[2], "r");
		name = argv[2];
	}

	if (bf == NULL) {
		printf(_("Batch file could not be opened: %s\n"), argv[2]);
		return;		/* not exit(), so that any parent batch can continue */
	}

	argsv[0] = argv[0];
	origbatch = batch;
	batch = TRUE;
	while (!feof(bf)) {
		ln[0] = 0;
		if (fgets(ln, sizeof(ln) - 2, bf) == NULL) {
			if (!feof(bf)) {
				printf_err("%s", _("Error reading batch! Terminating.\n"));
			} else {
				printf_info("%s", _("Batch processed, terminating.\n"));
			}
			break;
		}

		/* Skip all traling whitespace */
		for (pos = strlen(ln) - 1; pos >= 0; pos--) {
			if (isspace((int)ln[pos])) {
				ln[pos] = 0;
			} else {
				break;
			}
		}

		if (strlen(ln) < 1 || ln[0] == '#') {
			/* line is empty and is not a comment */
			continue;
		}

		/* split words into strings in the array argsv */
		// added support for params that contain space(s) by adam-pl
		i = 0;
		j = 0;
		argsc = 0;
		len = strlen(ln);
		token = 0x00;
		while (i <= len) {
		  switch (token) {
		    case 0x27:
		    case 0x22: // ' or " char
		      if (ln[i] == token) {
		        //printf( "(B) Found start token at i=%zu\n", i);
		        token = 0x00;
		        n = i - j;
		        argsc++;
		        argsv[argsc] = malloc(n + 1);
		        strncpy(argsv[argsc], ln + j, n);
		        argsv[argsc][n] = 0;
		        i++;
		        j = i + 1;
		        //printf( "n=%i, value=%s\n", n, argsv[argsc]);
		      }
		      break;
		    default:
		      if (ln[i] == ' ' || ln[i] == 0) {
		        argsc++;
		        n = i - j;
		        argsv[argsc] = malloc(n + 1);
		        strncpy(argsv[argsc], ln + j, n);
		        argsv[argsc][n] = 0;
		        j = i + 1;
		      }
		      else if (ln[i] == 0x27 || ln[i] == 0x22) {
			      //printf( "(A) Found end token at i=%zu\n", i);
			      token = ln[i];
			      j = i + 1;
		      }
		      break;
		  }
		  i++;
		}

		/* show params list */
		/*
		if (argsc > 0) {
		  printf( "\nSummary of params:\n");
		  for (j = 1; j <= argsc; j++) {
		    printf( "Parameter %i=[%s]\n", j, argsv[j]);
		  }
		}
		*/
		if (argsc > 0) {
			/* we have some usable command and parameters, send them into standard processing */
			printf ("----------------------------------------------------------------------------\n");
			printf(_("Executing batch \"%s\" - command %i: %s\n"), name, ++c, ln);
			/**
			 * @todo Handle return value from ProcessParameters.
			 */
			ProcessParameters(0, argsc + 1, argsv);
			for (j = 1; j <= argsc; j++) {
				free(argsv[j]);
			}
		}
	}
	if (!origbatch) {
		/* only close the batch if we are not in a nested batch */
		batch = FALSE;
		if (batchConn) {
			GSM_Terminate();
		}
	}
	fclose(bf);
}

NORETURN
void SendSMSDSMSObsolete(int argc, char *argv[])
{
	printf_err("%s\n", _("SMS daemon is now in separate binary, please use gammu-smsd-inject instead of gammu sendsmsdsms!"));
	Terminate(99);
}

NORETURN
void SMSDaemonObsolete(int argc, char *argv[])
{
	printf_err("%s\n", _("SMS daemon is now in separate binary, please use gammu-smsd instead of gammu smsd!"));
	Terminate(99);
}

void Help(int argc, char *argv[]);

static GSM_Parameters Parameters[] = {
/* *INDENT-OFF* */
	{"help",			0, 1, Help,			{H_Gammu,0},			"[topic]"},
	{"identify",			0, 0, Identify,			{H_Info,0},			""},
	{"version",			0, 0, Version,			{H_Gammu,0},			""},
	{"features",			0, 0, Features,			{H_Gammu,0},			""},
	{"checkversion",		0, 1, CheckVersion,		{H_Gammu,0},			"[STABLE]"},
	{"getdisplaystatus",		0, 0, GetDisplayStatus,		{H_Info,0},			""},
	{"getlocation",			0, 0, GetLocation,		{H_Info,0},			""},
	{"monitor",			0, 1, Monitor,			{H_Info,H_Network,H_Call,0},	"[times]"},
	{"battery",			0, 0, Battery,			{H_Info,0},			""},
	{"setautonetworklogin",	0, 0, SetAutoNetworkLogin,	{H_Network,0},			""},
	{"listnetworks",		0, 1, ListNetworks,		{H_Network,0},			"[country]"},
	{"getgprspoint",		1, 2, GetGPRSPoint,		{H_Network,0},			"start [stop]"},
	{"screenshot",			1, 1, Screenshot,		{H_Other,0},		"filenamebase"},
	{"getfilesystemstatus",	0, 0, GetFileSystemStatus,	{H_Filesystem,0},		""},
	{"getfilesystem",		0, 1, GetFileSystem,		{H_Filesystem,0},		"[-flatall|-flat]"},
	{"getfilefolder",		1,40, GetFileFolder,		{H_Filesystem,0},		"fileID, fileID, ..."},
	{"addfolder",			2, 2, AddFolder,		{H_Filesystem,0},		"parentfolderID name"},
	{"deletefolder",		1, 1, DeleteFolder,		{H_Filesystem,0},		"name"},
	{"getfolderlisting",		1, 1, GetFolderListing,		{H_Filesystem,0},		"folderID"},
	{"getrootfolders",		0, 0, GetRootFolders,		{H_Filesystem,0},		""},
	{"setfileattrib",		1, 5, SetFileAttrib,		{H_Filesystem,0},		"folderID [-system] [-readonly] [-hidden] [-protected]"},
	{"getfiles",			1,40, GetFiles,			{H_Filesystem,0},		"fileID, fileID, ..."},
	{"addfile",			2, 9, AddSendFile,		{H_Filesystem,0},		"folderID name [-type JAR|BMP|PNG|GIF|JPG|MIDI|WBMP|AMR|3GP|NRT][-readonly][-protected][-system][-hidden][-newtime]"},
	{"sendfile",			1, 1, AddSendFile,		{H_Filesystem,0},		"name"},
	{"deletefiles",		1,20, DeleteFiles,		{H_Filesystem,0},		"fileID"},
#if defined(GSM_ENABLE_NOKIA_DCT3) || defined(GSM_ENABLE_NOKIA_DCT4)
	{"nokiaaddplaylists",		0, 0, NokiaAddPlayLists,	{H_Filesystem,H_Nokia,0},	""},
	{"nokiaaddfile",		2, 5, NokiaAddFile,		{H_Filesystem,H_Nokia,0},	"APPLICATION|GAME file [-readonly][-overwrite][-overwriteall]"},
	{"nokiaaddfile",		2, 5, NokiaAddFile,		{H_Filesystem,H_Nokia,0},	"GALLERY|GALLERY2|CAMERA|TONES|TONES2|RECORDS|VIDEO|PLAYLIST|MEMORYCARD file [-name name][-protected][-readonly][-system][-hidden][-newtime]"},
	{"playsavedringtone",		1, 1, DCT4PlaySavedRingtone, 	{H_Ringtone,0},			"number"},
#endif
	{"playringtone",		1, 1, PlayRingtone, 		{H_Ringtone,0},			"file"},
	{"getdatetime",		0, 0, GetDateTime,		{H_DateTime,0},			""},
	{"setdatetime",		0, 2, SetDateTime,		{H_DateTime,0},			"[HH:MM[:SS]] [YYYY/MM/DD]"},
	{"getalarm",			0, 1, GetAlarm,			{H_DateTime,0},			"[start]"},
	{"setalarm",			2, 2, SetAlarm,			{H_DateTime,0},			"hour minute"},
	{"resetphonesettings",	1, 1, ResetPhoneSettings,	{H_Settings,0},			"PHONE|DEV|UIF|ALL|FACTORY"},
	{"getmemory",			2, 4, GetMemory,		{H_Memory,0},			ALL_MEMORY_TYPES " start [stop [-nonempty]]"},
	{"deletememory",		2, 3, DeleteMemory,		{H_Memory,0},			ALL_MEMORY_TYPES " start [stop]"},
	{"deleteallmemory",		1, 1, DeleteAllMemory,		{H_Memory,0},			ALL_MEMORY_TYPES},
	{"getallmemory",		1, 1, GetAllMemory,		{H_Memory,0},			ALL_MEMORY_TYPES},
	{"searchmemory",		1, 1, SearchMemory,		{H_Memory,0},			"text"},
	{"listmemorycategory",	1, 1, ListMemoryCategory,	{H_Memory, H_Category,0},	"text|number"},
	{"getfmstation",		1, 2, GetFMStation,		{H_FM,0},			"start [stop]"},
	{"getsmsc",			0, 2, GetSMSC,			{H_SMS,0},			"[start [stop]]"},
	{"setsmsc",			2, 2, SetSMSC,			{H_SMS,0},			"location number"},
	{"getsms",			2, 3, GetSMS,			{H_SMS,0},			"folder start [stop]"},
	{"deletesms",			2, 3, DeleteSMS,		{H_SMS,0},			"folder start [stop]"},
	{"deleteallsms",		1, 1, DeleteAllSMS,		{H_SMS,0},			"folder"},
	{"getsmsfolders",		0, 0, GetSMSFolders,		{H_SMS,0},			""},
	{"getallsms",			0, 1, GetAllSMS,		{H_SMS,0},			"-pbk"},
	{"geteachsms",		0, 1, GetEachSMS,		{H_SMS,0},			"-pbk"},

#define SMS_TEXT_OPTIONS	"[-inputunicode][-16bit][-flash][-len len][-autolen len][-unicode][-enablevoice][-disablevoice][-enablefax][-disablefax][-enableemail][-disableemail][-voidsms][-replacemessages ID][-replacefile file][-text msgtext][-textutf8 msgtext]"
#define SMS_PICTURE_OPTIONS	"[-text text][-unicode][-alcatelbmmi]"
#define SMS_PROFILE_OPTIONS	"[-name name][-bitmap bitmap][-ringtone ringtone]"
#define SMS_EMS_OPTIONS		"[-unicode][-16bit][-format lcrasbiut][-text text][-unicodefiletext file][-defsound ID][-defanimation ID][-tone10 file][-tone10long file][-tone12 file][-tone12long file][-toneSE file][-toneSElong file][-fixedbitmap file][-variablebitmap file][-variablebitmaplong file][-animation frames file1 ...][-protected number]"
#define SMS_SMSTEMPLATE_OPTIONS	"[-unicode][-text text][-unicodefiletext file][-defsound ID][-defanimation ID][-tone10 file][-tone10long file][-tone12 file][-tone12long file][-toneSE file][-toneSElong file][-variablebitmap file][-variablebitmaplong file][-animation frames file1 ...]"
#define SMS_ANIMATION_OPTIONS	""
#define SMS_OPERATOR_OPTIONS	"[-netcode netcode][-biglogo]"
#define SMS_RINGTONE_OPTIONS	"[-long][-scale]"
#define SMS_SAVE_OPTIONS	"[-folder id][-unread][-read][-unsent][-sent][-sender number][-smsname name]"
#define SMS_SEND_OPTIONS	"[-report][-validity HOUR|6HOURS|DAY|3DAYS|WEEK|MAX][-save [-folder number]]"
#define SMS_COMMON_OPTIONS	"[-smscset number][-smscnumber number][-reply][-maxsms num]"

	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,0},			"TEXT " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_TEXT_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Ringtone,0},		"RINGTONE file " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_RINGTONE_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"OPERATOR file " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_OPERATOR_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"CALLER file " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"PICTURE file " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_PICTURE_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"ANIMATION frames file1 file2... " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_ANIMATION_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_MMS,0},		"MMSINDICATOR URL Title Sender " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"WAPINDICATOR URL Title " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
#ifdef GSM_ENABLE_BACKUP
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"BOOKMARK file location " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"WAPSETTINGS file location DATA|GPRS " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_MMS,0},		"MMSSETTINGS file location  " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Calendar,0},		"CALENDAR file location " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_ToDo,0},		"TODO file location " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Memory,0},		"VCARD10|VCARD21 file SM|ME location [-nokia]" SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS},
#endif
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,H_Settings,0},		"PROFILE " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_PROFILE_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,0},			"EMS " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_EMS_OPTIONS},
	{"savesms",			1,30, SendSaveDisplaySMS,	{H_SMS,0},			"SMSTEMPLATE " SMS_SAVE_OPTIONS SMS_COMMON_OPTIONS SMS_SMSTEMPLATE_OPTIONS},

	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,0},			"TEXT destination " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_TEXT_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Ringtone,0},		"RINGTONE destination file " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_RINGTONE_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"OPERATOR destination file " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_OPERATOR_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"CALLER destination file " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"PICTURE destination file " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_PICTURE_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Logo,0},		"ANIMATION destination frames file1 file2... " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_ANIMATION_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_MMS,0},		"MMSINDICATOR destination URL Title Sender " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"WAPINDICATOR destination URL Title " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
#ifdef GSM_ENABLE_BACKUP
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"BOOKMARK destination file location " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_WAP,0},		"WAPSETTINGS destination file location DATA|GPRS " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_MMS,0},		"MMSSETTINGS destination file location " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Calendar,0},		"CALENDAR destination file location " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_ToDo,0},		"TODO destination file location " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Memory,0},		"VCARD10|VCARD21 destination file SM|ME location [-nokia]" SMS_SEND_OPTIONS SMS_COMMON_OPTIONS},
#endif
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,H_Settings,0},		"PROFILE destination " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS ""SMS_PROFILE_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,0},			"EMS destination " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_EMS_OPTIONS},
	{"sendsms",			2,30, SendSaveDisplaySMS,	{H_SMS,0},			"SMSTEMPLATE destination " SMS_SEND_OPTIONS SMS_COMMON_OPTIONS SMS_SMSTEMPLATE_OPTIONS},

	{"displaysms",		2,30, SendSaveDisplaySMS,	{H_SMS,0},			"... (options like in sendsms)"},

	{"addsmsfolder",		1, 1, AddSMSFolder,		{H_SMS,0},			"name"},
	{"smsd",			2, 2, SMSDaemonObsolete,	{H_Obsolete,0},		""},
	{"sendsmsdsms",		2,30, SendSMSDSMSObsolete,	{H_Obsolete,0},		""},
	{"getmmsfolders",		0, 0, GetMMSFolders,		{H_MMS,0},			""},
	{"getallmms",			0, 1, GetEachMMS,		{H_MMS,0},			"[-save]"},
	{"geteachmms",		0, 1, GetEachMMS,		{H_MMS,0},			"[-save]"},
	{"getringtone",		1, 2, GetRingtone,		{H_Ringtone,0},			"location [file]"},
	{"getphoneringtone",		1, 2, GetRingtone,		{H_Ringtone,0},			"location [file]"},
	{"getringtoneslist",		0, 0, GetRingtonesList,		{H_Ringtone,0},			""},
	{"setringtone",		1, 6, SetRingtone,		{H_Ringtone,0},			"file [-location location][-scale][-name name]"},
#if defined(GSM_ENABLE_NOKIA_DCT3) || defined(GSM_ENABLE_NOKIA_DCT4)
	{"nokiacomposer",		1, 1, NokiaComposer,		{H_Ringtone,H_Nokia,0},		"file"},
#endif
	{"copyringtone",		2, 3, CopyRingtone,		{H_Ringtone,0},			"source destination [RTTL|BINARY]"},
	{"getussd",			1, 1, GetUSSD,			{H_Call,0},			"code"},
	{"dialvoice",			1, 2, DialVoice,		{H_Call,0},			"number [show|hide]"},
	{"maketerminatedcall",	2, 3, MakeTerminatedCall,	{H_Call,0},			"number length [show|hide]"},
	{"getspeeddial",		1, 2, GetSpeedDial,		{H_Call,H_Memory,0},		"start [stop]"},
	{"cancelcall",		0, 1, CancelCall,		{H_Call,0},			"[id]"},
	{"answercall",		0, 1, AnswerCall,		{H_Call,0},			"[id]"},
	{"unholdcall",		1, 1, UnholdCall,		{H_Call,0},			"id"},
	{"holdcall",			1, 1, HoldCall,			{H_Call,0},			"id"},
	{"conferencecall",		1, 1, ConferenceCall,		{H_Call,0},			"id"},
	{"splitcall",			1, 1, SplitCall,		{H_Call,0},			"id"},
	{"switchcall",		0, 1, SwitchCall,		{H_Call,0},			"[id]"},
	{"transfercall",		0, 1, TransferCall,		{H_Call,0},			"[id]"},
	{"divert",			3, 5, CallDivert,		{H_Call,0},			"get|set all|busy|noans|outofreach all|voice|fax|data [number timeout]"},
	{"canceldiverts",		0, 0, CancelAllDiverts,		{H_Call,0},			""},
	{"senddtmf",			1, 1, SendDTMF,			{H_Call,0},			"sequence"},
	{"getcalendarsettings",	0, 0, GetCalendarSettings,	{H_Calendar,H_Settings,0},	""},
	{"getalltodo",		0, 0, GetAllToDo,		{H_ToDo,0},			""},
	{"listtodocategory",		1, 1, ListToDoCategory,		{H_ToDo, H_Category,0},		"text|number"},
	{"gettodo",			1, 2, GetToDo,			{H_ToDo,0},			"start [stop]"},
	{"deletetodo",		1, 2, DeleteToDo,		{H_ToDo,0},			"start [stop]"},
	{"getallnotes",		0, 0, GetAllNotes,		{H_Note,0},			""},
	{"deletecalendar",		1, 2, DeleteCalendar,		{H_Calendar,0},			"start [stop]"},
	{"getallcalendar",		0, 0, GetAllCalendar,		{H_Calendar,0},			""},
	{"getcalendar",		1, 2, GetCalendar,		{H_Calendar,0},			"start [stop]"},
	{"addcategory",       	2, 2, AddCategory,       	{H_Category,H_ToDo,H_Memory,0},	"TODO|PHONEBOOK text"},
	{"getcategory",       	2, 3, GetCategory,       	{H_Category,H_ToDo,H_Memory,0},	"TODO|PHONEBOOK start [stop]"},
	{"getallcategory",	  	1, 1, GetAllCategories,  	{H_Category,H_ToDo,H_Memory,0},	"TODO|PHONEBOOK"},
	{"reset",			1, 1, Reset,			{H_Other,0},			"SOFT|HARD"},
	{"getprofile",		1, 2, GetProfile,		{H_Settings,0},			"start [stop]"},
	{"getsecuritystatus",		0, 0, GetSecurityStatus,	{H_Info,0},			""},
	{"entersecuritycode",		2, 3, EnterSecurityCode,	{H_Other,0},			"PIN|PUK|PIN2|PUK2|PHONE|NETWORK code|- [newpin|-]"},
	{"deletewapbookmark", 	1, 2, DeleteWAPBookmark, 	{H_WAP,0},			"start [stop]"},
	{"getwapbookmark",		1, 2, GetWAPBookmark,		{H_WAP,0},			"start [stop]"},
	{"getwapsettings",		1, 2, GetWAPMMSSettings,	{H_WAP,0},			"start [stop]"},
	{"getmmssettings",		1, 2, GetWAPMMSSettings,	{H_MMS,0},			"start [stop]"},
	{"getsyncmlsettings",		1, 2, GetSyncMLSettings,	{H_WAP,0},			"start [stop]"},
	{"getchatsettings",		1, 2, GetChatSettings,		{H_WAP,0},			"start [stop]"},
	{"readmmsfile",		1, 2, ReadMMSFile,		{H_MMS,0},			"file [-save]"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"STARTUP [file]"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"CALLER location [file]"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"OPERATOR [file]"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"PICTURE location [file]"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"TEXT"},
	{"getbitmap",			1, 3, GetBitmap,		{H_Logo,0},			"DEALER"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"STARTUP file|1|2|3"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"COLOURSTARTUP [fileID]"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"WALLPAPER fileID"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"CALLER location [file]"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"OPERATOR [file [netcode]]"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"COLOUROPERATOR [fileID [netcode]]"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"PICTURE file location [text]"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"TEXT text"},
	{"setbitmap",			1, 4, SetBitmap,		{H_Logo,0},			"DEALER text"},
	{"copybitmap",		1, 3, CopyBitmap,		{H_Logo,0},			"inputfile [outputfile [OPERATOR|PICTURE|STARTUP|CALLER]]"},
	{"presskeysequence",		1, 1, PressKeySequence,		{H_Other,0},			"mMnNpPuUdD+-123456789*0#gGrR<>[]hHcCjJfFoOmMdD@"},
	{"setpower",			1, 1, SetPower,		{H_Other,0},		"ON|OFF"},
#if defined(WIN32) || defined(HAVE_PTHREAD)
	{"searchphone",		0, 1, SearchPhone,		{H_Other,0},			"[-debug]"},
#endif
	{"install",			0, 1, Install,			{H_Other,0},			"[-minimal]"},
#ifdef GSM_ENABLE_BACKUP
	{"savefile",			4, 5, SaveFile,			{H_Backup,H_Calendar,0},	"CALENDAR target.vcs file location"},
	{"savefile",			4, 5, SaveFile,			{H_Backup,H_ToDo,0},		"TODO target.vcs file location"},
	{"savefile",			4, 5, SaveFile,			{H_Backup,H_Memory,0},		"VCARD10|VCARD21 target.vcf file SM|ME location"},
	{"savefile",			4, 5, SaveFile,			{H_Backup,H_WAP,0},		"BOOKMARK target.url file location"},
	{"convertbackup",		2, 2, ConvertBackup,		{H_Backup,0},			"source.file output.file"},
	{"backup",			1, 2, DoBackup,			{H_Backup,H_Memory,H_Calendar,H_ToDo,H_Category,H_Ringtone,H_WAP,H_FM,0},			"file [-yes]"},
	{"backupsms",			1, 2, BackupSMS,		{H_Backup,H_SMS,0},		"file [-yes|-all]"},
	{"restore",			1, 2, Restore,			{H_Backup,H_Memory,H_Calendar,H_ToDo,H_Category,H_Ringtone,H_WAP,H_FM,0},			"file [-yes]"},
	{"addnew",			1, 4, AddNew,			{H_Backup,H_Memory,H_Calendar,H_ToDo,H_Category,H_Ringtone,H_WAP,H_FM,0},			"file [-yes] [-memory ME|SM|..]"},
	{"restoresms",		1, 2, RestoreSMS,		{H_Backup,H_SMS,0},		"file [-yes]"},
	{"addsms",			2, 3, AddSMS,			{H_Backup,H_SMS,0},		"folder file [-yes]"},
#endif
	{"clearall",			0, 0, ClearAll,			{H_Memory,H_Calendar,H_ToDo,H_Category,H_Ringtone,H_WAP,H_FM,0},	""},
	{"networkinfo",		0, 0, NetworkInfo,		{H_Network,0},			""},
#ifdef GSM_ENABLE_AT
	{"siemenssatnetmon",		0, 0, ATSIEMENSSATNetmon,	{H_Siemens,H_Network,0},	""},
	{"siemensnetmonact",		1, 1, ATSIEMENSActivateNetmon,	{H_Siemens,H_Network,0},	"netmon_type"},
	{"siemensnetmonitor",		1, 1, ATSIEMENSNetmonitor,	{H_Siemens,H_Network,0},	"test"},
#endif
#ifdef GSM_ENABLE_NOKIA6110
	{"nokiagetoperatorname", 	0, 0, DCT3GetOperatorName,	{H_Nokia,H_Network,0},		""},
	{"nokiasetoperatorname", 	0, 2, DCT3SetOperatorName,	{H_Nokia,H_Network,0},		"[networkcode name]"},
	{"nokiadisplayoutput", 	0, 0, DCT3DisplayOutput,	{H_Nokia,0},			""},
#endif
#ifdef GSM_ENABLE_NOKIA_DCT3
	{"nokianetmonitor",		1, 1, DCT3netmonitor,		{H_Nokia,H_Network,0},		"test"},
	{"nokianetmonitor36",		0, 0, DCT3ResetTest36,		{H_Nokia,0},			""},
	{"nokiadebug",		1, 2, DCT3SetDebug,		{H_Nokia,H_Network,0},		"filename [[v11-22][,v33-44]...]"},
#endif
#ifdef GSM_ENABLE_NOKIA_DCT4
	{"nokiagetpbkfeatures",	1, 1, DCT4GetPBKFeatures,	{H_Nokia,H_Memory,0},		"memorytype"},
	{"nokiasetvibralevel",	1, 1, DCT4SetVibraLevel,	{H_Nokia,H_Other,0},		"level"},
	{"nokiagetvoicerecord",	1, 1, DCT4GetVoiceRecord,	{H_Nokia,H_Other,0},		"location"},
#ifdef GSM_ENABLE_NOKIA6510
	{"nokiasetlights",		2, 2, DCT4SetLight,		{H_Nokia,H_Tests,0},		"keypad|display|torch on|off"},
	{"nokiatuneradio",		0, 0, DCT4TuneRadio,		{H_Nokia,H_FM,0},		""},
#endif
	{"nokiamakecamerashoot",	0, 0, DCT4MakeCameraShoot,	{H_Nokia,H_Other,0},		""},
#endif
#if defined(GSM_ENABLE_NOKIA_DCT3) || defined(GSM_ENABLE_NOKIA_DCT4)
	{"nokiavibratest",		0, 0, NokiaVibraTest,		{H_Nokia,H_Tests,0},		""},
	{"nokiagett9",		0, 0, NokiaGetT9,		{H_Nokia,H_SMS,0},		""},
	{"nokiadisplaytest",		1, 1, NokiaDisplayTest,		{H_Nokia,H_Tests,0},		"number"},
	{"nokiagetadc",		0, 0, NokiaGetADC,		{H_Nokia,H_Tests,0},		""},
	{"nokiasecuritycode",		0, 0, NokiaSecurityCode,	{H_Nokia,H_Info,0},		""},
	{"nokiaselftests",		0, 0, NokiaSelfTests,		{H_Nokia,H_Tests,0},		""},
	{"nokiasetphonemenus",	0, 0, NokiaSetPhoneMenus,	{H_Nokia,H_Other,0},		""},
#endif
#ifdef DEBUG
	{"decodesniff",		2, 3, DecodeSniff,		{H_Decode,0},			"MBUS2|IRDA file [phonemodel]"},
	{"decodebinarydump",		1, 2, DecodeBinaryDump,		{H_Decode,0},			"file [phonemodel]"},
#endif
	{"batch",			0, 1, RunBatch,			{H_Other,0},			"[file]"},
	{"",				0, 0, NULL,			{0}, ""}
};

static HelpCategoryDescriptions HelpDescriptions[] = {
	{H_Call,	"call",		N_("Calls")},
	{H_SMS,		"sms",		N_("SMS and EMS")},
	{H_Memory,	"memory",	N_("Memory (phonebooks and calls)")},
	{H_Filesystem,	"filesystem",	N_("Filesystem")},
	{H_Logo,	"logo",		N_("Logo and pictures")},
	{H_Ringtone,	"ringtone",	N_("Ringtones")},
	{H_Calendar,	"calendar",	N_("Calendar notes")},
	{H_ToDo,	"todo",		N_("To do lists")},
	{H_Note,	"note",		N_("Notes")},
	{H_DateTime,	"datetime",	N_("Date, time and alarms")},
	{H_Category,	"category",	N_("Categories")},
#ifdef GSM_ENABLE_BACKUP
	{H_Backup,	"backup",	N_("Backing up and restoring")},
#endif
#if defined(GSM_ENABLE_NOKIA_DCT3) || defined(GSM_ENABLE_NOKIA_DCT4)
	{H_Nokia,	"nokia",	N_("Nokia specific")},
#endif
#ifdef GSM_ENABLE_AT
	{H_Siemens,	"siemens",	N_("Siemens specific")},
#endif
	{H_Network,	"network",	N_("Network")},
	{H_WAP,		"wap",		N_("WAP settings and bookmarks")},
	{H_MMS,		"mms",		N_("MMS and MMS settings")},
	{H_Tests,	"tests",	N_("Phone tests")},
	{H_FM,		"fm",		N_("FM radio")},
	{H_Info,	"info",		N_("Phone information")},
	{H_Settings,	"settings",	N_("Phone settings")},
#ifdef DEBUG
	{H_Decode,	"decode",	N_("Dumps decoding")},
#endif
	{H_Other,	"other",	N_("Functions that don't fit elsewhere")},
	{H_Gammu,	"gammu",	N_("Gammu information")},
	{0,		NULL,		NULL}
/* *INDENT-ON* */
};

void HelpHeader(void)
{
	PrintVersion();
}

void HelpGeneral(void)
{
	int i = 0;

	HelpHeader();

	printf("%s\n\n", _("Usage: gammu [parameters] <command> [options]"));
	printf("%s\n", _("Parameters before command configure gammu behaviour:"));
	printf("%s\n", _("-c / --config <filename> ... name of configuration file"));
	printf("%s\n", _("-s / --section <confign> ... section of config file to use, eg. 42"));
	printf("%s\n", _("-d / --debug <level> ... debug level (nothing|text|textall|textalldate|binary|errors)"));
	printf("%s\n\n", _("-f / --debug-file <filename> ... file for logging debug messages"));

	printf("%s\n\n", _("Commands can be specified with or without leading --."));

	/* We might want to put here some most used commands */
	printf("%s\n\n", _("For more details, call help on specific topic (gammu --help topic). Topics are:"));

	while (HelpDescriptions[i].category != 0) {
		printf("%11s - %s\n", HelpDescriptions[i].option,
		       gettext(HelpDescriptions[i].description));
		i++;
	}
	printf("\n");
}

void HelpSplit(int cols, int len, const char *buff)
{
	int l, len2, pos, split;
	gboolean in_opt, first = TRUE;
	const char *remain;
	char spaces[50], buffer[500];

	if (cols == 0) {
		printf(" %s\n", buff);
	} else {
		printf(" ");
		spaces[0] = 0;
		len2 = strlen(buff);
		if (len + len2 < cols) {
			printf("%s\n", buff);
		} else {
			for (l = 0; l < len; l++)
				strcat(spaces, " ");

			remain = buff;

			while (strlen(remain) > 0) {
				split = 0;
				pos = 0;
				in_opt = FALSE;
				if (!first)
					printf("%s", spaces);
				while (pos < cols - len && remain[pos] != 0) {
					if (in_opt && remain[pos] == ']') {
						in_opt = FALSE;
						split = pos;
					} else if (remain[pos] == '[') {
						in_opt = TRUE;
					} else if (!in_opt
						   && remain[pos] == ' ') {
						split = pos - 1;
					}
					pos++;
				}
				/* Can not be split */
				if (split == 0) {
					printf("%s\n", remain);
					remain += strlen(remain);
				} else {
					first = FALSE;
					split++;
					strncpy(buffer, remain, split);
					buffer[split] = 0;
					printf("%s\n", buffer);
					remain += split;
					if (remain[0] == ' ')
						remain++;
				}
			}
		}
	}
}

void Help(int argc, char *argv[])
{
	int i = 0, j = 0, k, cols;
	gboolean disp;

#ifdef TIOCGWINSZ
	struct winsize w;
#endif
#if defined(WIN32) || defined(DJGPP)
#else
	char *columns;
#endif

	/* Just --help */
	if (argc == 1) {
		HelpGeneral();
		return;
	}

	if (!strcmp(argv[1], "all")) {
		HelpHeader();
	} else {
		while (HelpDescriptions[i].category != 0) {
			if (strcasecmp(argv[1], HelpDescriptions[i].option) ==
			    0)
				break;
			i++;
		}
		if (HelpDescriptions[i].category == 0) {
			HelpGeneral();
			printf("%s\n", _("Unknown help topic specified!"));
			return;
		}
		HelpHeader();
		printf(_("Gammu commands, topic: %s\n\n"),
		       HelpDescriptions[i].description);
	}

#if defined(WIN32) || defined(DJGPP)
	cols = 80;
#else
	cols = 0;
	/* If stdout is a tty, we will wrap to columns it has */
	if (isatty(1)) {
#ifdef TIOCGWINSZ
		if (ioctl(2, TIOCGWINSZ, &w) == 0) {
			if (w.ws_col > 0)
				cols = w.ws_col;
		}
#endif
		if (cols == 0) {
			columns = getenv("COLUMNS");
			if (columns != NULL) {
				cols = atoi(columns);
				if (cols <= 0)
					cols = 0;
			}
		}

		if (cols == 0) {
			/* Fallback */
			cols = 80;
		}
	}
#endif

	while (Parameters[j].Function != NULL) {
		k = 0;
		disp = FALSE;
		if (!strcmp(argv[1], "all")) {
			if (j == 0)
				disp = TRUE;
			if (j != 0) {
				if (strcmp
				    (Parameters[j].help,
				     Parameters[j - 1].help)) {
					disp = TRUE;
				} else {
					if (strcmp
					    (Parameters[j].parameter,
					     Parameters[j - 1].parameter)) {
						disp = TRUE;
					}
				}
			}
		} else {
			while (Parameters[j].help_cat[k] != 0) {
				if (Parameters[j].help_cat[k] ==
				    HelpDescriptions[i].category) {
					disp = TRUE;
					break;
				}
				k++;
			}
		}
		if (disp) {
			printf("%s", Parameters[j].parameter);
			if (Parameters[j].help[0] == 0) {
				printf("\n");
			} else {
				HelpSplit(cols - 1,
					  strlen(Parameters[j].parameter) + 1,
					  gettext(Parameters[j].help));
			}
		}
		j++;
	}
}

int ProcessParameters(int start, int argc, char *argv[])
{
	int z = 0;
	gboolean count_failed = FALSE;

	/* Check parameters */
	while (Parameters[z].Function != NULL) {
		if (strcasecmp(Parameters[z].parameter, argv[1 + start]) == 0 ||
		    (strncmp(argv[1 + start], "--", 2) == 0 &&
		     strcasecmp(Parameters[z].parameter,
				argv[1 + start] + 2) == 0)
		    ) {
			if (argc - 2 - start < Parameters[z].min_arg) {
				if (!count_failed) {
					if (Parameters[z].min_arg ==
					    Parameters[z].max_arg) {
						printf(_("More parameters required (function requires %d)\n"),
						       Parameters[z].min_arg);
					} else {
						printf(_("More parameters required (function requires %d to %d)\n"),
						       Parameters[z].min_arg,
						       Parameters[z].max_arg);
					}
					if (Parameters[z].help[0] != 0) {
						printf("%s:\n",
						       _("Parameters help"));
					}
				}
				if (Parameters[z].help[0] != 0) {
					printf("%s\n",
					       gettext(Parameters[z].help));
				}
				count_failed = TRUE;
			} else if (argc - 2 - start > Parameters[z].max_arg) {
				if (!count_failed) {
					if (Parameters[z].min_arg ==
					    Parameters[z].max_arg) {
						printf(_("Too many parameters (function accepts %d)\n"),
						       Parameters[z].min_arg);
					} else {
						printf(_("Too many parameters (function accepts %d to %d)\n"),
						       Parameters[z].min_arg,
						       Parameters[z].max_arg);
					}
					if (Parameters[z].help[0] != 0) {
						printf("%s:\n",
						       _("Parameters help"));
					}
				}
				if (Parameters[z].help[0] != 0) {
					printf("%s\n",
					       gettext(Parameters[z].help));
				}
				count_failed = TRUE;
			} else {
				Parameters[z].Function(argc - start,
						       argv + start);
				break;
			}
		}
		z++;
	}

	/* Tell user when we did nothing */
	if (Parameters[z].Function == NULL) {
		if (!count_failed) {
			HelpGeneral();
			printf("%s\n", _("Bad option!"));
			return 2;
		}
		return 2;
	}
	return 0;
}

int main(int argc, char *argv[])
{
	int start = 0;
	int i, ret;
	char *cp, *locales_path, *rss;
	GSM_Config *smcfg;
	GSM_Config *smcfg0;
	GSM_Debug_Info *di;
	GSM_Error error;

	gboolean help = FALSE;
	gboolean debug_level_set = FALSE;
	gboolean debug_file_set = FALSE;
	const char *config_file = NULL;
	int only_config = -1;

	di = GSM_GetGlobalDebug();

	/* We don't want to use getopt here */
	for (i = 1; i < argc; i++) {
		if (strcasecmp(argv[i], "--help") == 0 ||
		    strcasecmp(argv[i], "-h") == 0 ||
		    strcasecmp(argv[i], "help") == 0) {
			help = TRUE;
			start++;
		} else if ((strcasecmp(argv[i], "--config") == 0 ||
		    strcasecmp(argv[i], "-c") == 0) &&
		    i + 1 < argc) {
			i++;
			config_file = argv[i];
			start += 2;
		} else if ((strcasecmp(argv[i], "--section") == 0 ||
		    strcasecmp(argv[i], "-s") == 0) &&
		    i + 1 < argc) {
			i++;
			only_config = GetInt(argv[i]);
			start += 2;
		} else if ((strcasecmp(argv[i], "--debug") == 0 ||
		    strcasecmp(argv[i], "-d") == 0) &&
		    i + 1 < argc) {
			i++;
			GSM_SetDebugFileDescriptor(stderr, FALSE, di);
			GSM_SetDebugLevel(argv[i], di);
			debug_level_set = TRUE;
			start += 2;
		} else if ((strcasecmp(argv[i], "--debug-file") == 0 ||
		    strcasecmp(argv[i], "-f") == 0) &&
		    i + 1 < argc) {
			i++;
			error = GSM_SetDebugFile(argv[i], di);
			Print_Error(error);
			debug_file_set = TRUE;
			start += 2;
		} else if (isdigit((int)argv[i][0])) {
			/* Compatibilitty: config file section */
			only_config = GetInt(argv[i]);
			if (only_config >= 0)
				start++;
			else
				only_config = -1;
		} else if (GSM_SetDebugLevel(argv[i], di)) {
			/* Compatibility: debug level */
			start++;
			debug_level_set = TRUE;
		} else {
			break;
		}
	}

	error = GSM_FindGammuRC(&cfg, config_file);
	if (error != ERR_NONE || cfg == NULL) {
		if (error == ERR_FILENOTSUPPORTED) {
			printf_warn("%s\n",
				    _("Configuration could not be parsed!"));
		} else {
			printf_warn("%s\n", _("No configuration file found!"));
		}
	}
	locales_path = INI_GetValue(cfg, "gammu", "gammuloc", FALSE);
	GSM_InitLocales(locales_path);
#ifdef LIBINTL_LIB_FOUND
	if (locales_path != NULL) {
		bindtextdomain("gammu", locales_path);
	} else {
#if defined(LOCALE_PATH)
		bindtextdomain("gammu", LOCALE_PATH);
#else
		bindtextdomain("gammu", ".");
#endif
	}
	textdomain("gammu");
#endif

	gsm = GSM_AllocStateMachine();
	if (gsm == NULL) {
		printf_err("%s", _("Failed to allocate memory, aborting!\n"));
		exit(1);
	}

#ifdef CURL_FOUND
	curl_global_init(CURL_GLOBAL_ALL);
#endif

#ifdef DEBUG
	if (!debug_level_set) {
		GSM_SetDebugLevel("textall", di);
	}
	if (!debug_file_set) {
		GSM_SetDebugFileDescriptor(stdout, FALSE, di);
	}
#endif

	cp = INI_GetValue(cfg, "gammu", "gammucoding", FALSE);
	if (cp) {
		GSM_SetDebugCoding(cp, di);
	}

	/* Help? */
	if (help) {
		Help(argc - start, argv + start);
		Terminate(0);
	}

	/* Do we have enough parameters? */
	if (argc <= start + 1) {
		HelpGeneral();
		printf("%s\n", _("Too few parameters!"));
		Terminate(2);
	}

	smcfg0 = GSM_GetConfig(gsm, 0);

	for (i = 0; (smcfg = GSM_GetConfig(gsm, i)) != NULL; i++) {
		/* Wanted user specific configuration? */
		if (only_config != -1) {
			smcfg = smcfg0;
			error = GSM_ReadConfig(cfg, smcfg, only_config);
			/* Here we get only in first for loop */
			if (error != ERR_NONE) {
				printf_err(_("Failed to read [gammu%d] section from configuration file (gammurc)!\n"),
					   only_config);
				printf_warn("%s\n", _("No configuration read, using builtin defaults!"));
				GSM_ReadConfig(NULL, smcfg, 0);
			}
		} else {
			error = GSM_ReadConfig(cfg, smcfg, i);
			if (error != ERR_NONE) {
				if (i != 0) {
					/* We just end here, we already have some valid config */
					break;
				}
				if (error == ERR_USING_DEFAULTS) {
					printf_warn("%s\n", _("No configuration read, using builtin defaults!"));
				}
			}
		}
		GSM_SetConfigNum(gsm, GSM_GetConfigNum(gsm) + 1);

		/* We want to use only one file descriptor for global and state machine debug output */
		smcfg->UseGlobalDebugFile = TRUE;

		/* It makes no sense to open several debug logs... */
		if (i == 0) {
			/* Just for first config */
			/* When user gave debug level on command line */
			if (!debug_level_set) {
				/* Try to set debug level from config file */
				GSM_SetDebugLevel(smcfg->DebugLevel, di);
			}
			/* If user gave debug file in gammurc, we will use it */
			if (!debug_file_set) {
				error = GSM_SetDebugFile(smcfg->DebugFile, di);
				Print_Error(error);
			}
		}

		if (i == 0) {
			rss = INI_GetValue(cfg, "gammu", "rsslevel", FALSE);
			if (rss) {
				printf_warn("Configuration option rsslevel is ignored, use '%s' instead\n", "gammu checkversion");
			}
		}

		/* We wanted to read just user specified configuration. */
		if (only_config != -1) {
			break;
		}
	}

	/* Check used version vs. compiled */
	if (!strcasecmp(GetGammuVersion(), GAMMU_VERSION) == 0) {
		printf_err(_("Version of installed libGammu.so (%s) is different to version of Gammu (%s)\n"),
			   GetGammuVersion(), GAMMU_VERSION);
		Terminate(98);
	}

	ret = ProcessParameters(start, argc, argv);

	Terminate(ret);

	return ret;
}

/* How should editor hadle tabs in this file? Add editor commands here.
 * vim: noexpandtab sw=8 ts=8 sts=8:
 */
