/*
 * Manage user preferences with a little help from the Citadel server.
 *
 * Copyright (c) 1996-2012 by the citadel.org team
 *
 * This program is open source software.  You can redistribute it and/or
 * modify it under the terms of the GNU General Public License, version 3.
 *
 * 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.
 */

#include "webcit.h"
#include "webserver.h"
#include "dav.h"

HashList *PreferenceHooks;
extern HashList *HandlerHash;

typedef struct _PrefDef {
	ePrefType eType;
	StrBuf *Setting;
	const char *PrefStr;
	PrefEvalFunc OnLoad;
	StrBuf *OnLoadName;
} PrefDef;

typedef struct _Preference {
	PrefDef *Type;
	ePrefType eFlatPrefType;

	StrBuf *Key;
	StrBuf *Val;

	long lval;
	long decoded;
	StrBuf *DeQPed;
} Preference;

void DestroyPrefDef(void *vPrefDef)
{
	PrefDef *Prefdef = (PrefDef*) vPrefDef;
	FreeStrBuf(&Prefdef->Setting);
	FreeStrBuf(&Prefdef->OnLoadName);
	free(Prefdef);
}

void DestroyPreference(void *vPref)
{
	Preference *Pref = (Preference*) vPref;
	FreeStrBuf(&Pref->Key);
	FreeStrBuf(&Pref->Val);
	FreeStrBuf(&Pref->DeQPed);
	free(Pref);

}

void _RegisterPreference(const char *Setting, long SettingLen, 
			 const char *PrefStr, 
			 ePrefType Type, 
			 PrefEvalFunc OnLoad, 
			 const char *OnLoadName)
{
	PrefDef *Newpref = (PrefDef*) malloc(sizeof(PrefDef));
	Newpref->Setting = NewStrBufPlain(Setting, SettingLen);
	Newpref->PrefStr = PrefStr;
	Newpref->eType = Type;
	Newpref->OnLoad = OnLoad;
	if (Newpref->OnLoad != NULL) {
		Newpref->OnLoadName = NewStrBufPlain(OnLoadName, -1);
	}
	else
		Newpref->OnLoadName = NULL;
	Put(PreferenceHooks, Setting, SettingLen, Newpref, DestroyPrefDef);
}

const char *PrefGetLocalStr(const char *Setting, long len)
{
	void *hash_value;
	if (GetHash(PreferenceHooks, Setting, len, &hash_value) != 0) {
		PrefDef *Newpref = (PrefDef*) hash_value;
		return _(Newpref->PrefStr);

	}
	return "";
}

#ifdef DBG_PREFS_HASH
inline const char *PrintPref(void *vPref)
{
	Preference *Pref = (Preference*) vPref;
	if (Pref->DeQPed != NULL)
		return ChrPtr(Pref->DeQPed);
	else 
		return ChrPtr(Pref->Val);
}
#endif

void GetPrefTypes(HashList *List)
{
	HashPos *It;
	long len;
	const char *Key;
	void *vSetting;
	void *vPrefDef;
	Preference *Pref;
	PrefDef *PrefType;

	It = GetNewHashPos(List, 0);
	while (GetNextHashPos(List, It, &len, &Key, &vSetting)) 
	{
		Pref = (Preference*) vSetting;
		if (GetHash(PreferenceHooks, SKEY(Pref->Key), &vPrefDef) && 
		    (vPrefDef != NULL)) 
		{
			PrefType = (PrefDef*) vPrefDef;
			Pref->Type = PrefType;
			Pref->eFlatPrefType = Pref->Type->eType;

			syslog(LOG_DEBUG, "Loading [%s]with type [%d] [\"%s\"]\n",
				ChrPtr(Pref->Key),
				Pref->Type->eType,
				ChrPtr(Pref->Val));

			switch (Pref->Type->eType)
			{
			case PRF_UNSET: /* WHUT? */
				break;
			case PRF_STRING:
				break;
			case PRF_INT:
				Pref->lval = StrTol(Pref->Val);
				Pref->decoded = 1;
				break;
			case PRF_QP_STRING:
				Pref->DeQPed = NewStrBufPlain(NULL, StrLength(Pref->Val));
				StrBufEUid_unescapize(Pref->DeQPed, Pref->Val);
				Pref->decoded = 1;
				break;
			case PRF_YESNO:
				Pref->lval = strcmp(ChrPtr(Pref->Val), "yes") == 0;
				Pref->decoded = 1;
				break;
			}

			if (PrefType->OnLoad != NULL){

				syslog(LOG_DEBUG, "Loading with: -> %s(\"%s\", %ld)\n",
					ChrPtr(PrefType->OnLoadName),
					ChrPtr(Pref->Val),
					Pref->lval);
				PrefType->OnLoad(Pref->Val, Pref->lval);
			}
		}
	}
	DeleteHashPos(&It);
}

void ParsePref(HashList **List, StrBuf *ReadBuf)
{
	int Done = 0;
	Preference *Data = NULL;
	Preference *LastData = NULL;
				
	while (!Done) {
		if (StrBuf_ServGetln(ReadBuf) < 0)
			break;
		if ( (StrLength(ReadBuf)==3) && 
		     !strcmp(ChrPtr(ReadBuf), "000")) {
			Done = 1;
			break;
		}

		if ((ChrPtr(ReadBuf)[0] == ' ') &&
		    (LastData != NULL)) {
			StrBufAppendBuf(LastData->Val, ReadBuf, 1);
		}
		else {
			LastData = Data = malloc(sizeof(Preference));
			memset(Data, 0, sizeof(Preference));
			Data->Key = NewStrBuf();
			Data->Val = NewStrBuf();
			StrBufExtract_token(Data->Key, ReadBuf, 0, '|');
			StrBufExtract_token(Data->Val, ReadBuf, 1, '|');

			/***************** BEGIN VILE SLEAZY HACK ************************/

			/* some users might still have this start page configured, which now breaks */

			if (	(!strcasecmp(ChrPtr(Data->Key), "startpage"))
				&& (!strcasecmp(ChrPtr(Data->Val), "/do_template?template=summary_page"))
			) {
				FreeStrBuf(&Data->Val);
				Data->Val = NewStrBufPlain(HKEY("/summary"));
			}

			/******************* END VILE SLEAZY HACK ************************/

			if (!IsEmptyStr(ChrPtr(Data->Key)))
			{
				Put(*List, 
				    SKEY(Data->Key),
				    Data, 
				    DestroyPreference);
			}
			else 
			{
				StrBufTrim(ReadBuf);
				syslog(LOG_INFO, "ignoring spurious preference line: [%s]\n", 
					ChrPtr(ReadBuf));
				DestroyPreference(Data);
				LastData = NULL;
			}
			Data = NULL;
		}
	}
	GetPrefTypes(*List);
}


/*
 * display preferences dialog
 */
void load_preferences(void) 
{
	folder Room;
	wcsession *WCC = WC;
	int Done = 0;
	StrBuf *ReadBuf;
	long msgnum = 0L;
	
	memset(&Room, 0, sizeof(folder));
	ReadBuf = NewStrBufPlain(NULL, SIZ * 4);
	if (goto_config_room(ReadBuf, &Room) != 0) {
		FreeStrBuf(&ReadBuf);
		FlushFolder(&Room);

		return;	/* oh well. */
	}

	serv_puts("MSGS ALL|0|1");
	StrBuf_ServGetln(ReadBuf);
	if (GetServerStatus(ReadBuf, NULL) == 8) {
		serv_puts("subj|__ WebCit Preferences __");
		serv_puts("000");
	}
	while (!Done && (StrBuf_ServGetln(ReadBuf) >= 0)) {
		if ( (StrLength(ReadBuf)==3) && !strcmp(ChrPtr(ReadBuf), "000")) {
			Done = 1;
			break;
		}
		msgnum = StrTol(ReadBuf);
	}

	if (msgnum > 0L) {
		serv_printf("MSG0 %ld", msgnum);
		StrBuf_ServGetln(ReadBuf);
		if (GetServerStatus(ReadBuf, NULL) == 1) {
			while (	(StrBuf_ServGetln(ReadBuf) >= 0)
				&& (strcmp(ChrPtr(ReadBuf), "text")
				&& strcmp(ChrPtr(ReadBuf), "000"))
			) {
				/* flush */
			}
			if (!strcmp(ChrPtr(ReadBuf), "text")) {
				ParsePref(&WCC->hash_prefs, ReadBuf);
			}
		}
	}

	/* Go back to the room we're supposed to be in */
	if (StrLength(WCC->CurRoom.name) > 0) {
		serv_printf("GOTO %s", ChrPtr(WCC->CurRoom.name));
		StrBuf_ServGetln(ReadBuf);
		GetServerStatus(ReadBuf, NULL);
	}
	FreeStrBuf(&ReadBuf);
	FlushFolder(&Room);
}


/*
 * Go to the user's configuration room, creating it if necessary.
 * returns 0 on success or nonzero upon failure.
 */
int goto_config_room(StrBuf *Buf, folder *Room) 
{
	serv_printf("GOTO %s", USERCONFIGROOM);
	StrBuf_ServGetln(Buf);
	if (GetServerStatus(Buf, NULL) != 2) {	/* try to create the config room if not there */
		serv_printf("CRE8 1|%s|4|0", USERCONFIGROOM);
		StrBuf_ServGetln(Buf);
		GetServerStatus(Buf, NULL);

		serv_printf("GOTO %s", USERCONFIGROOM);
		StrBuf_ServGetln(Buf);
		if (GetServerStatus(Buf, NULL) != 2) {
			return(1);
		}
	}
	ParseGoto(Room, Buf);
	return(0);
}

void WritePrefsToServer(HashList *Hash)
{
	wcsession *WCC = WC;
	long len;
	HashPos *HashPos;
	void *vPref;
	const char *Key;
	Preference *Pref;
	StrBuf *SubBuf = NULL;
	
	Hash = WCC->hash_prefs;
#ifdef DBG_PREFS_HASH
	dbg_PrintHash(Hash, PrintPref, NULL);
#endif
	HashPos = GetNewHashPos(Hash, 0);
	while (GetNextHashPos(Hash, HashPos, &len, &Key, &vPref)!=0)
	{
		size_t nchars;
		if (vPref == NULL)
			continue;
		Pref = (Preference*) vPref;
		nchars = StrLength(Pref->Val);
		if (nchars > 80){
			int n = 0;
			size_t offset, nchars;
			if (SubBuf == NULL)
				SubBuf = NewStrBufPlain(NULL, SIZ);
			nchars = 1;
			offset = 0;
			while (nchars > 0) {
				if (n == 0)
					nchars = 70;
				else 
					nchars = 80;
				
				nchars = StrBufSub(SubBuf, Pref->Val, offset, nchars);
				
				if (n == 0) {
					serv_printf("%s|%s", ChrPtr(Pref->Key), ChrPtr(SubBuf));
				}
				else {
					serv_printf(" %s", ChrPtr(SubBuf));
				}
				
				offset += nchars;
				nchars = StrLength(Pref->Val) - offset;
				n++;
			}
			
		}
		else {
			serv_printf("%s|%s", ChrPtr(Pref->Key), ChrPtr(Pref->Val));
		}
		
	}
	FreeStrBuf(&SubBuf);
	DeleteHashPos(&HashPos);
}

/*
 * save the modifications
 */
void save_preferences(void) 
{
	folder Room;
	wcsession *WCC = WC;
	int Done = 0;
	StrBuf *ReadBuf;
	long msgnum = 0L;
	
	ReadBuf = NewStrBuf();
	memset(&Room, 0, sizeof(folder));
	if (goto_config_room(ReadBuf, &Room) != 0) {
		FreeStrBuf(&ReadBuf);
		FlushFolder(&Room);

		return;	/* oh well. */
	}

	/* make shure the config room has the right type, else it might reject our config */
	if (Room.view != VIEW_BBS) {
		serv_printf("VIEW %d", VIEW_BBS);
		StrBuf_ServGetln(ReadBuf);
		if (GetServerStatus(ReadBuf, NULL) != 2) {
			/* UPS? */
		}
		else if (goto_config_room(ReadBuf, &Room) != 0) {
			FreeStrBuf(&ReadBuf);
			FlushFolder(&Room);
			
			return;	/* oh well. */
		}
	}

	serv_puts("MSGS ALL|0|1");
	StrBuf_ServGetln(ReadBuf);
	if (GetServerStatus(ReadBuf, NULL) == 8) {
		serv_puts("subj|__ WebCit Preferences __");
		serv_puts("000");
	}
	while (!Done && (StrBuf_ServGetln(ReadBuf) >= 0)) {
		if ( (StrLength(ReadBuf)==3) && !strcmp(ChrPtr(ReadBuf), "000")) {
			Done = 1;
			break;
		}
		msgnum = StrTol(ReadBuf);
	}

	if (msgnum > 0L) {
		serv_printf("DELE %ld", msgnum);
		StrBuf_ServGetln(ReadBuf);
		GetServerStatus(ReadBuf, NULL);
	}

	serv_printf("ENT0 1||0|1|__ WebCit Preferences __|");
	StrBuf_ServGetln(ReadBuf);
	if (GetServerStatus(ReadBuf, NULL) == 4) {

		WritePrefsToServer(WCC->hash_prefs);
		serv_puts("");
		serv_puts("000");
	}

	/** Go back to the room we're supposed to be in */
	if (StrLength(WCC->CurRoom.name) > 0) {
		serv_printf("GOTO %s", ChrPtr(WCC->CurRoom.name));
		StrBuf_ServGetln(ReadBuf);
		GetServerStatus(ReadBuf, NULL);
	}
	FreeStrBuf(&ReadBuf);
	FlushFolder(&Room);
}

/*
 * query the actual setting of key in the citadel database
 *
 * key		config key to query
 * keylen	length of the key string
 * value	StrBuf-value to the key to get
 * returns:	found?
 */
int get_pref_backend(const char *key, size_t keylen, Preference **Pref)
{
	void *hash_value = NULL;
#ifdef DBG_PREFS_HASH
	dbg_PrintHash(WC->hash_prefs, PrintPref, NULL);
#endif
	if (GetHash(WC->hash_prefs, key, keylen, &hash_value) == 0) {
		*Pref = NULL;
		return 0;
	}
	else {
		*Pref = (Preference*) hash_value;
		return 1;
	}
}

int get_PREFERENCE(const char *key, size_t keylen, StrBuf **value)
{
	Preference *Pref;
	int Ret;

	Ret = get_pref_backend(key, keylen, &Pref);
	if (Ret != 0)
		*value = Pref->Val;
	else
		*value = NULL;
	return Ret;
}

/*
 * Write a key into the webcit preferences database for this user
 *
 * key			key whichs value is to be modified
 * keylen		length of the key string
 * value		value to set
 * save_to_server	1 = flush all data to the server, 0 = cache it for now
 */
long compare_preference(const Preference *PrefA, const Preference *PrefB)
{
	ePrefType TypeA, TypeB;

	if (PrefA->Type != NULL) {
		TypeA = PrefA->Type->eType;
	}
	else {
		TypeA = PrefA->eFlatPrefType;
	}

	if (PrefB->Type != NULL) {
		TypeB = PrefB->Type->eType;
	}
	else {
		TypeB = PrefB->eFlatPrefType;
	}

	if (	(TypeA != PRF_UNSET)
		&& (TypeB != PRF_UNSET)
		&& (TypeA != TypeB)
	) {
		if (TypeA > TypeB) {
			return 1;
		}
		else {	/* (PrefA->Type < PrefB->Type) */
			return -1;
		}
	}

	if (TypeB == PRF_UNSET) {
		TypeA = PRF_UNSET;
	}
		    
	switch (TypeA)
	{
	default:
	case PRF_UNSET:
	case PRF_STRING:
		return strcmp(ChrPtr(PrefA->Val), ChrPtr(PrefB->Val));
	case PRF_YESNO:
	case PRF_INT:
		if (PrefA->lval == PrefB->lval)
			return 0;
		else if (PrefA->lval > PrefB->lval)
			return 1;
		else
			return -1;
	case PRF_QP_STRING:
		return strcmp(ChrPtr(PrefA->DeQPed), 
			      ChrPtr(PrefB->DeQPed));
	}
}


/*
 * Write a key into the webcit preferences database for this user
 *
 * key			key which value is to be modified
 * keylen		length of the key string
 * value		value to set
 * save_to_server	1 = flush all data to the server, 0 = cache it for now
 */
void set_preference_backend(const char *key, size_t keylen, 
			    long lvalue, 
			    StrBuf *value, 
			    long lPrefType,
			    int save_to_server, 
			    PrefDef *PrefType) 
{
	wcsession *WCC = WC;
	void *vPrefDef;
	void *vPrefB;
	Preference *Pref;

	Pref = (Preference*) malloc(sizeof(Preference));
	memset(Pref, 0, sizeof(Preference));
	Pref->Key = NewStrBufPlain(key, keylen);

	if ((PrefType == NULL) &&
	    GetHash(PreferenceHooks, SKEY(Pref->Key), &vPrefDef) && 
	    (vPrefDef != NULL))
		PrefType = (PrefDef*) vPrefDef;

	if (PrefType != NULL)
	{
		Pref->Type = PrefType;
		Pref->eFlatPrefType = PrefType->eType;
		if (Pref->Type->eType != lPrefType)
			syslog(LOG_WARNING, "warning: saving preference with wrong type [%s] %d != %ld \n",
				key, Pref->Type->eType, lPrefType);
		switch (Pref->Type->eType)
		{
		case PRF_UNSET: /* default to string... */
		case PRF_STRING:
			Pref->Val = value;
			Pref->decoded = 1;
			break;
		case PRF_INT:
			Pref->lval = lvalue;
			Pref->Val = value;
			if (Pref->Val == NULL)
				Pref->Val = NewStrBufPlain(NULL, 64);
			StrBufPrintf(Pref->Val, "%ld", lvalue);
			Pref->decoded = 1;
			break;
		case PRF_QP_STRING:
			Pref->DeQPed = value;
			Pref->Val = NewStrBufPlain(NULL, StrLength(Pref->DeQPed) * 3);
			StrBufEUid_escapize(Pref->Val, Pref->DeQPed);
			Pref->decoded = 1;
			break;
		case PRF_YESNO:
			Pref->lval = lvalue;
			if (lvalue) 
				Pref->Val = NewStrBufPlain(HKEY("yes"));
			else
				Pref->Val = NewStrBufPlain(HKEY("no"));
			Pref->decoded = 1;
			break;
		}
		if (Pref->Type->OnLoad != NULL)
			Pref->Type->OnLoad(Pref->Val, Pref->lval);
	}
	else {
		Pref->eFlatPrefType = lPrefType;
		switch (lPrefType)
		{
		case PRF_STRING:
			Pref->Val = value;
			Pref->decoded = 1;
			break;
		case PRF_INT:
			Pref->lval = lvalue;
			Pref->Val = value;
			if (Pref->Val == NULL)
				Pref->Val = NewStrBufPlain(NULL, 64);
			StrBufPrintf(Pref->Val, "%ld", lvalue);
			Pref->decoded = 1;
			break;
		case PRF_QP_STRING:
			Pref->DeQPed = value;
			Pref->Val = NewStrBufPlain(NULL, StrLength(Pref->DeQPed) * 3);
			StrBufEUid_escapize(Pref->Val, Pref->DeQPed);
			Pref->decoded = 1;
			break;
		case PRF_YESNO:
			Pref->lval = lvalue;
			if (lvalue) 
				Pref->Val = NewStrBufPlain(HKEY("yes"));
			else
				Pref->Val = NewStrBufPlain(HKEY("no"));
			Pref->decoded = 1;
			break;
		}
	}

	if ((save_to_server != 0) && 
	    GetHash(WCC->hash_prefs, key, keylen, &vPrefB) && 
	    (vPrefB != NULL) && 
	    (compare_preference (Pref, vPrefB) == 0))
		save_to_server = 0;

	Put(WCC->hash_prefs, key, keylen, Pref, DestroyPreference);
	
	if (save_to_server) WCC->SavePrefsToServer = 1;
}

void set_PREFERENCE(const char *key, size_t keylen, StrBuf *value, int save_to_server) 
{
	set_preference_backend(key, keylen, 0, value, PRF_STRING, save_to_server, NULL);
}

int get_PREF_LONG(const char *key, size_t keylen, long *value, long Default)
{
	Preference *Pref;
	int Ret;

	Ret = get_pref_backend(key, keylen, &Pref);
	if (Ret == 0) {
		*value = Default;
		return 0;
	}

	if (Pref->decoded)
		*value = Pref->lval;
	else {
		*value = Pref->lval = atol(ChrPtr(Pref->Val));
		Pref->decoded = 1;
	}
	return Ret;
}


void set_PREF_LONG(const char *key, size_t keylen, long value, int save_to_server)
{
	set_preference_backend(key, keylen, value, NULL, PRF_INT, save_to_server, NULL);
}

int get_PREF_YESNO(const char *key, size_t keylen, int *value, int Default)
{
	Preference *Pref;
	int Ret;

	Ret = get_pref_backend(key, keylen, &Pref);
	if (Ret == 0) {
		*value = Default;
		return 0;
	}

	if (Pref->decoded)
		*value = Pref->lval;
	else {
		*value = Pref->lval = strcmp(ChrPtr(Pref->Val), "yes") == 0;
		Pref->decoded = 1;
	}
	return Ret;
}

void set_PREF_YESNO(const char *key, size_t keylen, long value, int save_to_server)
{
	set_preference_backend(key, keylen, value, NULL, PRF_YESNO, save_to_server, NULL);
}

int get_room_prefs_backend(const char *key, size_t keylen, 
			   Preference **Pref)
{
	StrBuf *pref_name;
	int Ret;

	pref_name = NewStrBufPlain (HKEY("ROOM:"));
	StrBufAppendBuf(pref_name, WC->CurRoom.name, 0);
	StrBufAppendBufPlain(pref_name, HKEY(":"), 0);
	StrBufAppendBufPlain(pref_name, key, keylen, 0);
	Ret = get_pref_backend(SKEY(pref_name), Pref);
	FreeStrBuf(&pref_name);

	return Ret;
}

const StrBuf *get_X_PREFS(const char *key, size_t keylen, 
			  const char *xkey, size_t xkeylen)
{
	int ret;
	StrBuf *pref_name;
	Preference *Prf;
	
	pref_name = NewStrBufPlain (HKEY("XPREF:"));
	StrBufAppendBufPlain(pref_name, xkey, xkeylen, 0);
	StrBufAppendBufPlain(pref_name, HKEY(":"), 0);
	StrBufAppendBufPlain(pref_name, key, keylen, 0);

	ret = get_pref_backend(SKEY(pref_name), &Prf);
	FreeStrBuf(&pref_name);

	if (ret)
		return Prf->Val;
	else return NULL;
}

void set_X_PREFS(const char *key, size_t keylen, const char *xkey, size_t xkeylen, StrBuf *value, int save_to_server)
{
	StrBuf *pref_name;
	
	pref_name = NewStrBufPlain (HKEY("XPREF:"));
	StrBufAppendBufPlain(pref_name, xkey, xkeylen, 0);
	StrBufAppendBufPlain(pref_name, HKEY(":"), 0);
	StrBufAppendBufPlain(pref_name, key, keylen, 0);

	set_preference_backend(SKEY(pref_name), 0, value, PRF_STRING, save_to_server, NULL);
	FreeStrBuf(&pref_name);
}


long get_ROOM_PREFS_LONG(const char *key, size_t keylen, long *value, long Default)
{
	Preference *Pref;
	int Ret;

	Ret = get_room_prefs_backend(key, keylen, &Pref);

	if (Ret == 0) {
		*value = Default;
		return 0;
	}

	if (Pref->decoded)
		*value = Pref->lval;
	else {
		*value = Pref->lval = atol(ChrPtr(Pref->Val));
		Pref->decoded = 1;
	}
	return Ret;
}


StrBuf *get_ROOM_PREFS(const char *key, size_t keylen)
{
	Preference *Pref;
	int Ret;

	Ret = get_room_prefs_backend(key, keylen, &Pref);

	if (Ret == 0) {
		return NULL;
	}
	else 
		return Pref->Val;
}

void set_ROOM_PREFS(const char *key, size_t keylen, StrBuf *value, int save_to_server)
{
	StrBuf *pref_name;
	
	pref_name = NewStrBufPlain (HKEY("ROOM:"));
	StrBufAppendBuf(pref_name, WC->CurRoom.name, 0);
	StrBufAppendBufPlain(pref_name, HKEY(":"), 0);
	StrBufAppendBufPlain(pref_name, key, keylen, 0);
	set_preference_backend(SKEY(pref_name), 0, value, PRF_STRING, save_to_server, NULL);
	FreeStrBuf(&pref_name);
}


void GetPreferences(HashList *Setting)
{
        wcsession *WCC = WC;
	HashPos *It;
	long len;
	const char *Key;
	void *vSetting;
	PrefDef *PrefType;
	StrBuf *Buf;
	long lval;
	HashList *Tmp;

	Tmp = WCC->hash_prefs;
	WCC->hash_prefs = Setting;

	It = GetNewHashPos(PreferenceHooks, 0);
	while (GetNextHashPos(PreferenceHooks, It, &len, &Key, &vSetting)) {
		PrefType = (PrefDef*) vSetting;

		if (!HaveBstr(SKEY(PrefType->Setting)))
			continue;
		switch (PrefType->eType) {
		case PRF_UNSET:
		case PRF_STRING:
			Buf = NewStrBufDup(SBstr(SKEY(PrefType->Setting)));
			set_preference_backend(SKEY(PrefType->Setting),
					       0, 
					       Buf, 
					       PRF_STRING,
					       1, 
					       PrefType);
			break;
		case PRF_INT:
			lval = LBstr(SKEY(PrefType->Setting));
			set_preference_backend(SKEY(PrefType->Setting),
					       lval, 
					       NULL, 
					       PRF_INT,
					       1, 
					       PrefType);
			break;
		case PRF_QP_STRING:
			Buf = NewStrBufDup(SBstr(SKEY(PrefType->Setting)));
			set_preference_backend(SKEY(PrefType->Setting),
					       0, 
					       Buf, 
					       PRF_QP_STRING,
					       1, 
					       PrefType);
			break;
		case PRF_YESNO:
			lval = YesBstr(SKEY(PrefType->Setting));
			set_preference_backend(SKEY(PrefType->Setting),
					       lval, 
					       NULL, 
					       PRF_YESNO,
					       1, 
					       PrefType);
			break;
		}
	}
	WCC->hash_prefs = Tmp;
	DeleteHashPos(&It);
}


/*
 * Commit new preferences and settings
 */
void set_preferences(void)
{	
	if (!havebstr("change_button")) {
		AppendImportantMessage(_("Cancelled.  No settings were changed."), -1);
		display_main_menu();
		return;
	}
	GetPreferences(WC->hash_prefs);
	display_main_menu();
}


void tmplput_CFG_Value(StrBuf *Target, WCTemplputParams *TP)
{
	Preference *Pref;
	if (get_pref_backend(TKEY(0), &Pref))
	{
		if (Pref->Type == NULL) {
			StrBufAppendTemplate(Target, TP, Pref->Val, 1);
		}
		switch (Pref->Type->eType)
		{
		case PRF_UNSET: /* default to string... */
		case PRF_STRING:
			StrBufAppendTemplate(Target, TP, Pref->Val, 1);
			break;
		case PRF_INT:
			if (Pref->decoded != 1) {
				if (Pref->Val == NULL)
					Pref->Val = NewStrBufPlain(NULL, 64);
				StrBufPrintf(Pref->Val, "%ld", Pref->lval);
				Pref->decoded = 1;
			}
			StrBufAppendTemplate(Target, TP, Pref->Val, 1);
			break;
		case PRF_QP_STRING:
			if (Pref->decoded != 1) {
				if (Pref->DeQPed == NULL)
					Pref->DeQPed = NewStrBufPlain(NULL, StrLength(Pref->Val));
					
				StrBufEUid_unescapize(Pref->DeQPed, Pref->Val);
				Pref->decoded = 1;
			}
			StrBufAppendTemplate(Target, TP, Pref->DeQPed, 1);
			break;
		case PRF_YESNO:
			if (Pref->decoded != 1) {
				Pref->lval = strcmp(ChrPtr(Pref->Val), "yes") == 0;
				Pref->decoded = 1;
			}
			StrBufAppendTemplate(Target, TP, Pref->Val, 1);
			break;
		}
	}
}

void tmplput_CFG_Descr(StrBuf *Target, WCTemplputParams *TP)
{
	const char *SettingStr;
	SettingStr = PrefGetLocalStr(TKEY(0));
	if (SettingStr != NULL) 
		StrBufAppendBufPlain(Target, SettingStr, -1, 0);
}
void tmplput_CFG_RoomValueLong(StrBuf *Target, WCTemplputParams *TP)
{
	long lvalue;
	long defval = 0;

	if (HAVE_PARAM(1))
		defval = GetTemplateTokenNumber(Target, TP, 1, 0);
	get_ROOM_PREFS_LONG(TKEY(0), &lvalue, defval);
	StrBufAppendPrintf(Target, "%ld", lvalue);
}
void tmplput_CFG_RoomValue(StrBuf *Target, WCTemplputParams *TP)
{
	StrBuf *pref = get_ROOM_PREFS(TKEY(0));
	if (pref != NULL) 
		StrBufAppendBuf(Target, pref, 0);
}
int ConditionalHasRoomPreference(StrBuf *Target, WCTemplputParams *TP) 
{
	if (get_ROOM_PREFS(TP->Tokens->Params[0]->Start, 
			   TP->Tokens->Params[0]->len) != NULL) 
		return 1;
  
	return 0;
}

int ConditionalPreference(StrBuf *Target, WCTemplputParams *TP)
{
	StrBuf *Pref;

	if (!get_PREFERENCE(TKEY(2), &Pref)) 
		return 0;
	
	if (!HAVE_PARAM(3)) {
		return 1;
	}
	else if (IS_NUMBER(TP->Tokens->Params[3]->Type))
	{
		return StrTol(Pref) == GetTemplateTokenNumber (Target, TP, 3, 0);
	}
	else 
	{
		const char *pch;
		long len;
		
		GetTemplateTokenString(Target, TP, 3, &pch, &len);
		
		return ((len == StrLength(Pref)) &&
			(strcmp(pch, ChrPtr(Pref)) == 0));
	}
}

int ConditionalHasPreference(StrBuf *Target, WCTemplputParams *TP)
{
	StrBuf *Pref;

	if (!get_PREFERENCE(TKEY(2), &Pref) || 
	    (Pref == NULL)) 
		return 0;
	else 
		return 1;
}


/********************************************************************************
 *                 preferences stored discrete in citserver
 ********************************************************************************/
CtxType CTX_VEA = CTX_NONE;
typedef struct __ValidEmailAddress {
	StrBuf *Address;
	int IsDefault;
}ValidEmailAddress;

void DeleteValidEmailAddress(void *v)
{
	ValidEmailAddress *VEA = (ValidEmailAddress*)v;
	FreeStrBuf(&VEA->Address);
	free(VEA);
}
void tmplput_VEA(StrBuf *Target, WCTemplputParams *TP)
{
	ValidEmailAddress* VEA = (ValidEmailAddress*) CTX((CTX_VEA));
	StrBufAppendTemplate(Target, TP, VEA->Address, 0);
}
int ConditionalPreferenceIsDefaulVEA(StrBuf *Target, WCTemplputParams *TP)
{
	ValidEmailAddress* VEA = (ValidEmailAddress*) CTX((CTX_VEA));
	return VEA->IsDefault;
}
HashList *GetGVEAHash(StrBuf *Target, WCTemplputParams *TP)
{
	StrBuf *Rcp;
	HashList *List = NULL;
	int Done = 0;
	int i, n = 1;
	char N[64];
	StrBuf *DefaultFrom = NULL;
	const StrBuf *EnvelopeTo;
	ValidEmailAddress *VEA;

	get_preference("defaultfrom", &DefaultFrom);
	EnvelopeTo = sbstr("nvto");
	Rcp = NewStrBuf();
	serv_puts("GVEA");
	StrBuf_ServGetln(Rcp);
	if (GetServerStatus(Rcp, NULL) == 1) {
		FlushStrBuf(Rcp);
		List = NewHash(1, NULL);
		while (!Done && (StrBuf_ServGetln(Rcp)>=0)) {
			if ( (StrLength(Rcp)==3) && 
			     !strcmp(ChrPtr(Rcp), "000")) 
			{
				Done = 1;
			}
			else {
				VEA = (ValidEmailAddress*) malloc(sizeof(ValidEmailAddress));
				i = snprintf(N, sizeof(N), "%d", n);
				StrBufTrim(Rcp);
				VEA->Address = Rcp;
				if (StrLength(EnvelopeTo) > 0)
					VEA->IsDefault = strstr(ChrPtr(EnvelopeTo), ChrPtr(Rcp)) != NULL;
				else if (StrLength(DefaultFrom) > 0)
					VEA->IsDefault = !strcmp(ChrPtr(Rcp), ChrPtr(DefaultFrom));
				else
					VEA->IsDefault = 0;

				Put(List, N, i, VEA, DeleteValidEmailAddress);
				Rcp = NewStrBuf();
			}
			n++;
		}
	}
	FreeStrBuf(&Rcp);
	return List;
}
void DeleteGVEAHash(HashList **KillMe)
{
	DeleteHash(KillMe);
}

HashList *GetGVSNHash(StrBuf *Target, WCTemplputParams *TP)
{
	StrBuf *Rcp;
	HashList *List = NULL;
	int Done = 0;
	int i, n = 1;
	char N[64];

	Rcp = NewStrBuf();
	serv_puts("GVSN");
	StrBuf_ServGetln(Rcp);
	if (GetServerStatus(Rcp, NULL) == 1) {
		FlushStrBuf(Rcp);
		List = NewHash(1, NULL);
		while (!Done && (StrBuf_ServGetln(Rcp)>=0)) {
			if ( (StrLength(Rcp)==3) && 
			     !strcmp(ChrPtr(Rcp), "000")) 
			{
				Done = 1;
			}
			else {
				i = snprintf(N, sizeof(N), "%d", n);
				StrBufTrim(Rcp);
				Put(List, N, i, Rcp, HFreeStrBuf);
				Rcp = NewStrBuf();
			}
			n++;
		}
	}
	FreeStrBuf(&Rcp);
	return List;
}
void DeleteGVSNHash(HashList **KillMe)
{
	DeleteHash(KillMe);
}




/*
 * Offer to make any page the user's "start page" (only if logged in)
 */
void offer_start_page(StrBuf *Target, WCTemplputParams *TP)
{
	if (WC->logged_in) {
		wc_printf("<a href=\"change_start_page?startpage=");
		urlescputs(ChrPtr(WC->Hdr->this_page));
		wc_printf("\">");
		wc_printf(_("Make this my start page"));
		wc_printf("</a>");
	};
}


/*
 * Change the user's start page
 */
void change_start_page(void) 
{
	const char *pch;
	void *vHandler;
	int ProhibitSave = 0;
	const StrBuf *pStartPage = sbstr("startpage");

	if (pStartPage != NULL) {
		pch = strchr(ChrPtr(pStartPage), '?');

		if ((pch != NULL) && (
			    GetHash(HandlerHash, ChrPtr(pStartPage), pch - ChrPtr(pStartPage), &vHandler), 
			    (vHandler != NULL) &&
			    ((((WebcitHandler*)vHandler)->Flags & PROHIBIT_STARTPAGE) != 0)))
		{ /* OK, This handler doesn't want to be set as start page, prune it. */
			ProhibitSave = 1;
		}
	}

	if ((pStartPage == NULL) || 
	    (ProhibitSave == 1))
	{
		set_preference_backend(HKEY("startpage"), 
				       0, 
				       NewStrBufPlain(HKEY("")),
				       PRF_STRING,
				       1, 
				       NULL);
		if (ProhibitSave == 1)
			AppendImportantMessage(_("This isn't allowed to become the start page."), -1);
		else
			AppendImportantMessage(_("You no longer have a start page selected."), -1);
		display_main_menu();
		return;
	}



	set_preference_backend(HKEY("startpage"), 
			       0, 
			       NewStrBufDup(pStartPage),
			       PRF_STRING,
			       1, 
			       NULL);

	output_headers(1, 1, 0, 0, 0, 0);
	do_template("newstartpage");
	wDumpContent(1);
}


void LoadStartpage(StrBuf *URL, long lvalue)
{
	const char *pch;
	void *vHandler;
	pch = strchr(ChrPtr(URL), '?');
	if (pch == NULL) {
		/* purge the sins of the past... */
		pch = strchr(ChrPtr(URL), '&');
		if (pch != NULL) {
			StrBufPeek(URL, pch, -1, '?');
			WC->SavePrefsToServer = 1;
		}
	}
	else if (GetHash(HandlerHash, ChrPtr(URL), pch - ChrPtr(URL), &vHandler), 
		 (vHandler != NULL) &&
		 ((((WebcitHandler*)vHandler)->Flags & PROHIBIT_STARTPAGE) != 0))
	{ /* OK, This handler doesn't want to be set as start page, prune it. */
		FlushStrBuf(URL);
		WC->SavePrefsToServer = 1;
	}
}


void 
InitModule_PREFERENCES
(void)
{
	RegisterCTX(CTX_VEA);

	WebcitAddUrlHandler(HKEY("set_preferences"), "", 0, set_preferences, 0);
	WebcitAddUrlHandler(HKEY("change_start_page"), "", 0, change_start_page, 0);

	RegisterPreference("startpage", _("Prefered startpage"), PRF_STRING, LoadStartpage);

	RegisterNamespace("OFFERSTARTPAGE", 0, 0, offer_start_page, NULL, CTX_NONE);
	RegisterNamespace("PREF:ROOM:VALUE", 1, 2, tmplput_CFG_RoomValue,  NULL, CTX_NONE);
	RegisterNamespace("PREF:ROOM:VALUE:INT", 1, 2, tmplput_CFG_RoomValueLong,  NULL, CTX_NONE);
	RegisterNamespace("PREF:VALUE", 1, 2, tmplput_CFG_Value, NULL, CTX_NONE);
	
	RegisterNamespace("PREF:DESCR", 1, 1, tmplput_CFG_Descr, NULL, CTX_NONE);

	RegisterConditional("COND:PREF", 4, ConditionalPreference, CTX_NONE);
	RegisterConditional("COND:PREF:SET", 4, ConditionalHasPreference, CTX_NONE);
	RegisterConditional("COND:ROOM:SET", 4, ConditionalHasRoomPreference, CTX_NONE);
	
	RegisterIterator("PREF:VALID:EMAIL:ADDR", 0, NULL, 
			 GetGVEAHash, NULL, DeleteGVEAHash, CTX_VEA, CTX_NONE, IT_NOFLAG);
	RegisterNamespace("PREF:VALID:EMAIL:ADDR:STR", 1, 1, tmplput_VEA, NULL, CTX_VEA);
	RegisterConditional("COND:PREF:VALID:EMAIL:ADDR:STR", 4, ConditionalPreferenceIsDefaulVEA, CTX_VEA);

	RegisterIterator("PREF:VALID:EMAIL:NAME", 0, NULL, 
			 GetGVSNHash, NULL, DeleteGVSNHash, CTX_STRBUF, CTX_NONE, IT_NOFLAG);

}


void 
ServerStartModule_PREFERENCES
(void)
{
	PreferenceHooks = NewHash(1, NULL);
}



void 
ServerShutdownModule_PREFERENCES
(void)
{
	DeleteHash(&PreferenceHooks);
}

void
SessionDetachModule__PREFERENCES
(wcsession *sess)
{
	if (sess->SavePrefsToServer) {
		save_preferences();
		sess->SavePrefsToServer = 0;
	}
}

void
SessionNewModule_PREFERENCES
(wcsession *sess)
{
	sess->hash_prefs = NewHash(1,NULL);
}

void 
SessionDestroyModule_PREFERENCES
(wcsession *sess)
{
	DeleteHash(&sess->hash_prefs);
}
