/*
** Copyright 2003 S. Varshavchik.
** See COPYING for distribution information.
*/

#ifndef	maildirkeywords_h
#define	maildirkeywords_h

#include	"config.h"
#include	<stdio.h>
#include	<string.h>

#ifdef  __cplusplus
extern "C" {
#endif


/*
** IMAP keywords.  This data structure is designed so that it is possible to:
** A) Find all messages that have the given keyword,
** B) Find all keywords that are set for the given message,
** C) Optimize memory usage, even with many keywords set for many msgs.
*/

/* A doubly-linked list of keywords */

struct libmail_keywordEntry {
	struct libmail_keywordEntry *prev, *next; /* Doubly-linked list */


#define keywordName(ke) ((char *)((ke)+1))

	union {
		void *userPtr; /* Misc ptr */
		unsigned long userNum; /* Or misc num */
	} u;

	struct libmail_kwMessageEntry *firstMsg, *lastMsg;
	/* Doubly-linked list of messages that use this keyword */
};


/* The main keyword hash table */

struct libmail_kwHashtable {

	struct libmail_keywordEntry heads[43], tails[43];
	/* Dummy head/tail nodes for each hash bucket */

	int keywordAddedRemoved;
};

struct libmail_kwMessageEntry {
	struct libmail_kwMessageEntry *next, *prev;
	/* Doubly-linked list of all keywords set for this message */

	struct libmail_kwMessageEntry *keywordNext, *keywordPrev;
	/* Doubly-linked list of all entries for the same keyword */

	struct libmail_keywordEntry *libmail_keywordEntryPtr; /* The keyword */
	struct libmail_kwMessage *libmail_kwMessagePtr; /* The message */
};

struct libmail_kwMessage {

	struct libmail_kwMessageEntry *firstEntry, *lastEntry;
	/* Doubly-linked list of all keywords set for this message */

	union {
		void *userPtr; /* Misc ptr */
		unsigned long userNum; /* Or misc num */
	} u;
};

/*
** Initialize a libmail_kwHashtable
*/
void libmail_kwhInit(struct libmail_kwHashtable *);


/*
** Returns 0 if libmail_kwHashtable is empty.  Return -1, errno=EIO if it's
** not (sanity check).
*/
int libmail_kwhCheck(struct libmail_kwHashtable *);

/*
** Enumerate all defined keywords.
*/
int libmail_kwEnumerate(struct libmail_kwHashtable *h,
			int (*callback_func)(struct libmail_keywordEntry *,
					     void *),
			void *callback_arg);

/*
** Find a keyword in the hashtable, optionally create it.  If createIfNew,
** then we MUST add the returned result to some keywordMessage, else there'll
** be a cleanup problem.
*/
struct libmail_keywordEntry *
libmail_kweFind(struct libmail_kwHashtable *ht,
			 const char *name,
			 int createIfNew);


extern const char *libmail_kwVerbotten;
/*
** Optional list of chars prohibited in keyword names.  They get automagically
** replaced with underscores.
*/
extern int libmail_kwCaseSensitive;
/*
** Non zero if keyword names are case sensitive.
*/

/*
** Clear a reference to a particular keyword, from a particular message.
*/
void libmail_kweClear(struct libmail_keywordEntry *);

/*
** Create an abstract message object, with no keywords currently defined.
*/
struct libmail_kwMessage *libmail_kwmCreate();

/*
** Destroy the message object, automatically removing any keywords that were
** used by the object.
*/
void libmail_kwmDestroy(struct libmail_kwMessage *);

/*
** Link a keyword to a message.
*/
int libmail_kwmSet(struct libmail_kwMessage *, struct libmail_keywordEntry *);

/*
** Link a keyword to a message, by keyword's name.
*/
int libmail_kwmSetName(struct libmail_kwHashtable *,
		       struct libmail_kwMessage *, const char *);

/*
** Compare two messages, return 0 if they have the same keywords.
*/
int libmail_kwmCmp(struct libmail_kwMessage *,
		   struct libmail_kwMessage *);

/*
** Clear a keyword from a message.
*/
int libmail_kwmClearName(struct libmail_kwMessage *, const char *);
/*
** libmail_kwmClearName is for INTERNAL USE ONLY -- the name doesn't get vetted
** by libmail_kweFind.
*/

/*
** Clear a keyword from a message, the public version.
*/
int libmail_kwmClear(struct libmail_kwMessage *, struct libmail_keywordEntry *);
/*
**
*/
int libmail_kwmClearEntry(struct libmail_kwMessageEntry *);


/*****************************************************************************

The above is low-level stuff.  And now, a high level maildir storage API:

*****************************************************************************/

/*
** Read keywords from a maildir.  The application presumably has read and
** compiled a list of messages it found in the maildir's cur (and new)
** directories.
**
** The function maildir_kwRead() will now read the keywords associated with
** each message.  How the application maintains the list of messages in the
** maildir is irrelevant.  The application simply needs to allocate a pointer
** to a libmail_kwMessage structure, one pointer for each message in the
** maildir.  Each pointer must be initialized to a NULL, and the application
** provides a set of callback functions, as defined below, that return
** a pointer to this pointer (pay attention now), given the filename.
** maildir_kwRead() invokes the callback functions, as appropriate, while
** it's doing its business.
**
** There's other callback functions too, so let's get to business.
** The application must initialize the following structure before calling
** maildir_kwRead().  This is where the pointers to all callback functions
** are provided:
*/

struct maildir_kwReadInfo {

	struct libmail_kwMessage **(*findMessageByFilename)(const char
							    *filename,
							    int autocreate,
							    size_t *indexNum,
							    void *voidarg);
	/*
	** Return a pointer to a libmail_kwMessage * that's associated with
	** the message whose name is 'filename'.  'filename' will not have
	** :2, and the message's flags, so the application needs to be
	** careful.
	**
	** All libmail_kwMessage *s are initially NULL.  If autocreate is not
	** zero, the application must use libmail_kwmCreate() to initialize
	** the pointer, before returning.  Otherwise, the application should
	** return a pointer to a NULL libmail_kwMessage *.
	**
	** The application may use libmail_kwMessage.u for its own purposes.
	**
	** The application should return NULL if it can't find 'filename'
	** in its list of messages in the maildir.  That is a defined
	** possibility, and occur in certain race conditions (which are
	** properly handled, of course).
	**
	** If indexNum is not NULL, the application should set *indexNum to
	** the found message's index (if the application does not return NULL).
	** All messages the application has must be consecutively numbered,
	** beginning with 0 and up to, but not including, whatever the
	** application returns in getMessageCount().
	*/

	size_t (*getMessageCount)(void *voidarg);
	/*
	** The application should return the number of messages it thinks
	** there are in the maildir.
	*/

	struct libmail_kwMessage **(*findMessageByIndex)(size_t indexNum,
							 int autocreate,
							 void *voidarg);
	/*
	** This one is like the findMessageByFilename() callback, except that
	** instead of filename the applicationg gets indexNum which ranges
	** between 0 and getMessageCount()-1.
	** The return code from this callback is identical to the return code
	** from findMessageByFilename(), and autocreate's semantics are also
	** the same.
	*/

	const char *(*getMessageFilename)(size_t n, void *voidarg);
	/*
	** The application should return the filename for message #n.  The
	** application may or may not include :2, in the returned ptr.
	*/

	struct libmail_kwHashtable * (*getKeywordHashtable)(void *voidarg);
	/*
	** The application should return the libmail_kwHashtable that it
	** allocated to store all the keyword stuff.  Read keywords are
	** allocated from this hashtable.
	*/

	void (*updateKeywords)(size_t n, struct libmail_kwMessage *kw,
			       void *voidarg);
	/*
	** The updateKeywords callback gets invoked by maildir_kwRead()
	** if it needs to throw out the list of keywords it already read for
	** a given message, and replace it, instead, with another set of
	** keywords.  This can happen in certain circumstances.
	**
	** The application should retrieve the libmail_kwMessage pointer for
	** message #n.  It may or may not be null.  If it's not null, the
	** application must use libmail_kwmDestroy().  Then, afterwards,
	** the application should save 'kw' as the new pointer.
	**
	** This callback is provided so that the application may save whatever
	** it wants to save in kw->u.userPtr or kw->u.userNum, because 'kw'
	** was created by libmail_kwRead(), and not one of the two findMessage
	** callbacks.
	*/

	void *voidarg;
	/*
	** All of the above callbacks receive this voidarg as their last
	** argument.
	*/

	int tryagain;
	/*
	** libmail_kwRead() returns 0 for success, or -1 for a system failure
	** (check errno).
	**
	** If libmail_kwRead() returned 0, the application MUST check
	** tryagain.
	**
	** If tryagain is not 0, the application MUST:
	**     A) Take any non-NULL libmail_kwMessage pointers that are
	**        associated with each message in the maildir, use
	**        libmail_kwmDestroy() to blow them away, and reset each
	**        pointer to NULL.
	**
	**     B) Invoke libmail_kwRead() again.
	**
	** A non-0 tryagain indicates a recoverable race condition.
	*/


	/* Everything below is internal */

	int updateNeeded;
	int errorOccured;
};


int maildir_kwRead(const char *maildir,
		   struct maildir_kwReadInfo *rki);

/*
** maildir_kwSave saves new keywords for a particular message:
*/

int maildir_kwSave(const char *maildir, /* The maildir */
		   const char *filename,
		    /* The message.  :2, is allowed, and ignored */

		   struct libmail_kwMessage *newKeyword,
		    /* New keywords.  The ptr may be NULL */

		   char **tmpname,
		   char **newname,

		   int tryAtomic);

int maildir_kwSaveArray(const char *maildir,
			const char *filename,
			const char **flags,
			char **tmpname,
			char **newname,
			int tryAtomic);

/*
** maildir_kwSave returns -1 for an error.  If it return 0, it will initialize
** *tmpname and *newname, both will be full path filenames.  The application
** needs to simply call rename() with both filenames, and free() them, to
** effect the change.  Example:
**
**  char *tmpname, *newname;
**
**  if (maildir_kwSave( ..., &tmpname, &newname) == 0)
**  {
**         rename(tmpname, newname);
**
**         free(tmpname);
**         free(newname);
**  }
**
**  Of course, rename()s return code should also be checked.
**
**  If 'tryAtomic' is non-zero, the semantics are going to be slightly
**  different.  tryAtomic is non-zero when we want to update keywords
**  atomically.  To do that, first, use maildir_kwRead()  (or, most likely
**  maildir_kwgReadMaildir) to read the existing keywords, update the keywords
**  for the particular message, use maildir_kwSave(), but instead of rename()
**  use link().  Whether link succeeds or not, use unlink(tmpname) in any
**  case.  If link() failed with EEXIST, we had a race condition, so try
**  again.
**  Note - in NFS environments, must check not only that links succeeds, but
**  if stat-ing the tmpfile the number of links also must be 2.
*/

/*
** Set libmail_kwEnabled to ZERO in order to silently disable all maildir
** keyword functionality.  It's optional in Courier-IMAP.  Setting this flag
** to zero disables all actual keyword read/write functions, however all the
** necessary data still gets created (namely the courierimapkeywords
** subdirectory.
*/

extern int libmail_kwEnabled;


/*
** The following functions are "semi-internal".
**
** maildir_kwExport() uses the same struct maildir_kwReadInfo, to "export"
** the list of keywords assigned to all messages into a file.
**
** maildir_kwImport() imports the saved keyword list.
**
** These functions are meant to save a "snapshot" of the keywords into a
** flag file, nothing else.
*/

int maildir_kwExport(FILE *, struct maildir_kwReadInfo *);
int maildir_kwImport(FILE *, struct maildir_kwReadInfo *);


/****************************************************************************

Generic maildir_kwRead implementation.

****************************************************************************/

struct libmail_kwGeneric {

	struct libmail_kwHashtable kwHashTable;

	size_t nMessages;

	struct libmail_kwGenericEntry **messages;
	int messagesValid;

	struct libmail_kwGenericEntry *messageHashTable[99];
};

struct libmail_kwGenericEntry {

	struct libmail_kwGenericEntry *next; /* On the hash table */

	char *filename;
	size_t messageNum;
	struct libmail_kwMessage *keywords;
};

void libmail_kwgInit(struct libmail_kwGeneric *g);
int libmail_kwgDestroy(struct libmail_kwGeneric *g);
int libmail_kwgReadMaildir(struct libmail_kwGeneric *g,
			   const char *maildir);

struct libmail_kwGenericEntry *
libmail_kwgFindByName(struct libmail_kwGeneric *g, const char *filename);

struct libmail_kwGenericEntry *
libmail_kwgFindByIndex(struct libmail_kwGeneric *g, size_t n);

#ifdef  __cplusplus
}

#include <set>
#include <string>

/* Some C++ wrappers */

namespace mail {
	namespace keywords {

		class Hashtable {

		public:
			struct libmail_kwHashtable kwh;

			Hashtable();
			~Hashtable();

			Hashtable(const Hashtable &); /* UNDEFINED */
			Hashtable &operator=(const Hashtable &);
			/* UNDEFINED */
		};


		class MessageBase {
		public:
			struct libmail_kwMessage *km;
			size_t refCnt;

			MessageBase();
			~MessageBase();

			MessageBase(const MessageBase &); /* UNDEFINED */
			MessageBase &operator=(const MessageBase *);
			/* UNDEFINED */
		};

		class Message {

			MessageBase *b;

			bool copyOnWrite();

		public:
			Message();
			~Message();

			Message(const Message &);
			Message &operator=(const Message &);

			void getFlags(std::set<std::string> &) const;
			/* Extract list of flags */

			bool setFlags(Hashtable &,
				      const std::set<std::string> &);
			/* Set the flags. */

			bool addFlag(Hashtable &, std::string);
			bool remFlag(std::string);

			bool empty() const {
				return b->km == NULL
					|| b->km->firstEntry == NULL;
			}

			bool operator==(struct libmail_kwMessage *m) const {
				return b->km == NULL ?
					m == NULL || m->firstEntry == NULL:
					m && libmail_kwmCmp(b->km, m) == 0;
			}

			bool operator !=(struct libmail_kwMessage *m) const {
				return ! operator==(m);
			}

			void replace(struct libmail_kwMessage *p)
				{
					if (b->km)
						libmail_kwmDestroy(b->km);
					b->km=p;
				}

		};
	}
}


/* BONUS: */

int maildir_kwSave(const char *maildir,
		   const char *filename,
		   std::set<std::string> keywords,

		   char **tmpname,
		   char **newname,

		   int tryAtomic);

#endif

#endif
