#ifndef SYNCENGINE_H
#define SYNCENGINE_H

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <pthread.h>

// The plugins must conform to this API version to work
#define MULTISYNC_API_VER 3

#define SYNC_GCONF_PATH "/apps/multisync/main"

typedef enum {
  SYNC_OBJECT_TYPE_UNKNOWN = 0x00,
  SYNC_OBJECT_TYPE_CALENDAR = 0x01,
  SYNC_OBJECT_TYPE_PHONEBOOK = 0x02,
  SYNC_OBJECT_TYPE_TODO = 0x04,
  SYNC_OBJECT_TYPE_ANY = 0xff
} sync_object_type;


typedef enum {
  SYNC_RECUR_NONE = 0x00,
  SYNC_RECUR_LUID = 0x01,
  SYNC_RECUR_UID = 0x02
} sync_recur_type;

typedef struct {
  char *uid;
  char *luid;
  char *enddate;
  sync_object_type object_type;
} idpair;

typedef enum {
  SYNC_MSG_NOMSG = 0,
  SYNC_MSG_OBJECTCHANGE = 1,
  SYNC_MSG_REQDONE = 2,
  SYNC_MSG_SLOWSYNC = 3,
  SYNC_MSG_TRUE = 4,
  SYNC_MSG_FALSE = 5,
  SYNC_MSG_FORCESYNC = 6,
  SYNC_MSG_QUIT = 7,
  SYNC_MSG_GET_LOCAL_CHANGES = 8,
  SYNC_MSG_GET_REMOTE_CHANGES = 9,
  SYNC_MSG_LOCAL_MODIFY = 10,
  SYNC_MSG_REMOTE_MODIFY = 11,
  SYNC_MSG_LOCAL_SYNCDONE = 12,
  SYNC_MSG_REMOTE_SYNCDONE = 13,
  SYNC_MSG_FORCERESYNC = 14,
  SYNC_MSG_SETTINGSCHANGED = 15,
  SYNC_MSG_REQFAILED = -1,
  SYNC_MSG_CONNECTIONERROR = -2,
  SYNC_MSG_MODIFYERROR = -3,
  SYNC_MSG_PROTOCOLERROR = -4,
  SYNC_MSG_NORIGHTSERROR = -5,
  SYNC_MSG_DONTEXISTERROR = -6,
  SYNC_MSG_DATABASEFULLERROR = -7,
  SYNC_MSG_USERDEFERRED = -8
} sync_msg_type;
  
typedef struct {
  GList *msg_queue;
  int msg_status;
  GCond *msg_signal;
  GMutex *msg_mutex;
} sync_msg_port;

typedef struct {
  sync_msg_type type;
  gpointer data;
} sync_msg;


#define DATADIR ".multisync"

#define SYNC_OBJ_MODIFIED 1
#define SYNC_OBJ_ADDED 2
#define SYNC_OBJ_SOFTDELETED 3
#define SYNC_OBJ_HARDDELETED 4
#define SYNC_OBJ_RECUR 5
// Used if the object is caught in a filter
#define SYNC_OBJ_FILTERED 6

typedef enum {
  SYNC_DIR_LOCALTOREMOTE = 1,
  SYNC_DIR_REMOTETOLOCAL = 2
} sync_direction;

typedef enum {
  SYNC_RULE_ALL = 1,
  SYNC_RULE_ONLYIF = 2,
  SYNC_RULE_ALLBUT = 3,
  SYNC_RULE_NONE = 4
} sync_rule;

typedef enum {
  SYNC_DUPLICATE_ACTION_NONE = 0,
  SYNC_DUPLICATE_ACTION_KEEPBOTH = 1,
  SYNC_DUPLICATE_ACTION_KEEPFIRST = 2,
  SYNC_DUPLICATE_ACTION_KEEPSECOND = 3
} sync_duplicate_action;

// A filtering rule for synchronization
typedef struct {
  sync_object_type type;
  sync_direction dir;
  sync_rule rule;
  char *field;
  char *data;
} sync_filter;

typedef struct {
  char *comp; // The data associated to this object type:
  // This data should be freeable using g_free()
  char *uid;
  char *removepriority; // Word to sort on for removal when database is full,
  // usually the date when the entry ends.
  int change_type;
  sync_object_type object_type;
} changed_object;

typedef struct { // Extra information stored internally when processing changes.
  sync_recur_type recur;
  char *origuid;
} change_list_extra;

// A call to syncobj_modify_list() must return a list of 
// syncobj_modify_result's. The list will be freed by the sync engine. 
// The list order is the same as the list of changed_object's sent to 
// syncobj_modify_list().
typedef struct {
  sync_msg_type result; // The result of the operation
  char *returnuid; // Return the UID for e.g. a new entry (may be NULL if N/A)
                   // as a gmalloc'ed string.
} syncobj_modify_result;

typedef struct {
  GList *changes; // List of changed_object's
  sync_object_type newdbs; // Set the bit for the corresponding type
  // if the database is not recognized or has been reset.
} change_info;

typedef struct {
  gpointer plugin;
  char *longname;
  char *shortname;
} sync_plugin;

typedef enum {
  SYNC_LOG_SUCCESS = 0,
  SYNC_LOG_ERROR = 1
} sync_log_type;

typedef struct {
  time_t timestamp;
  char *logstring;
  int repeat;
  sync_log_type type;
} sync_pair_log;

typedef struct {
  sync_plugin *localclient;
  sync_plugin *remoteclient;
  char *localname;
  char *remotename;
  char *datapath;
  int pairno;
  int reconninterval;
  int dwelltime;
  int syncinterval;
  gboolean manualonly;
  sync_object_type object_types;
  GtkWidget *optionwindow;
  gpointer original;
  pthread_t syncthread;
  gboolean thread_running;
  GList *iddb;
  sync_msg_port msg_objchange;
  sync_msg_port msg_callret;
  int syncmissed;
  gboolean (*callback)(gpointer);
  gpointer callbackdata;
  GList *log; // List of log messages
  char *errorstr; // Error string from failed plugin calls
  char *displayname;
  char *syncsound;
  char *welcomesound;
  gboolean playsyncsound;
  gboolean playwelcomesound;
  GList *filters; // Filters for selecting which entries to let through
  char *status;
  sync_duplicate_action default_duplicate_action; // User has checked default duplicate action
  sync_duplicate_action duplicate_mode; // The saved duplicate mode
} sync_pair;

typedef struct  {
  int calendarrecords;
  int maxcalendarrecords;
  int todorecords;
  int maxtodorecords;
  int phonebookrecords;
  int maxphonebookrecords;
  gboolean fake_recurring;
  gboolean managedbsize;
  sync_object_type object_types;
  gboolean is_feedthrough;
} client_connection;

typedef enum {
  CONNECTION_TYPE_LOCAL,
  CONNECTION_TYPE_REMOTE
} connection_type;

gpointer sync_main(gpointer data);
char *sync_error_string(sync_msg_type err);
GList* sync_load_ids(sync_pair *thissync);
void sync_save_ids(GList* list, sync_pair *thissync);

void sync_log(sync_pair *pair, char* logstring, sync_log_type type);
char* sync_get_luid(char *uid, sync_object_type objtype, sync_pair *handle);
GList *sync_get_recur_luids(char *uid, sync_object_type objtype, 
			    sync_pair *handle);
GList *sync_get_recur_uids(char *luid,sync_object_type objtype,
			   sync_pair *handle);
char* sync_get_uid(char *luid, sync_object_type objtype, sync_pair *handle);
void sync_insert_idpair(char *uid, char *luid, sync_object_type objtype, 
			char *removepriority, sync_recur_type recur, 
			sync_pair *handle);
void sync_delete_idpair(char *uid, char *luid, sync_object_type objtype, 
			sync_pair *handle);
void sync_object_changed(sync_pair *thissync);
char* sync_get_oldest_luid(sync_object_type objtype, GList *changelist,
			   sync_pair *handle);
char* sync_get_oldest_uid(sync_object_type objtype, GList *changelist,
			  sync_pair *handle);
gboolean sync_is_luid_fake_recur(char *luid, sync_object_type objtype, 
				 sync_pair *handle);
GList* sync_adjust_capacity(GList *change_list, GList **extra,
			    int fromlocal, client_connection *otherconn,
			    sync_plugin* otherplugin,
			    sync_object_type objtype,
			    sync_pair *thissync);

void sync_object_changed(sync_pair *thissync);
void sync_set_requestdone(sync_pair *thissync);
gpointer sync_set_requestfailed(sync_pair *thissync);
gpointer sync_set_requestfailederror(char* errorstr, sync_pair *thissync);
void sync_set_requestdata(gpointer data,sync_pair *thissync);
void sync_set_requestmsg(sync_msg_type type,sync_pair *thissync);
void sync_set_requestmsgerror(sync_msg_type type, char *errorstr, sync_pair *thissync);
void sync_set_requestdatamsg(gpointer data, sync_msg_type type,sync_pair *thissync);
void sync_feedthrough_get_changes(sync_pair *thissync, connection_type type,
				  sync_object_type newdbs);
void sync_feedthrough_modify(sync_pair *thissync, connection_type type,
			     GList *modify);
void sync_feedthrough_syncdone(sync_pair *thissync, connection_type type,
			       gboolean success);
sync_pair *sync_init(sync_pair *pair);
void sync_msg_init(sync_msg_port *msg);
int sync_wait_msg(sync_msg_port *msg, GTimeVal *untiltime);
int sync_wait_msg_data(sync_msg_port *port, GTimeVal *untiltime, 
		       gpointer *data);
void sync_send_msg(sync_msg_port *msg, int type);
void sync_send_msg_once(sync_msg_port *port, int type);
void sync_send_msg_data(sync_msg_port *port, int type, gpointer data);
void sync_free_changes(GList *changes);
void sync_free_modify_results(GList *results);
changed_object* sync_copy_changed_object(changed_object *change);
void sync_free_changed_object(changed_object *change);
void sync_free_change_info(change_info *change);
GList* sync_add_to_change_list(GList *list, char* comp, char* uid,
			       char* removepriority, int change_type, 
			       sync_object_type object_type,
			       GList **extra, char *origuid, 
			       sync_recur_type recur);
GList* sync_do_syncobj_modifies(sync_pair *thissync, client_connection *conn,
				 sync_plugin* plugin,
				GList *change_list);
void sync_process_changes(client_connection *localconn,
			  client_connection *remoteconn, GList *localchanges,
			  GList *remotechanges, sync_object_type newdbs, 
			  sync_pair *thissync);
void sync_log_sync_success(int actualcount, sync_pair *thissync);
void read_syncpairs(void);
sync_plugin* get_plugin_by_name(char *name);
char *name_extension(char *name);
void read_pluginlist(void);
int sync_connect_plugin(sync_pair *thissync, connection_type type,
			client_connection **conn);
void sync_disconnect_plugin(sync_pair *thissync, connection_type type,
			    client_connection **conn);

sync_pair *clone_syncpair(sync_pair *orig);
void apply_syncpair(sync_pair *orig, sync_pair* newpair);
void free_syncpair(sync_pair *pair);
void save_syncpair(sync_pair *pair);
void sync_force_sync(sync_pair *thissync);
void sync_force_resync(sync_pair *thissync);
void sync_settings_changed(sync_pair *thissync);
void sync_quit(sync_pair *thissync, gboolean (*callback)(gpointer), gpointer data);
void sync_plugin_window_closed();
char* sync_objtype_as_string(sync_object_type objtype);

#endif
