#include <time.h>
#include <multisync.h>
#include <csa/csa.h>
#include "csa_plugin.h"

extern gboolean multisync_debug;

/* sync_connect()

   This is called once every time the sync engine tries to get changes
   from the two plugins, or only once if always_connected() returns true.
   Typically, this is where you should try to connect to the device
   to be synchronized, as well as load any options and so on.
   The returned struct must contain a

   client_connection commondata;

   first for common MultiSync data.

   NOTE: Oddly enough (for historical reasons) this callback MUST
   return the connection handle from this function call (and NOT by
   using sync_set_requestdata()). The sync engine still waits for a
   sync_set_requestdone() call (or _requestfailed) before continuing.
*/

csa_connection* sync_connect(sync_pair *handle, connection_type type,
			sync_object_type object_types)
{
  csa_connection* conn;
  CSA_calendar_user user;
  CSA_return_code stat;

  conn = g_malloc0(sizeof(csa_connection));
  g_assert(conn);

  conn->sync_pair = handle;
  conn->conntype = type;
  conn->commondata.object_types = object_types;

  csa_load_config(conn);

  /* Initialise the CSA connection */
  memset((void *)&user, NULL, sizeof(CSA_calendar_user));
  user.calendar_address = conn->csaserver;
  user.user_type = CSA_USER_TYPE_INDIVIDUAL;
  user.user_name = NULL;
  user.calendar_user_extensions = NULL;

  if((stat = csa_logon(NULL, &user, NULL, NULL, NULL, &conn->session, NULL)) 
	!= CSA_SUCCESS) {
      char msg[256];

      snprintf(msg, 256, "Couldn't connect to CSA server: %s.", 
	       conn->csaserver);
      sync_log(conn->sync_pair, msg, SYNC_LOG_ERROR);

      if(stat == CSA_E_CALENDAR_NOT_EXIST) {
	/* try to create the calendar */
	if(csa_add_calendar(NULL, &user, 0, NULL, NULL) != CSA_SUCCESS) {
      		snprintf(msg, 256, "Couldn't create CSA server: %s.", 
	       		conn->csaserver);
      		sync_log(conn->sync_pair, msg, SYNC_LOG_ERROR);
	} else {
		dd(printf("CSA: created calendar\n"));
	}
      }

      sync_set_requestfailed(conn->sync_pair);
      return(conn);
  } else {
      char msg[256];

      snprintf(msg, 256, "Connected to CSA server: %s.", 
	       conn->csaserver);

      sync_log(conn->sync_pair, msg, SYNC_LOG_SUCCESS);
  }

  sync_set_requestdone(conn->sync_pair);

  return(conn);
}

/* sync_disconnect()

   Called by the sync engine to free the connection handle and disconnect
   from the database client.
*/

void sync_disconnect(csa_connection *conn)
{
  sync_pair *sync_pair = conn->sync_pair;

  if(conn->session)
    {
      csa_logoff(conn->session, NULL);
      conn->session = NULL;
    }

  sync_log(conn->sync_pair, "Disconnected from CSA server", SYNC_LOG_SUCCESS);
  sync_set_requestdone(sync_pair);
}

/* sync_done()

   This function is called by the sync engine after a synchronization has
   been completed. If success is true, the sync was successful, and
   all changes reported by get_changes can be forgot. If your database
   is based on a change counter, this can be done by simply saving the new
   change counter.
*/

void sync_done(csa_connection *conn, gboolean success)
{
  if(success) {
	if(conn->commondata.object_types & SYNC_OBJECT_TYPE_CALENDAR) {
		/* Write out the list of entries v.s. modification times */
		csa_save_state(conn);
	}

        nvlist_free(conn->csa_entries);
	conn->csa_entries = NULL;
  }
  sync_set_requestdone(conn->sync_pair);
}

/*
   The most important function in the plugin. This function is called
   periodically by the sync engine to poll for changes in the database to
   be synchronized. The function should return a pointer to a gmalloc'ed
   change_info struct (which will be freed by the sync engine after usage).
   using sync_set_requestdata(change_info*, sync_pair*).

   For all data types set in the argument "newdbs", ALL entries should
   be returned. This is used when the other end reports that a database has
   been reset (by e.g. selecting "Reset all data" in a mobile phone.)
   Testing for a data type is simply done by

   if (newdbs & SYNC_OBJECT_TYPE_SOMETHING) ...

   The "commondata" field of the connection handle contains the field
   commondata.object_types which specifies which data types should
   be synchronized. Only return changes from these data types.

   The changes reported by this function should be the remembered
   and rereported every time until sync_done() (see below) has been
   called with a success value. This ensures that no changes get lost
   if some connection fails.
*/

void get_changes(csa_connection *conn, sync_object_type newdbs)
{
  change_info *chinfo;
  GList * changes = 0;

  nvlist_t * state_file_entries = NULL;

  nvpair_t * csa_entry, * state_entry;

  if(conn->commondata.object_types & SYNC_OBJECT_TYPE_CALENDAR) {

	dd(printf("CSA: get entries from %s\n", conn->csaserver));
	csa_get_entries(conn, &changes);

	if ((newdbs & SYNC_OBJECT_TYPE_CALENDAR) == 0) {
  		/* Load our state information and look for changes */

		dd(printf("CSA: load existing entries for %s\n",conn->csaserver));
  		csa_load_state(conn, &state_file_entries);
	}
	
  	/*
   	 * Check the list of entries against our state file for changes,
   	 * and then populate the changes array with the uids; then check
   	 * the state file against the csa entries for items that we saw
   	 * last time but have been deleted.
   	 */

  	/* Loop around the csa entries; if the timestamp doesn't match the
   	 * state file one, or there is no state file entry, it's a change
   	 */

  	csa_entry = NULL;
  	while(conn->csa_entries &&
		((csa_entry = nvlist_next_nvpair(conn->csa_entries, csa_entry)) != NULL)) {
		if(!csa_entry_matches(state_file_entries, csa_entry)) {
			char * uid;
			changed_object *change;
			GString * vcal;
	
			/* this is a changed or new uid */
	
			uid = nvpair_name(csa_entry);
			vcal = csa_get_entry_vcal(conn, csa_entry);
	
			dd(printf("UID %s has changed on %s\n", uid,
				conn->csaserver));
	
			change = g_malloc0(sizeof(changed_object));
			g_assert(change);

			change->uid = g_strdup(uid);
			change->comp = vcal->str;
			change->change_type =
				csa_entry_exists(state_file_entries,csa_entry)
					?SYNC_OBJ_MODIFIED:SYNC_OBJ_ADDED;
			change->object_type = SYNC_OBJECT_TYPE_CALENDAR;

			changes = g_list_append(changes, change);
		}
  	}

  	/* Loop around the state entries; if a corresponding csa entry doesn't
   	 * exist, it has been deleted so is a change
   	 */

  	state_entry = NULL;
  	while(state_file_entries &&
		((state_entry = nvlist_next_nvpair(state_file_entries, state_entry)) != NULL)) {
		if(!csa_entry_exists(conn->csa_entries, state_entry)) {
			char * uid;
			changed_object *change;
	
			uid = nvpair_name(state_entry);

			dd(printf("UID %s has been deleted on %s\n", uid,
				conn->csaserver));
	
			change = g_malloc0(sizeof(changed_object));
			g_assert(change);
			change->uid = g_strdup(uid);
			change->comp = g_strdup("");
			change->change_type = SYNC_OBJ_HARDDELETED;
			change->object_type = SYNC_OBJECT_TYPE_CALENDAR;
	
			changes = g_list_append(changes, change);
		}
  	}

  	/*
    	 * Tidy up
   	 */

        if(state_file_entries) nvlist_free(state_file_entries);
 
  	chinfo = g_malloc(sizeof(change_info));
  	chinfo->changes = changes;
  	chinfo->newdbs = 0;

	/*
	 * Look for a calendar reset; i.e. no values in it
	 */

	/*
	if(conn->csa_entries &&
		(nvlist_next_nvpair(conn->csa_entries, NULL) == NULL)) {
  		chinfo->newdbs = SYNC_OBJECT_TYPE_CALENDAR;
	}
	*/
		

  	sync_set_requestdata(chinfo, conn->sync_pair);
  }
}

/* syncobj_modify()

   Modify or add an object in the database. This is called by the sync
   engine when a change has been reported in the other end.

   Arguments:
   object	A string containing the actual data of the object. E.g. for
   		an objtype of SYNC_OBJECT_TYPE_CALENDAR, this is a
		vCALENDAR 2.0 string (see RFC 2445).
   uid		The unique ID of this entry. If it is new (i.e. the sync engine
		has not seen it before), this is NULL.
   objtype    The data type of this object.
   returnuid  If uid is NULL, then the ID of the newly created object should
		be returned in this buffer (if non-NULL). The length of the
		ID should be returned in returnuidlen.
*/

void syncobj_modify(csa_connection *conn,
                    char *comp,
                    char *uid, sync_object_type objtype,
                    char *uidret, int *uidretlen) 
{
	dd(printf("CSA: syncobj_modify: uid %s\n",uid?uid:"<new>"));

	if(objtype & SYNC_OBJECT_TYPE_CALENDAR) {
		if(uid == NULL) {
			csa_add_vcal_entry(conn, comp, uidret);
			*uidretlen=strlen(uidret);
		} else {
			csa_update_vcal_entry(conn, comp, uid);
		}
	}
	sync_set_requestdone(conn->sync_pair);
}
 
/*
 * Delete the object from the server. We won't care about the softdelete
 * flag since the server doesn't have soft or harddelete.
 */

void syncobj_delete(csa_connection *conn, char *uid,
                    sync_object_type objtype, int softdelete) 
{
  
	dd(printf("syncobj_delete: uid: %s\n", uid));
  	if(objtype & SYNC_OBJECT_TYPE_CALENDAR) {
		CSA_entry_handle *handle;
		if(csa_get_entry_handle(conn, uid, &handle)) {
			csa_delete_entry(conn->session, handle[0],
				CSA_SCOPE_ALL, NULL);
			csa_remove_from_list(conn, uid);
		}
  	}
	sync_set_requestdone(conn->sync_pair);
}
                                                                                
/* short_name()
 *
 * Return a short plugin name for internal use.
 */

char* short_name()
{
  return("CSA");
}

/* long_name()
 *
 * Return a long name which can be shown to the user.
 */

char *long_name()
{
  return("CDE Calendar Server");
}

/* object_types()
 *
 *  Return the data types this plugin can handle.
 */

sync_object_type object_types() 
{
  return(SYNC_OBJECT_TYPE_CALENDAR);
}

/* plugin_init()
 *
 * Initialize the plugin. Called once upon loading of the plugin (NOT
 * once per sync pair).
 */

void plugin_init(void) 
{
} 

/* plugin_info()
 *
 * Return an even longer description of what this plugin does. This will
 * be shown next to the drop-down menu in the sync pair options.
 */


char *plugin_info()
{
  return("Allows synchronization of a calendar against a CDE Calendar Server.");
}

/* always_connected()
 * Return TRUE if this client does not have to be polled (i.e. can be
 * constantly connected).
 */

gboolean always_connected()
{
  return(FALSE);
}

/* plugin_API_version()
 *
 *
 * Return the MultiSync API version for which the plugin was compiled.
 * It is defined in multisync.h as MULTISYNC_API_VER.
 * Do not use return(MULTISYNC_API_VER), though, as the plugin will then
 * get valid after a simple recompilation. This may not be all that is needed.
 */

int plugin_API_version(void)
{
  return(3);
}
