/* 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Copyright (c) 2011 - 2012 Michal Cihar <michal@cihar.com>
 */


#define _GNU_SOURCE
#include "../../gsmstate.h"
#include "../../protocol/s60/s60-ids.h"
#include "../../gsmcomon.h"
#include "../../misc/coding/coding.h"
#include "../../gsmphones.h"
#include "../../gsmstate.h"
#include "../../service/gsmmisc.h"
#include "../../misc/locales.h"
#include "../pfunc.h"
#include <string.h>

#include "../../../helper/string.h"

#if defined(GSM_ENABLE_S60)

GSM_Error S60_Install(GSM_StateMachine *s, const char *ExtraPath, gboolean Minimal)
{
	GSM_StateMachine *gsm;
	GSM_Debug_Info *debug_info;
	GSM_Config *cfg;
	GSM_Error error;
	GSM_File PythonFile, PIPSFile, AppletFile;
	gboolean install_python, install_pips;

	PythonFile.Buffer 	= NULL;
	PythonFile.Used 	= 0;
	PIPSFile.Buffer 	= NULL;
	PIPSFile.Used 	= 0;
	AppletFile.Buffer 	= NULL;
	AppletFile.Used 	= 0;

	error = PHONE_FindDataFile(s, &AppletFile, ExtraPath, "gammu-s60-remote.sis");
	if (error != ERR_NONE) {
		smprintf(s, "Failed to find applet, trying another filename!\n");
		error = PHONE_FindDataFile(s, &AppletFile, ExtraPath, "gammu-s60-remote-sign.sis");
		if (error != ERR_NONE) {
			smprintf(s, "Failed to load applet data!\n");
			return ERR_INSTALL_NOT_FOUND;
		}
	}

	if (Minimal) {

		install_python = FALSE;
		install_pips = FALSE;

	} else {

		error = PHONE_FindDataFile(s, &PythonFile, ExtraPath, "Python_2.0.0.sis");
		if (error == ERR_NONE) {
			install_python = TRUE;
		} else {
			smprintf(s, "Could not find Python for S60 to install, skipping!\n");
			install_python = FALSE;
			install_pips = FALSE;
		}

		if (install_python) {
			error = PHONE_FindDataFile(s, &PIPSFile, ExtraPath, "pips.sis");
			if (error == ERR_NONE) {
				install_pips = TRUE;
			} else {
				smprintf(s, "Could not find PIPS to install, skipping!\n");
				install_pips = FALSE;
			}
		}
	}

	gsm = GSM_AllocStateMachine();
	if (gsm == NULL) {
		return ERR_MOREMEMORY;
	}

	/* Copy debug configuration */
	debug_info = GSM_GetDebug(gsm);
	*debug_info = *GSM_GetDebug(s);
	debug_info->closable = FALSE;
	GSM_SetDebugFileDescriptor(GSM_GetDebug(s)->df, FALSE, debug_info);
	GSM_SetDebugLevel(s->CurrentConfig->DebugLevel, debug_info);

	/* Generate configuration */
	cfg = GSM_GetConfig(gsm, 0);
	cfg->Device = strdup(s->CurrentConfig->Device);
	cfg->Connection = strdup("blueobex");
	strcpy(cfg->Model, "obexnone");
	strcpy(cfg->DebugLevel, s->CurrentConfig->DebugLevel);
	cfg->UseGlobalDebugFile = s->CurrentConfig->UseGlobalDebugFile;

	/* We have one configuration */
	GSM_SetConfigNum(gsm, 1);

	error = GSM_InitConnection(gsm, 1);
	if (error != ERR_NONE) {
		return error;
	}

	if (install_pips) {
		error = PHONE_UploadFile(gsm, &PIPSFile);
		free(PIPSFile.Buffer);
		if (error != ERR_NONE) {
			return error;
		}
	}

	if (install_python) {
		error = PHONE_UploadFile(gsm, &PythonFile);
		free(PythonFile.Buffer);
		if (error != ERR_NONE) {
			return error;
		}
	}

	error = PHONE_UploadFile(gsm, &AppletFile);
	free(AppletFile.Buffer);
	if (error != ERR_NONE) {
		return error;
	}

	error = GSM_TerminateConnection(gsm);
	if (error != ERR_NONE) {
		return error;
	}

	/* Free up used memory */
	GSM_FreeStateMachine(gsm);

	return ERR_NONE;
}

GSM_Error S60_Initialise(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	size_t i;

	Priv->SMSLocations = NULL;
	Priv->SMSLocationsSize = 0;
	Priv->SMSLocationsPos = 0;

	Priv->ContactLocations = NULL;
	Priv->ContactLocationsSize = 0;
	Priv->ContactLocationsPos = 0;

	Priv->CalendarLocations = NULL;
	Priv->CalendarLocationsSize = 0;
	Priv->CalendarLocationsPos = 0;

	Priv->ToDoLocations = NULL;
	Priv->ToDoLocationsSize = 0;
	Priv->ToDoLocationsPos = 0;

	s->Phone.Data.NetworkInfo = NULL;
	s->Phone.Data.SignalQuality = NULL;
	s->Phone.Data.BatteryCharge = NULL;
	s->Phone.Data.Memory = NULL;
	s->Phone.Data.MemoryStatus = NULL;
	s->Phone.Data.CalStatus = NULL;
	s->Phone.Data.ToDoStatus = NULL;

	for (i = 0; i < sizeof(Priv->MessageParts) / sizeof(Priv->MessageParts[0]); i++) {
		Priv->MessageParts[i] = NULL;
	}

	error = GSM_WaitFor (s, NULL, 0, 0, S60_TIMEOUT, ID_Initialise);
	if (error != ERR_NONE) {
		return error;
	}

	if (Priv->MajorVersion != 1 || Priv->MinorVersion != 6) {
		smprintf(s, "Unsupported protocol version\n");
		return ERR_NOTSUPPORTED;
	}

	error = GSM_WaitFor(s, NULL, 0, NUM_HELLO_REQUEST, S60_TIMEOUT, ID_EnableEcho);
	if (error != ERR_NONE) {
		return error;
	}

	return error;

//	return ERR_NONE;
}

/**
 * Splits values from S60 reply.
 */
GSM_Error S60_SplitValues(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	unsigned char * pos = msg->Buffer - 1;
	size_t i;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	for (i = 0; i < sizeof(Priv->MessageParts) / sizeof(Priv->MessageParts[0]); i++) {
		Priv->MessageParts[i] = NULL;
	}

	i = 0;

	if (msg->Length == 0) {
		return ERR_NONE;
	}

	while ((pos - msg->Buffer) < (ssize_t)msg->Length) {
		if (i >=  sizeof(Priv->MessageParts) / sizeof(Priv->MessageParts[0])) {
			smprintf(s, "Too many reply parts!\n");
			return ERR_MOREMEMORY;
		}
		Priv->MessageParts[i++] = pos + 1;

		/* Find end of next field */
		pos = strchr(pos + 1, NUM_SEPERATOR);
		if (pos == NULL) {
			break;
		}

		/* Zero terminate string */
		*pos = 0;
	}
	return ERR_NONE;
}

GSM_Error S60_Terminate(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	free(Priv->SMSLocations);
	Priv->SMSLocations = NULL;
	Priv->SMSLocationsSize = 0;
	Priv->SMSLocationsPos = 0;

	free(Priv->ContactLocations);
	Priv->ContactLocations = NULL;
	Priv->ContactLocationsSize = 0;
	Priv->ContactLocationsPos = 0;

	free(Priv->CalendarLocations);
	Priv->CalendarLocations = NULL;
	Priv->CalendarLocationsSize = 0;
	Priv->CalendarLocationsPos = 0;

	free(Priv->ToDoLocations);
	Priv->ToDoLocations = NULL;
	Priv->ToDoLocationsSize = 0;
	Priv->ToDoLocationsPos = 0;

	return GSM_WaitFor(s, NULL, 0, NUM_QUIT, S60_TIMEOUT, ID_Terminate);
}

static GSM_Error S60_Reply_Generic(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	switch (msg->Type) {
		case NUM_SYSINFO_REPLY_START:
		case NUM_CONTACTS_REPLY_HASH_SINGLE_START:
		case NUM_CONTACTS_REPLY_CONTACT_START:
		case NUM_CALENDAR_REPLY_ENTRIES_START:
			return ERR_NEEDANOTHERANSWER;
		case NUM_LOCATION_REPLY_NA:
			return ERR_NOTSUPPORTED;
		case NUM_CONTACTS_REPLY_CONTACT_NOT_FOUND:
		case NUM_CALENDAR_REPLY_ENTRY_NOT_FOUND:
			return ERR_EMPTY;
		default:
			return ERR_NONE;
	}
}

static GSM_Error S60_Reply_SendSMS(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	switch (msg->Type) {
		case NUM_MESSAGE_SEND_REPLY_RETRY:
			if (s->User.SendSMSStatus != NULL) {
				s->User.SendSMSStatus(s, 1, -1, s->User.SendSMSStatusUserData);
			}
			return ERR_BUSY;
		case NUM_MESSAGE_SEND_REPLY_STATUS:
			return ERR_NONE;
		case NUM_MESSAGE_SEND_REPLY_OK:
			if (s->User.SendSMSStatus != NULL) {
				s->User.SendSMSStatus(s, 0, -1, s->User.SendSMSStatusUserData);
			}
			return ERR_NONE;
		case NUM_MESSAGE_SEND_REPLY_FAILURE:
			if (s->User.SendSMSStatus != NULL) {
				s->User.SendSMSStatus(s, 1, -1, s->User.SendSMSStatusUserData);
			}
			return ERR_UNKNOWN;
		default:
			return ERR_UNKNOWN;
	}
}

static GSM_Error S60_Reply_Connect(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	char *pos;

	Priv->MajorVersion = atoi(msg->Buffer);
	pos = strchr(msg->Buffer, '.');
	if (pos == NULL) {
		return ERR_UNKNOWN;
	}
	Priv->MinorVersion = atoi(pos + 1);
	smprintf(s, "Connected to Gammu S60 Remote version %d.%d\n", Priv->MajorVersion, Priv->MinorVersion);

	return ERR_NONE;
}

static GSM_Error S60_GetInfo(GSM_StateMachine *s)
{
	return GSM_WaitFor(s, "1", 1, NUM_SYSINFO_REQUEST, S60_TIMEOUT, ID_GetModel);
}

static GSM_Error S60_GetSignalQuality(GSM_StateMachine *s, GSM_SignalQuality *sig)
{
	GSM_Error error;

	sig->BitErrorRate = -1;
	sig->SignalStrength = -1;
	sig->SignalPercent = -1;

	s->Phone.Data.SignalQuality = sig;
	error = S60_GetInfo(s);
	s->Phone.Data.SignalQuality = NULL;
	return error;
}

static GSM_Error S60_GetNetworkInfo(GSM_StateMachine *s, GSM_NetworkInfo *netinfo)
{
	GSM_Error error;

	s->Phone.Data.NetworkInfo = netinfo;
	error = GSM_WaitFor(s, NULL, 0, NUM_LOCATION_REQUEST, S60_TIMEOUT, ID_GetNetworkInfo);
	s->Phone.Data.NetworkInfo = NULL;
	return error;
}

static GSM_Error S60_Reply_GetNetworkInfo(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	char *mcc, *mnc, *lac, *cellid;

	if (s->Phone.Data.NetworkInfo == NULL) {
		return ERR_NONE;
	}

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	/* Grab values */
	mcc = Priv->MessageParts[0];
	mnc = Priv->MessageParts[1];
	lac = Priv->MessageParts[2];
	cellid =Priv->MessageParts[3];

	/* We need all of them */
	if (mcc == NULL || mnc == NULL || lac == NULL || cellid == NULL) {
		return ERR_UNKNOWN;
	}

	strcpy(s->Phone.Data.NetworkInfo->CID, cellid);
	strcpy(s->Phone.Data.NetworkInfo->NetworkCode, mcc);
	strcat(s->Phone.Data.NetworkInfo->NetworkCode, " ");
	strcat(s->Phone.Data.NetworkInfo->NetworkCode, mnc);
	s->Phone.Data.NetworkInfo->State = GSM_NetworkStatusUnknown;
	strcpy(s->Phone.Data.NetworkInfo->LAC, lac);
	s->Phone.Data.NetworkInfo->NetworkName[0] = 0;
	s->Phone.Data.NetworkInfo->NetworkName[1] = 0;
	s->Phone.Data.NetworkInfo->GPRS = 0;
	s->Phone.Data.NetworkInfo->PacketCID[0] = 0;
	s->Phone.Data.NetworkInfo->PacketState = 0;
	s->Phone.Data.NetworkInfo->PacketLAC[0] = 0;

	return ERR_NONE;
}

static GSM_Error S60_GetBatteryCharge(GSM_StateMachine *s, GSM_BatteryCharge *bat)
{
	GSM_Error error;

	GSM_ClearBatteryCharge(bat);
	s->Phone.Data.BatteryCharge = bat;
	error = S60_GetInfo(s);
	s->Phone.Data.BatteryCharge = NULL;
	return error;
}


static GSM_Error S60_Reply_GetInfo(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	char *pos;
	int signal_value;
	GSM_SignalQuality *Signal = s->Phone.Data.SignalQuality;
	GSM_BatteryCharge *BatteryCharge = s->Phone.Data.BatteryCharge;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}
	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[1] == NULL) {
		return ERR_UNKNOWN;
	}
	smprintf(s, "Received %s=%s\n", Priv->MessageParts[0], Priv->MessageParts[1]);
	if (strcmp(Priv->MessageParts[0], "imei") == 0) {
		strcpy(s->Phone.Data.IMEI, Priv->MessageParts[1]);
	} else if (strcmp(Priv->MessageParts[0], "model") == 0) {
		/* Parse manufacturer */
		pos = strcasestr(Priv->MessageParts[1], "(C)");
		if (pos != NULL) {
			strcpy(s->Phone.Data.Manufacturer, pos + 3);
		}
		/* Try to find model */
		pos = strchr(Priv->MessageParts[1], ' ');
		if (pos != NULL) {
			pos = strchr(pos + 1, ' ');
			if (pos != NULL) {
				strcpy(s->Phone.Data.Model, pos + 1);
				pos = strchr(s->Phone.Data.Model, ' ');
				if (pos != NULL) {
					*pos = 0;
				}
			} else {
				strcpy(s->Phone.Data.Model, Priv->MessageParts[1]);
			}
		} else {
			strcpy(s->Phone.Data.Model, Priv->MessageParts[1]);
		}
		s->Phone.Data.ModelInfo = GetModelData(s, NULL, s->Phone.Data.Model, NULL);

		if (s->Phone.Data.ModelInfo->number[0] == 0)
			s->Phone.Data.ModelInfo = GetModelData(s, NULL, NULL, s->Phone.Data.Model);

		if (s->Phone.Data.ModelInfo->number[0] == 0)
			s->Phone.Data.ModelInfo = GetModelData(s, s->Phone.Data.Model, NULL, NULL);

		if (s->Phone.Data.ModelInfo->number[0] == 0) {
			smprintf(s, "Unknown model, but it should still work\n");
		}
		smprintf(s, "[Model name: `%s']\n", s->Phone.Data.Model);
		smprintf(s, "[Model data: `%s']\n", s->Phone.Data.ModelInfo->number);
		smprintf(s, "[Model data: `%s']\n", s->Phone.Data.ModelInfo->model);
	} else if (strcmp(Priv->MessageParts[0], "s60_version") == 0) {
		strcpy(s->Phone.Data.Version, Priv->MessageParts[1]);
		strcat(s->Phone.Data.Version, ".");
		strcat(s->Phone.Data.Version, Priv->MessageParts[2]);
		GSM_CreateFirmwareNumber(s);
	} else if (Signal != NULL && strcmp(Priv->MessageParts[0], "signal_dbm") == 0) {
		Signal->SignalStrength = atoi(Priv->MessageParts[1]);
	} else if (Signal != NULL && strcmp(Priv->MessageParts[0], "signal_bars") == 0) {
		signal_value = atoi(Priv->MessageParts[1]);
		if (signal_value != 0) {
			Signal->SignalPercent = 100 * 7 / signal_value;
		} else {
			Signal->SignalPercent = 0;
		}
	} else if (BatteryCharge != NULL && strcmp(Priv->MessageParts[0], "battery") == 0) {
		BatteryCharge->BatteryPercent = atoi(Priv->MessageParts[1]);
	}
	return ERR_NEEDANOTHERANSWER;
}

static GSM_Error S60_GetMemoryStatus(GSM_StateMachine *s, GSM_MemoryStatus *Status)
{
	GSM_Error error;

	if (Status->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}

	s->Phone.Data.MemoryStatus = Status;
	Status->MemoryUsed = 0;
	Status->MemoryFree = 1000;
	error = GSM_WaitFor(s, "", 0, NUM_CONTACTS_REQUEST_COUNT, S60_TIMEOUT, ID_GetMemoryStatus);
	s->Phone.Data.MemoryStatus = NULL;
	return error;
}

static GSM_Error S60_GetMemoryLocations(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	Priv->ContactLocationsPos = 0;
	return GSM_WaitFor(s, "", 0, NUM_CONTACTS_REQUEST_HASH_SINGLE, S60_TIMEOUT, ID_GetMemoryStatus);
}

static GSM_Error S60_StoreLocation(GSM_StateMachine *s, int **locations, size_t *size, size_t *pos, int location)
{
	if ((*pos) + 3 >= (*size)) {
		*locations = (int *)realloc(*locations, ((*size) + 20) * sizeof(int));
		if (*locations == NULL) {
			return ERR_MOREMEMORY;
		}
		*size += 20;
	}
	(*locations)[(*pos)] = location;
	(*locations)[(*pos) + 1] = 0;
	(*pos) += 1;
	return ERR_NONE;
}


static GSM_Error S60_Reply_GetMemoryStatus(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	s->Phone.Data.MemoryStatus->MemoryUsed = atoi(msg->Buffer);

	return ERR_NONE;
}

static GSM_Error S60_Reply_ContactHash(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;

	}

	if (Priv->MessageParts[0] == NULL) {
		return ERR_UNKNOWN;
	}

	error = S60_StoreLocation(s, &Priv->ContactLocations, &Priv->ContactLocationsSize, &Priv->ContactLocationsPos, atoi(Priv->MessageParts[0]));
	if (error != ERR_NONE) {
		return error;

	}

	return ERR_NEEDANOTHERANSWER;
}

static GSM_Error S60_Reply_GetCalendarStatus(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[1] == NULL || Priv->MessageParts[2] == NULL) {
		return ERR_UNKNOWN;
	}

	if (s->Phone.Data.CalStatus != NULL) {
		s->Phone.Data.CalStatus->Used = atoi(Priv->MessageParts[1]);
	}
	if (s->Phone.Data.ToDoStatus != NULL) {
		s->Phone.Data.ToDoStatus->Used = atoi(Priv->MessageParts[2]);
	}

	return ERR_NONE;
}

static GSM_Error S60_GetCalendarStatus(GSM_StateMachine *s, GSM_CalendarStatus *Status)
{
	GSM_Error error;

	s->Phone.Data.CalStatus = Status;
	Status->Used = 0;
	Status->Free = 1000;
	error = GSM_WaitFor(s, "", 0, NUM_CALENDAR_REQUEST_COUNT, S60_TIMEOUT, ID_GetCalendarNotesInfo);
	s->Phone.Data.CalStatus = NULL;
	return error;
}

static GSM_Error S60_GetCalendarLocations(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	Priv->CalendarLocationsPos = 0;
	return GSM_WaitFor(s, "", 0, NUM_CALENDAR_REQUEST_ENTRIES_ALL, S60_TIMEOUT, ID_GetCalendarNotesInfo);
}

static GSM_Error S60_Reply_CalendarCount(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;

	}

	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[1] == NULL) {
		return ERR_UNKNOWN;
	}

	if (strcmp(Priv->MessageParts[1], "appointment") != 0 &&
		strcmp(Priv->MessageParts[1], "event") != 0 &&
		strcmp(Priv->MessageParts[1], "reminder") != 0 &&
		strcmp(Priv->MessageParts[1], "anniversary") != 0) {
		return ERR_NEEDANOTHERANSWER;
	}

	error = S60_StoreLocation(s, &Priv->CalendarLocations, &Priv->CalendarLocationsSize, &Priv->CalendarLocationsPos, atoi(Priv->MessageParts[0]));
	if (error != ERR_NONE) {
		return error;

	}

	if (s->Phone.Data.CalStatus != NULL) {
		s->Phone.Data.CalStatus->Used++;
	}
	return ERR_NEEDANOTHERANSWER;
}

static GSM_Error S60_GetToDoStatus(GSM_StateMachine *s, GSM_ToDoStatus *Status)
{
	GSM_Error error;

	s->Phone.Data.ToDoStatus = Status;
	Status->Used = 0;
	Status->Free = 1000;
	error = GSM_WaitFor(s, "", 0, NUM_CALENDAR_REQUEST_COUNT, S60_TIMEOUT, ID_GetCalendarNotesInfo);
	s->Phone.Data.ToDoStatus = NULL;
	return error;
}

static GSM_Error S60_GetToDoLocations(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	Priv->ToDoLocationsPos = 0;
	return GSM_WaitFor(s, "", 0, NUM_CALENDAR_REQUEST_ENTRIES_ALL, S60_TIMEOUT, ID_GetToDoInfo);
}

static GSM_Error S60_Reply_ToDoCount(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;

	}

	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[1] == NULL) {
		return ERR_UNKNOWN;
	}

	if (strcmp(Priv->MessageParts[1], "todo") != 0) {
		return ERR_NEEDANOTHERANSWER;
	}

	error = S60_StoreLocation(s, &Priv->ToDoLocations, &Priv->ToDoLocationsSize, &Priv->ToDoLocationsPos, atoi(Priv->MessageParts[0]));
	if (error != ERR_NONE) {
		return error;

	}

	if (s->Phone.Data.ToDoStatus != NULL) {
		s->Phone.Data.ToDoStatus->Used++;
	}
	return ERR_NEEDANOTHERANSWER;
}

GSM_Error S60_GetMemory(GSM_StateMachine *s, GSM_MemoryEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	if (Entry->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}
	Entry->EntriesNum = 0;

	sprintf(buffer, "%d", Entry->Location);

	s->Phone.Data.Memory = Entry;
	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CONTACTS_REQUEST_CONTACT, S60_TIMEOUT, ID_GetMemory);
	s->Phone.Data.Memory = NULL;

	return error;
}

GSM_Error S60_GetNextMemory(GSM_StateMachine *s, GSM_MemoryEntry *Entry, gboolean Start)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	if (Entry->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}

	if (Start) {
		error = S60_GetMemoryLocations(s);
		if (error != ERR_NONE) {
			return error;
		}
		Priv->ContactLocationsPos = 0;
	}

	if (Priv->ContactLocations[Priv->ContactLocationsPos] == 0) {
		return ERR_EMPTY;
	}

	Entry->Location = Priv->ContactLocations[Priv->ContactLocationsPos++];

	return S60_GetMemory(s, Entry);
}

static GSM_Error S60_Reply_GetMemory(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	char *pos, *type, *location, *value;
	GSM_MemoryEntry *Entry;
	gboolean text = FALSE;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	Entry = s->Phone.Data.Memory;

	/* Grab values */
	pos = Priv->MessageParts[0];
	type = Priv->MessageParts[1];
	location = Priv->MessageParts[2];
	value =Priv->MessageParts[3];

	/* We need all of them */
	if (pos == NULL || type == NULL || location == NULL || value == NULL) {
		return ERR_UNKNOWN;
	}

	/* Handle location */
	if ((strcmp(location, "work") == 0)) {
		Entry->Entries[Entry->EntriesNum].Location = PBK_Location_Work;
	} else if ((strcmp(location, "home") == 0)) {
		Entry->Entries[Entry->EntriesNum].Location = PBK_Location_Home;
	} else {
		Entry->Entries[Entry->EntriesNum].Location = PBK_Location_Unknown;
	}

	/* Store in contacts */
	if(strcmp(type, "city") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_City;
	} else if(strcmp(type, "company_name") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Company;
	} else if(strcmp(type, "country") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Country;
	} else if(strcmp(type, "date") == 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Date;
		if (!ReadVCALDateTime(value, &(Entry->Entries[Entry->EntriesNum].Date))) {
			return ERR_UNKNOWN;
		}
	} else if(strcmp(type, "dtmf_string") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_DTMF;
	} else if(strcmp(type, "email_address") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Email;
	} else if(strcmp(type, "po_box") == 0) {
		text = TRUE;
	} else if(strcmp(type, "extended_address") == 0) {
		text = TRUE;
	} else if(strcmp(type, "fax_number") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Number_Fax;
	} else if(strcmp(type, "first_name") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_FirstName;
	} else if(strcmp(type, "second_name") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_SecondName;
	} else if(strcmp(type, "job_title") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_JobTitle;
	} else if(strcmp(type, "last_name") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_LastName;
	} else if(strcmp(type, "voip") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_VOIP;
	} else if(strcmp(type, "sip_id") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_SIP;
	} else if(strcmp(type, "push_to_talk") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_PushToTalkID;
	} else if(strcmp(type, "mobile_number") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Number_Mobile;
	} else if(strcmp(type, "note") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Note;
	} else if(strcmp(type, "pager_number") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Number_Pager;
	} else if(strcmp(type, "phone_number") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Number_General;
	} else if(strcmp(type, "postal_address") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Postal;
	} else if(strcmp(type, "postal_code") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_Zip;
	} else if(strcmp(type, "state") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_State;
	} else if(strcmp(type, "street_address") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_StreetAddress;
	} else if(strcmp(type, "url") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_URL;
	} else if(strcmp(type, "video_number") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Number_Video;
	} else if(strcmp(type, "wvid") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_WVID;
	} else if(strcmp(type, "thumbnail_image") == 0) {
		/* TODO */
	} else if(strcmp(type, "suffix") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_NameSuffix;
	} else if(strcmp(type, "prefix") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_NamePrefix;
	} else if(strcmp(type, "share_view") == 0) {
		text = TRUE;
		Entry->Entries[Entry->EntriesNum].EntryType = PBK_Text_SWIS;
	} else {
		smprintf(s, "WARNING: Ignoring unknown field type: %s\n", type);
		return ERR_NEEDANOTHERANSWER;
	}

	if (text) {
		if (strlen(value) == 0) {
			/* Ignore empty responses */
			return ERR_NEEDANOTHERANSWER;
		}
		DecodeUTF8(Entry->Entries[Entry->EntriesNum].Text, value, strlen(value));
	}

	Entry->EntriesNum++;
	if (Entry->EntriesNum >= GSM_PHONEBOOK_ENTRIES) {
		smprintf(s, "ERROR: reached limit of phonebook entries\n");
		return ERR_MOREMEMORY;
	}

	return ERR_NEEDANOTHERANSWER;
}

GSM_Error S60_DeleteMemory(GSM_StateMachine *s, GSM_MemoryEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	if (Entry->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}

	sprintf(buffer, "%d", Entry->Location);

	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CONTACTS_DELETE, S60_TIMEOUT, ID_None);

	return error;
}

GSM_Error S60_SetMemoryEntry(GSM_StateMachine *s, GSM_SubMemoryEntry *Entry, int pos, int reqtype)
{
	const char *type, *location = "none";
	char value[(GSM_PHONEBOOK_TEXT_LENGTH + 1) * 2];
	char buffer [100 + (GSM_PHONEBOOK_TEXT_LENGTH + 1) * 2];
	gboolean text = FALSE;

	switch (Entry->Location) {
		case PBK_Location_Unknown:
			location = "none";
			break;
		case PBK_Location_Home:
			location = "home";
			break;
		case PBK_Location_Work:
			location = "work";
			break;
	}

	switch (Entry->EntryType) {
		case PBK_Text_City:
			type = "city";
			text = TRUE;
			break;
		case PBK_Text_Company:
			type = "company_name";
			text = TRUE;
			break;
		case PBK_Text_Country:
			type = "country";
			text = TRUE;
			break;
		case PBK_Date:
			type = "date";
			snprintf(value, sizeof(value), "%04d%02d%02d", Entry->Date.Year, Entry->Date.Month, Entry->Date.Day);
			break;
		case PBK_Text_DTMF:
			type = "dtmf_string";
			text = TRUE;
			break;
		case PBK_Text_Email:
			type = "email_address";
			text = TRUE;
			break;
		case PBK_Number_Fax:
			type = "fax_number";
			text = TRUE;
			break;
		case PBK_Text_FirstName:
			type = "first_name";
			text = TRUE;
			break;
		case PBK_Text_SecondName:
			type = "second_name";
			text = TRUE;
			break;
		case PBK_Text_JobTitle:
			type = "job_title";
			text = TRUE;
			break;
		case PBK_Text_LastName:
			type = "last_name";
			text = TRUE;
			break;
		case PBK_Text_VOIP:
			type = "voip";
			text = TRUE;
			break;
		case PBK_Text_SIP:
			type = "sip_id";
			text = TRUE;
			break;
		case PBK_PushToTalkID:
			type = "push_to_talk";
			text = TRUE;
			break;
		case PBK_Number_Mobile:
			type = "mobile_number";
			text = TRUE;
			break;
		case PBK_Text_Note:
			type = "note";
			text = TRUE;
			break;
		case PBK_Number_Pager:
			type = "pager";
			text = TRUE;
			break;
		case PBK_Number_General:
			type = "phone_number";
			text = TRUE;
			break;
		case PBK_Text_Postal:
			type = "postal_address";
			text = TRUE;
			break;
		case PBK_Text_Zip:
			type = "postal_code";
			text = TRUE;
			break;
		case PBK_Text_State:
			type = "state";
			text = TRUE;
			break;
		case PBK_Text_StreetAddress:
			type = "street_address";
			text = TRUE;
			break;
		case PBK_Text_URL:
			type = "url";
			text = TRUE;
			break;
		case PBK_Number_Video:
			type = "video_number";
			text = TRUE;
			break;
		case PBK_Text_WVID:
			type = "wvid";
			text = TRUE;
			break;
		case PBK_Text_NameSuffix:
			type = "suffix";
			text = TRUE;
			break;
		case PBK_Text_NamePrefix:
			type = "prefix";
			text = TRUE;
			break;
		case PBK_Text_SWIS:
			type = "share_view";
			text = TRUE;
			break;
		default:
			Entry->AddError = ERR_NOTIMPLEMENTED;
			return ERR_NONE;
	}

	if (text) {
		EncodeUTF8(value, Entry->Text);
	}

	snprintf(buffer, sizeof(buffer), "%d%c%s%c%s%c%s%c",
		pos, NUM_SEPERATOR,
		type , NUM_SEPERATOR,
		location, NUM_SEPERATOR,
		value, NUM_SEPERATOR);

	return GSM_WaitFor(s, buffer, strlen(buffer), reqtype, S60_TIMEOUT, ID_None);
}

GSM_Error S60_SetMemory(GSM_StateMachine *s, GSM_MemoryEntry *Entry)
{
	GSM_MemoryEntry oldentry;
	GSM_Error error;
	int i;

	if (Entry->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}
	oldentry.MemoryType = Entry->MemoryType;
	oldentry.Location = Entry->Location;

	/* Read existing entry */
	error = S60_GetMemory(s, &oldentry);
	if (error != ERR_NONE) {
		return error;
	}

	/* TODO: Here should be some clever method for doing only needed changes */

	/* Delete all existing fields */
	for (i = 0; i < oldentry.EntriesNum; i++) {
		error = S60_SetMemoryEntry(s, &(oldentry.Entries[i]), Entry->Location, NUM_CONTACTS_CHANGE_REMOVEFIELD);
		if (error != ERR_NONE) {
			return error;
		}
	}

	/* Set all new fields */
	for (i = 0; i < Entry->EntriesNum; i++) {
		error = S60_SetMemoryEntry(s, &(Entry->Entries[i]), Entry->Location, NUM_CONTACTS_CHANGE_ADDFIELD);
		if (error != ERR_NONE) {
			return error;
		}
	}

	return ERR_NONE;
}

GSM_Error S60_AddMemory(GSM_StateMachine *s, GSM_MemoryEntry *Entry)
{
	GSM_Error error;

	if (Entry->MemoryType != MEM_ME) {
		return ERR_NOTSUPPORTED;
	}

	s->Phone.Data.Memory = Entry;
	error = GSM_WaitFor(s, NULL, 0, NUM_CONTACTS_ADD, S60_TIMEOUT, ID_SetMemory);
	s->Phone.Data.Memory = NULL;

	if (error != ERR_NONE) {
		return error;
	}

	return S60_SetMemory(s, Entry);
}

static GSM_Error S60_Reply_AddMemory(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	s->Phone.Data.Memory->Location = atoi(msg->Buffer);
	smprintf(s, "Added contact ID %d\n", s->Phone.Data.Memory->Location);
	return ERR_NONE;
}

static GSM_Error S60_Reply_GetCalendar(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	char *type, *content, *location, *start, *end, *modified, *replication, *alarm_time, *repeat, *repeat_rule, *repeat_start, *repeat_end, *interval;
	GSM_CalendarEntry *Entry;
	int i;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	/* Check for required fields */
	for (i = 0; i < 16; i++) {
		if (Priv->MessageParts[i] == NULL) {
			smprintf(s, "Not enough parts in reply!\n");
			return ERR_UNKNOWN;
		}
	}

	Entry = s->Phone.Data.Cal;

	/* Grab values */
	/* No need to parse position */
	type = Priv->MessageParts[1];
	content = Priv->MessageParts[2];
	location = Priv->MessageParts[3];
	start = Priv->MessageParts[4];
	end = Priv->MessageParts[5];
	modified = Priv->MessageParts[6];
	replication = Priv->MessageParts[7];
	alarm_time = Priv->MessageParts[8];
	/* Priority not used for calendar */
	repeat = Priv->MessageParts[10];
	repeat_rule = Priv->MessageParts[11];
	/* We do not handle repeat_exceptions for now */
	repeat_start = Priv->MessageParts[13];
	repeat_end = Priv->MessageParts[14];
	interval = Priv->MessageParts[15];

	/* Check for correct type */
	if (strcmp(type, "appointment") == 0) {
		Entry->Type = GSM_CAL_MEETING;
	} else if (strcmp(type, "event") == 0) {
		Entry->Type = GSM_CAL_MEMO;
	} else if (strcmp(type, "anniversary") == 0) {
		Entry->Type = GSM_CAL_BIRTHDAY;
	} else if (strcmp(type, "reminder") == 0) {
		Entry->Type = GSM_CAL_REMINDER;
	} else {
		return ERR_EMPTY;
	}

	if (strlen(content) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_TEXT;
		DecodeUTF8(Entry->Entries[Entry->EntriesNum].Text, content, strlen(content));
		Entry->EntriesNum++;
	}

	if (strlen(location) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_LOCATION;
		DecodeUTF8(Entry->Entries[Entry->EntriesNum].Text, location, strlen(location));
		Entry->EntriesNum++;
	}

	if (strlen(start) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_START_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), start);
		Entry->EntriesNum++;
	}

	if (strlen(end) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_END_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), end);
		Entry->EntriesNum++;
	}

	if (strlen(modified) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_LAST_MODIFIED;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), modified);
		Entry->EntriesNum++;
	}

	if (strlen(replication) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_PRIVATE;
		if (strcmp(replication, "open") == 0) {
			Entry->Entries[Entry->EntriesNum].Number = 0;
		} else {
			Entry->Entries[Entry->EntriesNum].Number = 1;
		}
		Entry->EntriesNum++;
	}

	if (strlen(alarm_time) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_TONE_ALARM_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), alarm_time);
		Entry->EntriesNum++;
	}

	if ((strlen(repeat) > 0) && (strlen(repeat_rule) > 0)) {
		if (strcmp(repeat, "daily") == 0 ) {
		} else if (strcmp(repeat, "weekly") == 0 ) {
			Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_DAYOFWEEK;
			Entry->Entries[Entry->EntriesNum].Number = atoi(repeat_rule);
			Entry->EntriesNum++;
		} else if (strcmp(repeat, "monthly_by_dates") == 0 ) {
			Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_DAY;
			Entry->Entries[Entry->EntriesNum].Number = atoi(repeat_rule);
			Entry->EntriesNum++;
		} else if (strcmp(repeat, "monthly_by_days") == 0 ) {
		} else if (strcmp(repeat, "yearly_by_date") == 0 ) {
		} else if (strcmp(repeat, "yearly_by_day") == 0 ) {
			Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_DAYOFYEAR;
			Entry->Entries[Entry->EntriesNum].Number = atoi(repeat_rule);
			Entry->EntriesNum++;
		} else {
			smprintf(s, "Unknown value for repeating: %s\n", repeat);
			return ERR_UNKNOWN;
		}
	}

	if (strlen(repeat_start) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_STARTDATE;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), repeat_start);
		Entry->EntriesNum++;
	}

	if (strlen(repeat_end) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_STOPDATE;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), repeat_end);
		Entry->EntriesNum++;
	}

	if (strlen(interval) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = CAL_REPEAT_FREQUENCY;
		Entry->Entries[Entry->EntriesNum].Number = atoi(interval);
		Entry->EntriesNum++;
	}


	/* TODO: implement rest of repeating */

	return ERR_NONE;
}

GSM_Error S60_GetCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	Entry->EntriesNum = 0;

	sprintf(buffer, "%d", Entry->Location);

	s->Phone.Data.Cal = Entry;
	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CALENDAR_REQUEST_ENTRY, S60_TIMEOUT, ID_GetCalendarNote);
	s->Phone.Data.Cal = NULL;

	return error;
}

static GSM_Error S60_Reply_AddCalendar(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	s->Phone.Data.Cal->Location = atoi(msg->Buffer);
	smprintf(s, "Added calendar ID %d\n", s->Phone.Data.Cal->Location);
	return ERR_NONE;
}

int S60_FindCalendarField(GSM_StateMachine *s, GSM_CalendarEntry *Entry, GSM_CalendarType Type)
{
	int i;

	for (i = 0; i < Entry->EntriesNum; i++) {
		if (Entry->Entries[i].EntryType == Type && Entry->Entries[i].AddError == ERR_NOTSUPPORTED) {
			Entry->Entries[i].AddError = ERR_NONE;
			return i;
		}
	}
	return -1;
}

void S60_SetCalendarError(GSM_StateMachine *s, GSM_CalendarEntry *Entry)
{
	int i;

	for (i = 0; i < Entry->EntriesNum; i++) {
		Entry->Entries[i].AddError = ERR_NOTSUPPORTED;
	}
}

GSM_Error S60_SetAddCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry, int request, int ID_request)
{
	int i;
	char buffer[1024];
	const char *type;

	if (ID_request == ID_SetCalendarNote) {
		sprintf(buffer, "%d%s", Entry->Location, NUM_SEPERATOR_STR);
	} else {
		switch (Entry->Type) {
			case GSM_CAL_MEETING:
				type = "appointment";
				break;
			case GSM_CAL_MEMO:
				type = "event";
				break;
			case GSM_CAL_BIRTHDAY:
				type = "anniversary";
				break;
			case GSM_CAL_REMINDER:
				type = "reminder";
				break;
			default:
				type = "appointment";
				break;
		}
		sprintf(buffer, "%s%s", type, NUM_SEPERATOR_STR);
	}


	S60_SetCalendarError(s, Entry);

	i = S60_FindCalendarField(s, Entry, CAL_TEXT);
	if (i == -1) {
		i = S60_FindCalendarField(s, Entry, CAL_DESCRIPTION);
	}
	if (i != -1) {
		EncodeUTF8(buffer + strlen(buffer), Entry->Entries[i].Text);
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_LOCATION);
	if (i != -1) {
		EncodeUTF8(buffer + strlen(buffer), Entry->Entries[i].Text);
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_START_DATETIME);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_END_DATETIME);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_PRIVATE);
	if (i != -1) {
		if (Entry->Entries[i].Number) {
			strcat(buffer, "private");
		} else {
			strcat(buffer, "open");
		}
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_TONE_ALARM_DATETIME);
	if (i == -1) {
		i = S60_FindCalendarField(s, Entry, CAL_SILENT_ALARM_DATETIME);
	}
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	/* Priority */
	strcat(buffer, "2");
	strcat(buffer, NUM_SEPERATOR_STR);

	/* TODO: implement repeating */
	strcat(buffer, NUM_SEPERATOR_STR); /* Type */
	strcat(buffer, NUM_SEPERATOR_STR); /* Days */
	strcat(buffer, NUM_SEPERATOR_STR); /* Exceptions */

	i = S60_FindCalendarField(s, Entry, CAL_REPEAT_STARTDATE);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_REPEAT_STOPDATE);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindCalendarField(s, Entry, CAL_REPEAT_FREQUENCY);
	if (i != -1) {
		sprintf(buffer + strlen(buffer), "%d", Entry->Entries[i].Number);
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	return GSM_WaitFor(s, buffer, strlen(buffer), request, S60_TIMEOUT, ID_request);
}

GSM_Error S60_SetCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry)
{
	return S60_SetAddCalendar(s, Entry, NUM_CALENDAR_ENTRY_CHANGE, ID_SetCalendarNote);
}

GSM_Error S60_AddCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry)
{
	s->Phone.Data.Cal = Entry;
	return S60_SetAddCalendar(s, Entry, NUM_CALENDAR_ENTRY_ADD, ID_AddCalendarNote);
}

GSM_Error S60_GetNextCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry, gboolean Start)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	if (Start) {
		error = S60_GetCalendarLocations(s);
		if (error != ERR_NONE) {
			return error;
		}
		Priv->CalendarLocationsPos = 0;
	}

	if (Priv->CalendarLocations[Priv->CalendarLocationsPos] == 0) {
		return ERR_EMPTY;
	}

	Entry->Location = Priv->CalendarLocations[Priv->CalendarLocationsPos++];

	return S60_GetCalendar(s, Entry);
}

GSM_Error S60_DeleteCalendar(GSM_StateMachine *s, GSM_CalendarEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	sprintf(buffer, "%d", Entry->Location);

	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CALENDAR_ENTRY_DELETE, S60_TIMEOUT, ID_None);

	return error;
}

static GSM_Error S60_Reply_GetToDo(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;
	char *type, *content, *location, *start, *end, *modified, *replication, *alarm_time, *priority, *crossedout, *crossedout_time;
	GSM_ToDoEntry *Entry;
	int i;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	/* Check for required fields */
	for (i = 0; i < 18; i++) {
		if (Priv->MessageParts[i] == NULL) {
			smprintf(s, "Not enough parts in reply!\n");
			return ERR_UNKNOWN;
		}
	}

	Entry = s->Phone.Data.ToDo;

	/* Grab values */
	/* No need to parse position */
	type = Priv->MessageParts[1];
	content = Priv->MessageParts[2];
	location = Priv->MessageParts[3];
	start = Priv->MessageParts[4];
	end = Priv->MessageParts[5];
	modified = Priv->MessageParts[6];
	replication = Priv->MessageParts[7];
	alarm_time = Priv->MessageParts[8];
	priority = Priv->MessageParts[9];
	/* Repeating does not make sense for todo */
	crossedout = Priv->MessageParts[16];
	crossedout_time = Priv->MessageParts[17];

	/* Check for correct type */
	if (strcmp(type, "todo") == 0) {
		Entry->Type = GSM_CAL_MEMO;
	} else {
		return ERR_EMPTY;
	}

	if (strlen(content) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_TEXT;
		DecodeUTF8(Entry->Entries[Entry->EntriesNum].Text, content, strlen(content));
		Entry->EntriesNum++;
	}

	if (strlen(location) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_LOCATION;
		DecodeUTF8(Entry->Entries[Entry->EntriesNum].Text, location, strlen(location));
		Entry->EntriesNum++;
	}

	if (strlen(start) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_START_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), start);
		Entry->EntriesNum++;
	}

	if (strlen(end) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_END_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), end);
		Entry->EntriesNum++;
	}

	if (strlen(modified) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_LAST_MODIFIED;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), modified);
		Entry->EntriesNum++;
	}

	if (strlen(replication) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_PRIVATE;
		if (strcmp(replication, "open") == 0) {
			Entry->Entries[Entry->EntriesNum].Number = 0;
		} else {
			Entry->Entries[Entry->EntriesNum].Number = 1;
		}
		Entry->EntriesNum++;
	}

	if (strlen(alarm_time) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_ALARM_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), alarm_time);
		Entry->EntriesNum++;
	}

	if (strlen(priority) > 0) {
		Entry->Priority = atoi(priority);
	}

	if (strlen(crossedout) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_COMPLETED;
		Entry->Entries[Entry->EntriesNum].Number = atoi(crossedout);
		Entry->EntriesNum++;
	}

	if (strlen(crossedout_time) > 0) {
		Entry->Entries[Entry->EntriesNum].EntryType = TODO_COMPLETED_DATETIME;
		GSM_DateTimeFromTimestamp(&(Entry->Entries[Entry->EntriesNum].Date), crossedout_time);
		Entry->EntriesNum++;
	}

	return ERR_NONE;
}

GSM_Error S60_GetToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	Entry->EntriesNum = 0;

	sprintf(buffer, "%d", Entry->Location);

	s->Phone.Data.ToDo = Entry;
	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CALENDAR_REQUEST_ENTRY, S60_TIMEOUT, ID_GetToDo);
	s->Phone.Data.ToDo = NULL;

	return error;
}

GSM_Error S60_GetNextToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry, gboolean Start)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	if (Start) {
		error = S60_GetToDoLocations(s);
		if (error != ERR_NONE) {
			return error;
		}
		Priv->ToDoLocationsPos = 0;
	}

	if (Priv->ToDoLocations[Priv->ToDoLocationsPos] == 0) {
		return ERR_EMPTY;
	}

	Entry->Location = Priv->ToDoLocations[Priv->ToDoLocationsPos++];

	return S60_GetToDo(s, Entry);
}

GSM_Error S60_DeleteToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry)
{
	char buffer[100];
	GSM_Error error;

	sprintf(buffer, "%d", Entry->Location);

	error = GSM_WaitFor(s, buffer, strlen(buffer), NUM_CALENDAR_ENTRY_DELETE, S60_TIMEOUT, ID_None);

	return error;
}

static GSM_Error S60_Reply_AddToDo(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	s->Phone.Data.ToDo->Location = atoi(msg->Buffer);
	smprintf(s, "Added todo ID %d\n", s->Phone.Data.ToDo->Location);
	return ERR_NONE;
}

int S60_FindToDoField(GSM_StateMachine *s, GSM_ToDoEntry *Entry, GSM_ToDoType Type)
{
	int i;

	for (i = 0; i < Entry->EntriesNum; i++) {
		if (Entry->Entries[i].EntryType == Type) {
			return i;
		}
	}
	return -1;
}

GSM_Error S60_SetAddToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry, int request, int ID_request)
{
	int i;
	char buffer[1024];

	if (ID_request == ID_SetToDo) {
		sprintf(buffer, "%d%s", Entry->Location, NUM_SEPERATOR_STR);
	} else {
		sprintf(buffer, "%s%s", "todo", NUM_SEPERATOR_STR);
	}

	i = S60_FindToDoField(s, Entry, TODO_TEXT);
	if (i == -1) {
		i = S60_FindToDoField(s, Entry, TODO_DESCRIPTION);
	}
	if (i != -1) {
		EncodeUTF8(buffer + strlen(buffer), Entry->Entries[i].Text);
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindToDoField(s, Entry, TODO_LOCATION);
	if (i != -1) {
		EncodeUTF8(buffer + strlen(buffer), Entry->Entries[i].Text);
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindToDoField(s, Entry, TODO_START_DATETIME);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindToDoField(s, Entry, TODO_END_DATETIME);
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindToDoField(s, Entry, TODO_PRIVATE);
	if (i != -1) {
		if (Entry->Entries[i].Number) {
			strcat(buffer, "private");
		} else {
			strcat(buffer, "open");
		}
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	i = S60_FindToDoField(s, Entry, TODO_ALARM_DATETIME);
	if (i == -1) {
		i = S60_FindToDoField(s, Entry, TODO_SILENT_ALARM_DATETIME);
	}
	if (i != -1) {
		GSM_DateTimeToTimestamp(&(Entry->Entries[i].Date), buffer + strlen(buffer));
	}
	strcat(buffer, NUM_SEPERATOR_STR);

	/* Priority */
	sprintf(buffer + strlen(buffer), "%d", Entry->Priority);
	strcat(buffer, NUM_SEPERATOR_STR);

	strcat(buffer, NUM_SEPERATOR_STR); /* Type */
	strcat(buffer, NUM_SEPERATOR_STR); /* Days */
	strcat(buffer, NUM_SEPERATOR_STR); /* Exceptions */
	strcat(buffer, NUM_SEPERATOR_STR); /* Start */
	strcat(buffer, NUM_SEPERATOR_STR); /* End */
	strcat(buffer, NUM_SEPERATOR_STR); /* Frequency */

	return GSM_WaitFor(s, buffer, strlen(buffer), request, S60_TIMEOUT, ID_request);
}

GSM_Error S60_SetToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry)
{
	return S60_SetAddToDo(s, Entry, NUM_CALENDAR_ENTRY_CHANGE, ID_SetToDo);
}

GSM_Error S60_AddToDo(GSM_StateMachine *s, GSM_ToDoEntry *Entry)
{
	s->Phone.Data.ToDo = Entry;
	return S60_SetAddToDo(s, Entry, NUM_CALENDAR_ENTRY_ADD, ID_AddToDo);
}

GSM_Error S60_GetScreenshot(GSM_StateMachine *s, GSM_BinaryPicture *picture)
{
	GSM_Error error;

	s->Phone.Data.Picture = picture;
	error = GSM_WaitFor(s, NULL, 0, NUM_SCREENSHOT, S60_TIMEOUT, ID_Screenshot);
	s->Phone.Data.Picture = NULL;

	return error;
}

GSM_Error S60_Reply_Screenshot(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	s->Phone.Data.Picture->Type = PICTURE_PNG;
	s->Phone.Data.Picture->Buffer = (unsigned char *)malloc(msg->Length);
	if (s->Phone.Data.Picture->Buffer == NULL) {
		return ERR_MOREMEMORY;
	}
	s->Phone.Data.Picture->Length = DecodeBASE64(msg->Buffer, s->Phone.Data.Picture->Buffer, msg->Length);
	return ERR_NONE;
}

GSM_Error S60_GetSMSStatus(GSM_StateMachine *s, GSM_SMSMemoryStatus *status)
{
	GSM_Error error;

	status->SIMUnRead = 0;
	status->SIMUsed = 0;
	status->SIMSize = 0;
	status->TemplatesUsed = 0;
	status->PhoneUnRead = 0;
	status->PhoneUsed = 0;
	status->PhoneSize = 0;

	s->Phone.Data.SMSStatus = status;
	error = GSM_WaitFor(s, NULL, 0, NUM_MESSAGE_REQUEST_COUNT, S60_TIMEOUT, ID_GetSMSStatus);
	s->Phone.Data.SMSStatus = NULL;

	return error;
}

GSM_Error S60_Reply_GetSMSStatus(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[1] == NULL) {
		return ERR_UNKNOWN;
	}
	s->Phone.Data.SMSStatus->PhoneUsed = atoi(Priv->MessageParts[0]);
	s->Phone.Data.SMSStatus->PhoneUnRead = atoi(Priv->MessageParts[1]);
	s->Phone.Data.SMSStatus->PhoneSize = s->Phone.Data.SMSStatus->PhoneUsed + 1000;

	return ERR_NONE;
}

GSM_Error S60_GetSMSFolders(GSM_StateMachine *s UNUSED, GSM_SMSFolders *folders)
{
	folders->Number=2;
	EncodeUnicode(folders->Folder[0].Name,_("Inbox"),strlen(_("Inbox")));
	EncodeUnicode(folders->Folder[1].Name,_("Outbox"),strlen(_("Outbox")));
	folders->Folder[0].InboxFolder = TRUE;
	folders->Folder[1].InboxFolder = FALSE;
	folders->Folder[0].OutboxFolder 	= FALSE;
	folders->Folder[1].OutboxFolder 	= TRUE;
	folders->Folder[0].Memory      = MEM_ME;
	folders->Folder[1].Memory      = MEM_ME;
	return ERR_NONE;
}

GSM_Error S60_GetSMS(GSM_StateMachine *s, GSM_MultiSMSMessage *sms)
{

	char buffer[100];

	sprintf(buffer, "%d", sms->SMS[0].Location);

	sms->Number = 1;
	GSM_SetDefaultReceivedSMSData(&(sms->SMS[0]));
	s->Phone.Data.SaveSMSMessage = &(sms->SMS[0]);


	return GSM_WaitFor(s, buffer, strlen(buffer), NUM_MESSAGE_REQUEST_ONE, S60_TIMEOUT, ID_GetSMSMessage);
}

GSM_Error S60_DeleteSMS(GSM_StateMachine *s, GSM_SMSMessage *sms)
{

	char buffer[100];

	sprintf(buffer, "%d", sms->Location);

	return GSM_WaitFor(s, buffer, strlen(buffer), NUM_MESSAGE_DELETE, S60_TIMEOUT, ID_None);
}

GSM_Error S60_Reply_GetSMS(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	int i;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;
	}

	if (Priv->MessageParts[0] == NULL || Priv->MessageParts[0][0] == 0) {
		return ERR_EMPTY;
	}

	/* Check for required fields */
	for (i = 0; i < 6; i++) {
		if (Priv->MessageParts[i] == NULL) {
			smprintf(s, "Not enough parts in reply!\n");
			return ERR_UNKNOWN;
		}
	}

	/* Folder */
	if (strcmp(Priv->MessageParts[0], "inbox") == 0) {
		s->Phone.Data.SaveSMSMessage->Folder = 1;
		s->Phone.Data.SaveSMSMessage->InboxFolder = TRUE;
		s->Phone.Data.SaveSMSMessage->PDU = SMS_Deliver;
	} else {
		s->Phone.Data.SaveSMSMessage->Folder = 2;
		s->Phone.Data.SaveSMSMessage->InboxFolder = FALSE;
		s->Phone.Data.SaveSMSMessage->PDU = SMS_Submit;
	}

	/* ID */

	/* Time */
	GSM_DateTimeFromTimestamp(&(s->Phone.Data.SaveSMSMessage->DateTime), Priv->MessageParts[2]);

	/* Address */
	DecodeUTF8(s->Phone.Data.SaveSMSMessage->Number, Priv->MessageParts[3], strlen(Priv->MessageParts[3]));

	/* Content */
	DecodeUTF8(s->Phone.Data.SaveSMSMessage->Text, Priv->MessageParts[4], strlen(Priv->MessageParts[4]));
	s->Phone.Data.SaveSMSMessage->Length = UnicodeLength(s->Phone.Data.SaveSMSMessage->Text);
	s->Phone.Data.SaveSMSMessage->Coding = SMS_Coding_Default_No_Compression;

	/* Unread */
	if (strcmp(Priv->MessageParts[5], "1") == 0) {
		s->Phone.Data.SaveSMSMessage->State = SMS_UnRead;
	} else if (s->Phone.Data.SaveSMSMessage->InboxFolder) {
		s->Phone.Data.SaveSMSMessage->State = SMS_Read;
	} else {
		s->Phone.Data.SaveSMSMessage->State = SMS_Sent;
	}
	return ERR_NONE;
}

GSM_Error S60_Reply_SMSLocation(GSM_Protocol_Message *msg, GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	GSM_Error error;

	error = S60_SplitValues(msg, s);
	if (error != ERR_NONE) {
		return error;

	}

	if (Priv->MessageParts[0] == NULL) {
		return ERR_UNKNOWN;
	}

	error = S60_StoreLocation(s, &Priv->SMSLocations, &Priv->SMSLocationsSize, &Priv->SMSLocationsPos, atoi(Priv->MessageParts[0]));
	if (error != ERR_NONE) {
		return error;

	}

	return ERR_NEEDANOTHERANSWER;
}


static GSM_Error S60_GetSMSLocations(GSM_StateMachine *s)
{
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;
	Priv->SMSLocationsPos = 0;
	return GSM_WaitFor(s, "", 0, NUM_MESSAGE_REQUEST_LIST, S60_TIMEOUT, ID_GetSMSStatus);
}

GSM_Error S60_GetNextSMS(GSM_StateMachine *s, GSM_MultiSMSMessage *sms, gboolean Start)
{
	GSM_Error error;
	GSM_Phone_S60Data *Priv = &s->Phone.Data.Priv.S60;

	if (Start) {
		error = S60_GetSMSLocations(s);
		if (error != ERR_NONE) {
			return error;
		}
		Priv->SMSLocationsPos = 0;
	}

	if (Priv->SMSLocations[Priv->SMSLocationsPos] == 0) {
		return ERR_EMPTY;
	}

	sms->SMS[0].Location = Priv->SMSLocations[Priv->SMSLocationsPos++];

	return S60_GetSMS(s, sms);
}

GSM_Error S60_SendSMS(GSM_StateMachine *s, GSM_SMSMessage *sms)
{
	char buffer[((GSM_MAX_SMS_LENGTH + 1) * 2) + ((GSM_MAX_SMS_NAME_LENGTH + 1) * 2) + ((GSM_MAX_NUMBER_LENGTH + 1) * 2)];
	char textbuffer[((GSM_MAX_SMS_LENGTH + 1) * 2)];

	if (sms->UDH.Type != UDH_NoUDH) {
		return ERR_NOTSUPPORTED;
	}

	EncodeUTF8(buffer, sms->Name);
	strcat(buffer, NUM_SEPERATOR_STR);
	EncodeUTF8(buffer + strlen(buffer), sms->Number);
	strcat(buffer, NUM_SEPERATOR_STR);
	switch (sms->Coding) {
		case SMS_Coding_Unicode_No_Compression:
		case SMS_Coding_Unicode_Compression:
			strcat(buffer, "UCS2");
			break;
		case SMS_Coding_Default_No_Compression:
		case SMS_Coding_Default_Compression:
			strcat(buffer, "7bit");
			break;
		case SMS_Coding_8bit:
			strcat(buffer, "8bit");
			break;
	}
	strcat(buffer, NUM_SEPERATOR_STR);
	EncodeUTF8(textbuffer, sms->Text);
	EncodeSpecialChars(buffer + strlen(buffer), textbuffer);
	strcat(buffer, NUM_SEPERATOR_STR);

	return GSM_WaitFor(s, buffer, strlen(buffer), NUM_MESSAGE_SEND_REQUEST, S60_TIMEOUT, ID_None);
}

GSM_Error S60_GetSMSC(GSM_StateMachine *s, GSM_SMSC *smsc)
{
	if (smsc->Location != 1) {
		return ERR_EMPTY;
	}
	smsc->Name[0] = 0;
	smsc->Name[1] = 0;
	smsc->Number[0] = 0;
	smsc->Number[1] = 0;
	smsc->Validity.Format = SMS_Validity_NotAvailable;
	smsc->Validity.Relative = SMS_VALID_Max_Time;
	smsc->DefaultNumber[0] = 0;
	smsc->DefaultNumber[1] = 0;
	smsc->Format = SMS_FORMAT_Text;
	return ERR_NONE;
}

GSM_Error S60_DialVoice(GSM_StateMachine *s, char *Number, GSM_CallShowNumber ShowNumber)
{
	return GSM_WaitFor(s, Number, strlen(Number), NUM_DIAL, S60_TIMEOUT, ID_None);
}

GSM_Error S60_CancelCall(GSM_StateMachine *s, int ID, gboolean all)
{
	return GSM_WaitFor(s, "", 0, NUM_HANGUP, S60_TIMEOUT, ID_None);
}

GSM_Reply_Function S60ReplyFunctions[] = {

	{S60_Reply_Connect,	"", 0x00, NUM_CONNECTED, ID_Initialise },
	{S60_Reply_Generic,	"", 0x00, NUM_HELLO_REPLY, ID_EnableEcho },

	{S60_Reply_Generic,	"", 0x00, NUM_SYSINFO_REPLY_START, ID_GetModel },
	{S60_Reply_GetInfo,	"", 0x00, NUM_SYSINFO_REPLY_LINE, ID_GetModel },
	{S60_Reply_Generic,	"", 0x00, NUM_SYSINFO_REPLY_END, ID_GetModel },

	{S60_Reply_Generic,	"", 0x00, NUM_CONTACTS_REPLY_HASH_SINGLE_START, ID_GetMemoryStatus },
	{S60_Reply_ContactHash, "", 0x00, NUM_CONTACTS_REPLY_HASH_SINGLE_LINE, ID_GetMemoryStatus },
	{S60_Reply_Generic,	"", 0x00, NUM_CONTACTS_REPLY_HASH_SINGLE_END, ID_GetMemoryStatus },

	{S60_Reply_SMSLocation, "", 0x00, NUM_MESSAGE_REPLY_LIST, ID_GetSMSStatus },
	{S60_Reply_Generic,	"", 0x00, NUM_MESSAGE_REPLY_END, ID_GetSMSStatus },

	{S60_Reply_GetMemoryStatus,	"", 0x00, NUM_CONTACTS_REPLY_COUNT, ID_GetMemoryStatus },

	{S60_Reply_Generic,	"", 0x00, NUM_CALENDAR_REPLY_ENTRIES_START, ID_GetCalendarNotesInfo },
	{S60_Reply_CalendarCount, "", 0x00, NUM_CALENDAR_REPLY_ENTRY, ID_GetCalendarNotesInfo },
	{S60_Reply_Generic,	"", 0x00, NUM_CALENDAR_REPLY_ENTRIES_END, ID_GetCalendarNotesInfo },

	{S60_Reply_Generic,	"", 0x00, NUM_CALENDAR_REPLY_ENTRIES_START, ID_GetToDoInfo },
	{S60_Reply_ToDoCount, "", 0x00, NUM_CALENDAR_REPLY_ENTRY, ID_GetToDoInfo },
	{S60_Reply_Generic,	"", 0x00, NUM_CALENDAR_REPLY_ENTRIES_END, ID_GetToDoInfo },

	{S60_Reply_Generic, "", 0x00, NUM_CONTACTS_REPLY_CONTACT_START, ID_GetMemory },
	{S60_Reply_GetMemory, "", 0x00, NUM_CONTACTS_REPLY_CONTACT_LINE, ID_GetMemory },
	{S60_Reply_Generic, "", 0x00, NUM_CONTACTS_REPLY_CONTACT_END, ID_GetMemory },
	{S60_Reply_Generic, "", 0x00, NUM_CONTACTS_REPLY_CONTACT_NOT_FOUND, ID_GetMemory },

	{S60_Reply_GetCalendar, "", 0x00, NUM_CALENDAR_REPLY_ENTRY, ID_GetCalendarNote },
	{S60_Reply_Generic, "", 0x00, NUM_CALENDAR_REPLY_ENTRY_NOT_FOUND, ID_GetCalendarNote },

	{S60_Reply_GetToDo, "", 0x00, NUM_CALENDAR_REPLY_ENTRY, ID_GetToDo },
	{S60_Reply_Generic, "", 0x00, NUM_CALENDAR_REPLY_ENTRY_NOT_FOUND, ID_GetToDo },

	{S60_Reply_AddMemory, "", 0x00, NUM_CONTACTS_ADD_REPLY_ID, ID_SetMemory },

	{S60_Reply_GetNetworkInfo, "", 0x00, NUM_LOCATION_REPLY, ID_GetNetworkInfo },
	{S60_Reply_Generic, "", 0x00, NUM_LOCATION_REPLY_NA, ID_GetNetworkInfo },

	{S60_Reply_Generic, "", 0x00, NUM_QUIT, ID_Terminate },

	{S60_Reply_Screenshot, "", 0x00, NUM_SCREENSHOT_REPLY, ID_Screenshot },

	{S60_Reply_GetSMSStatus, "", 0x00,NUM_MESSAGE_REPLY_COUNT, ID_GetSMSStatus },
	{S60_Reply_GetSMS, "", 0x00, NUM_MESSAGE_REPLY_ONE, ID_GetSMSMessage },

	{S60_Reply_GetCalendarStatus, "", 0x00, NUM_CALENDAR_REPLY_COUNT, ID_GetCalendarNotesInfo },

	{S60_Reply_SendSMS, "", 0x00, NUM_MESSAGE_SEND_REPLY_RETRY, ID_IncomingFrame },
	{S60_Reply_SendSMS, "", 0x00, NUM_MESSAGE_SEND_REPLY_STATUS, ID_IncomingFrame },
	{S60_Reply_SendSMS, "", 0x00, NUM_MESSAGE_SEND_REPLY_OK, ID_IncomingFrame },
	{S60_Reply_SendSMS, "", 0x00, NUM_MESSAGE_SEND_REPLY_FAILURE, ID_IncomingFrame },

	{S60_Reply_Generic, "", 0x00, NUM_CALENDAR_ENTRY_CHANGE_REPLY_TIME, ID_SetCalendarNote },
	{S60_Reply_AddCalendar, "", 0x00, NUM_CALENDAR_ENTRY_ADD_REPLY, ID_AddCalendarNote },

	{S60_Reply_Generic, "", 0x00, NUM_CALENDAR_ENTRY_CHANGE_REPLY_TIME, ID_SetToDo },
	{S60_Reply_AddToDo, "", 0x00, NUM_CALENDAR_ENTRY_ADD_REPLY, ID_AddToDo },

	{NULL,			"", 0x00, 0x00, ID_None }
};

GSM_Phone_Functions S60Phone = {
	"s60",
	S60ReplyFunctions,
	S60_Install,
	S60_Initialise,
	S60_Terminate,
	GSM_DispatchMessage,
	NOTIMPLEMENTED,			/* 	ShowStartInfo		*/
	S60_GetInfo,                 /*      GetManufacturer */
	S60_GetInfo,                 /*      GetModel */
	S60_GetInfo,                 /*      GetFirmware */
	S60_GetInfo,                 /*      GetIMEI */
	NOTIMPLEMENTED,			/*	GetOriginalIMEI		*/
	NOTIMPLEMENTED,			/*	GetManufactureMonth	*/
	NOTIMPLEMENTED,			/*	GetProductCode		*/
	NOTIMPLEMENTED,			/*	GetHardware		*/
	NOTIMPLEMENTED,			/*	GetPPM			*/
	NOTIMPLEMENTED,			/*	GetSIMIMSI		*/
	NOTIMPLEMENTED,			/*	GetDateTime		*/
	NOTIMPLEMENTED,			/*	SetDateTime		*/
	NOTIMPLEMENTED,			/*	GetAlarm		*/
	NOTIMPLEMENTED,			/*	SetAlarm		*/
	NOTSUPPORTED,			/* 	GetLocale		*/
	NOTSUPPORTED,			/* 	SetLocale		*/
	NOTIMPLEMENTED,			/*	PressKey		*/
	NOTIMPLEMENTED,			/*	Reset			*/
	NOTIMPLEMENTED,			/*	ResetPhoneSettings	*/
	NOTIMPLEMENTED,			/*	EnterSecurityCode	*/
	NOTIMPLEMENTED,			/*	GetSecurityStatus	*/
	NOTIMPLEMENTED,			/*	GetDisplayStatus	*/
	NOTIMPLEMENTED,			/*	SetAutoNetworkLogin	*/
	S60_GetBatteryCharge,
	S60_GetSignalQuality,
	S60_GetNetworkInfo,
	NOTIMPLEMENTED,     		/*  	GetCategory 		*/
 	NOTSUPPORTED,       		/*  	AddCategory 		*/
        NOTIMPLEMENTED,      		/*  	GetCategoryStatus 	*/
	S60_GetMemoryStatus,
	S60_GetMemory,
	S60_GetNextMemory,
	S60_SetMemory,
	S60_AddMemory,
	S60_DeleteMemory,
	NOTIMPLEMENTED,                 /*      DeleteAllMemory */
	NOTIMPLEMENTED,			/*	GetSpeedDial		*/
	NOTIMPLEMENTED,			/*	SetSpeedDial		*/
	S60_GetSMSC,
	NOTIMPLEMENTED,			/*	SetSMSC			*/
	S60_GetSMSStatus,
	S60_GetSMS,
	S60_GetNextSMS,
	NOTIMPLEMENTED,			/*	SetSMS			*/
	NOTIMPLEMENTED,			/*	AddSMS			*/
	S60_DeleteSMS,
	S60_SendSMS,
	NOTSUPPORTED,			/*	SendSavedSMS		*/
	NOTSUPPORTED,			/*	SetFastSMSSending	*/
	NOTIMPLEMENTED,			/*	SetIncomingSMS		*/
	NOTIMPLEMENTED,			/* 	SetIncomingCB		*/
	S60_GetSMSFolders,
 	NOTIMPLEMENTED,			/* 	AddSMSFolder		*/
 	NOTIMPLEMENTED,			/* 	DeleteSMSFolder		*/
	S60_DialVoice,
        NOTIMPLEMENTED,			/*	DialService		*/
	NOTIMPLEMENTED,			/*	AnswerCall		*/
	S60_CancelCall,
 	NOTIMPLEMENTED,			/* 	HoldCall 		*/
 	NOTIMPLEMENTED,			/* 	UnholdCall 		*/
 	NOTIMPLEMENTED,			/* 	ConferenceCall 		*/
 	NOTIMPLEMENTED,			/* 	SplitCall		*/
 	NOTIMPLEMENTED,			/* 	TransferCall		*/
 	NOTIMPLEMENTED,			/* 	SwitchCall		*/
 	NOTIMPLEMENTED,			/* 	GetCallDivert		*/
 	NOTIMPLEMENTED,			/* 	SetCallDivert		*/
 	NOTIMPLEMENTED,			/* 	CancelAllDiverts	*/
	NOTIMPLEMENTED,			/*	SetIncomingCall		*/
	NOTIMPLEMENTED,			/*  	SetIncomingUSSD		*/
	NOTIMPLEMENTED,			/*	SendDTMF		*/
	NOTIMPLEMENTED,			/*	GetRingtone		*/
	NOTIMPLEMENTED,			/*	SetRingtone		*/
	NOTIMPLEMENTED,			/*	GetRingtonesInfo	*/
	NOTIMPLEMENTED,			/* 	DeleteUserRingtones	*/
	NOTIMPLEMENTED,			/*	PlayTone		*/
	NOTIMPLEMENTED,			/*	GetWAPBookmark		*/
	NOTIMPLEMENTED,			/* 	SetWAPBookmark 		*/
	NOTIMPLEMENTED, 		/* 	DeleteWAPBookmark 	*/
	NOTIMPLEMENTED,			/* 	GetWAPSettings 		*/
	NOTIMPLEMENTED,			/* 	SetWAPSettings 		*/
	NOTSUPPORTED,			/*	GetSyncMLSettings	*/
	NOTSUPPORTED,			/*	SetSyncMLSettings	*/
	NOTSUPPORTED,			/*	GetChatSettings		*/
	NOTSUPPORTED,			/*	SetChatSettings		*/
	NOTSUPPORTED,			/* 	GetMMSSettings		*/
	NOTSUPPORTED,			/* 	SetMMSSettings		*/
	NOTSUPPORTED,			/*	GetMMSFolders		*/
	NOTSUPPORTED,			/*	GetNextMMSFileInfo	*/
	NOTIMPLEMENTED,			/*	GetBitmap		*/
	NOTIMPLEMENTED,			/*	SetBitmap		*/
	S60_GetToDoStatus,
	S60_GetToDo,
	S60_GetNextToDo,
	S60_SetToDo,
	S60_AddToDo,
	NOTIMPLEMENTED,                 /*      DeleteTodo */
	NOTIMPLEMENTED,                 /*      DeleteAllTodo */
	S60_GetCalendarStatus,
	S60_GetCalendar,
	S60_GetNextCalendar,
	S60_SetCalendar,
	S60_AddCalendar,
	NOTIMPLEMENTED,                 /*      DeleteCalendar */
	NOTIMPLEMENTED,                 /*      DeleteAllCalendar */
	NOTSUPPORTED,			/* 	GetCalendarSettings	*/
	NOTSUPPORTED,			/* 	SetCalendarSettings	*/
	NOTIMPLEMENTED,                 /*      GetNoteStatus */
	NOTIMPLEMENTED,                 /*      GetNote */
	NOTIMPLEMENTED,                 /*      GetNextNote */
	NOTIMPLEMENTED,                 /*      SetNote */
	NOTIMPLEMENTED,                 /*      AddNote */
	NOTIMPLEMENTED,                 /*      DeleteNote */
	NOTIMPLEMENTED,                 /*      DeleteAllNotes */
	NOTIMPLEMENTED, 		/*	GetProfile		*/
	NOTIMPLEMENTED, 		/*	SetProfile		*/
    	NOTIMPLEMENTED,			/*  	GetFMStation        	*/
    	NOTIMPLEMENTED,			/*  	SetFMStation        	*/
    	NOTIMPLEMENTED,			/*  	ClearFMStations       	*/
	NOTIMPLEMENTED,                 /*      GetNextFileFolder */
	NOTIMPLEMENTED,			/*	GetFolderListing	*/
	NOTSUPPORTED,			/*	GetNextRootFolder	*/
	NOTSUPPORTED,			/*	SetFileAttributes	*/
	NOTIMPLEMENTED,                 /*      GetFilePart */
	NOTIMPLEMENTED,                 /*      AddFilePart */
	NOTIMPLEMENTED,                 /*      SendFilePart */
	NOTIMPLEMENTED, 		/* 	GetFileSystemStatus	*/
	NOTIMPLEMENTED,                 /*      DeleteFile */
	NOTIMPLEMENTED,                 /*      AddFolder */
	NOTIMPLEMENTED,                 /*      DeleteFile */		/* 	DeleteFolder		*/
	NOTSUPPORTED,			/* 	GetGPRSAccessPoint	*/
	NOTSUPPORTED,			/* 	SetGPRSAccessPoint	*/
	S60_GetScreenshot,
	NOTSUPPORTED,			/* 	SetPower		*/
	NOTSUPPORTED,			/* 	PostConnect		*/
	NONEFUNCTION			/*	PreAPICall		*/
};
#endif


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