/* 
   MultiSync Evolution Plugin - Synchronize Ximian Evolution data
   Addressbook synchronization.
   Copyright (C) 2002-2003 Tobias Karlsson <tobbe@island.liu.se> and
                           Bo Lincoln <lincoln@lysator.liu.se>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2 as
   published by the Free Software Foundation;

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
   IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
   CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

   ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
   COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
   SOFTWARE IS DISCLAIMED.
*/

/*
 *  $Id: addr_sync.c,v 1.22 2003/11/03 18:49:30 lincoln Exp $
 */

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include "evolution_sync.h"
#include "addr_sync.h"

extern gboolean multisync_debug;

// Returns the UID of a vcard as a g_malloced string.
char* evo_addr_get_uid(char *vcard) {
  char *pos = vcard;
  while (pos) {
    if (!strncmp(pos, "UID:", 4)) {
      char uid[256];
      if (sscanf(pos, "UID:%255[^\r\n]", uid) > 0) {
	return(g_strdup(uid));
      }
    }
    pos = strstr(pos, "\n");
    if (pos)
      pos++;
  }
  return(NULL);
}

void evo_addr_change(evolution_connection *conn, GList *ids, GList *cards,
		     int change_type) {
  switch(conn->addr_mode) {
  case EVO_ADDR_MODE_GETVIEW:
    break;
  case EVO_ADDR_MODE_GETCHANGES: 
  case EVO_ADDR_MODE_GETALL: 
    {
      // Add to change list
      int n;
      if (change_type == SYNC_OBJ_ADDED || change_type == SYNC_OBJ_MODIFIED) {
	for (n = 0; n < g_list_length(cards); n++) {
	  ECard *ecard;
	  changed_object* change;
	  char *tmp;
	    
	  change = g_malloc0(sizeof(changed_object));
	  g_assert(change);
	  ecard = g_list_nth_data(cards, n);
	  tmp = e_card_get_vcard_assume_utf8(ecard);
	  if (tmp) {
	    change->comp = sync_vtype_convert(tmp, 0, NULL); // Shape it up
	    g_free(tmp);
	  }
	  change->change_type = change_type;
	  change->object_type = SYNC_OBJECT_TYPE_PHONEBOOK;
	  change->uid = evo_addr_get_uid(change->comp);
	  conn->addr_changes = evo_append_change(conn->addr_changes, change);
	}
      }
      if (!cards && ids) {
	for (n = 0; n < g_list_length(ids); n++) {
	  char *id = g_list_nth_data(ids, n);
	  changed_object* change;

	  change = g_malloc0(sizeof(changed_object));
	  g_assert(change);
	  change->change_type = change_type;
	  change->object_type = SYNC_OBJECT_TYPE_PHONEBOOK;
	  change->uid = g_strdup(id);
	  conn->addr_changes = evo_append_change(conn->addr_changes, change);
	}
      }
    }
    break;
  case EVO_ADDR_MODE_MODIFIED:
    // Remove from change list
    break;
  case EVO_ADDR_MODE_WAITING:
    conn->addr_mode = EVO_ADDR_MODE_GOT_CHANGE;
    break;
    {
      // We are just removing our own changes from the change db
      // Check that it is in fact OUR change we get here
      
      
      
    }
  default:
    break;
  }
}


// FIXME: Changed in 1.4
void evo_addr_removed_cb (EBookView *book_view, GList *ids, 
			  gpointer data) {
  evolution_connection *conn = data;
  evo_addr_change(conn, ids, NULL, SYNC_OBJ_HARDDELETED);
}

void evo_addr_added_cb  (EBookView *book_view, GList *cards,
			 gpointer data) {
  evolution_connection *conn = data;
  evo_addr_change(conn, NULL, cards, SYNC_OBJ_ADDED);
}

void evo_addr_changed_cb (EBookView *book_view,  GList *cards,
			  gpointer data) {
  evolution_connection *conn = data;
  evo_addr_change(conn, NULL, cards, SYNC_OBJ_MODIFIED);
}

void evo_addr_seqcompl_cb (EBookView *book_view, 
			   EBookViewStatus status, 
			   gpointer data) {
  evolution_connection *conn = data;
  
  switch(conn->addr_mode) {
  case EVO_ADDR_MODE_GETVIEW:
    dd(printf("Get view done.\n"));
    conn->addr_mode = EVO_ADDR_MODE_WAITING;
    if (conn->callback)
      (conn->callback)(NULL, conn); // We have loaded the DB
    break;
  case EVO_ADDR_MODE_GETCHANGES:
    dd(printf("Get changes done.\n"));
    if (conn->callback)
      (conn->callback)(conn->addr_changes, conn);
    g_object_unref (G_OBJECT (book_view));
    conn->addr_mode = EVO_ADDR_MODE_WAITING;
    break;
  case EVO_ADDR_MODE_REMOVINGCHANGE:
    if (conn->callback)
      (conn->callback)(conn->addr_changes, conn);
    g_object_unref (G_OBJECT (book_view));
    conn->addr_mode = EVO_ADDR_MODE_WAITING;
    break;
  case EVO_ADDR_MODE_GETALL:
    dd(printf("Get all done.\n"));
    conn->addr_mode = EVO_ADDR_MODE_REMOVINGCHANGE;
    e_book_get_changes (conn->ebook, conn->changedbname, 
			evo_addr_view_cb, conn);
    g_object_unref (G_OBJECT (book_view));
    break;
  case EVO_ADDR_MODE_MODIFIED:
    dd(printf("Modification done\n"));
    evo_addr_modify_next(conn, FALSE);
    break;
  case EVO_ADDR_MODE_GOT_CHANGE:
    sync_object_changed(conn->sync_pair);
    conn->addr_mode = EVO_ADDR_MODE_WAITING;
    break;
  case EVO_ADDR_MODE_WAITING:
    break;
  default:
    conn->addr_mode = EVO_ADDR_MODE_WAITING;
    break;
  }

}

void evo_addr_view_cb (EBook *book, EBookStatus status, EBookView *book_view, 
		       gpointer data) {
  evolution_connection *conn = data;
  if (status == E_BOOK_STATUS_SUCCESS) {
    if (conn->addr_mode == EVO_ADDR_MODE_GETVIEW)
      conn->ebookview = book_view;
    g_object_ref (G_OBJECT (book_view));
    g_signal_connect (G_OBJECT (book_view), "card_changed",
			G_CALLBACK (evo_addr_changed_cb), conn);
    g_signal_connect (G_OBJECT (book_view), "card_added",
			G_CALLBACK (evo_addr_added_cb), conn);
    g_signal_connect (G_OBJECT (book_view), "card_removed",
			G_CALLBACK (evo_addr_removed_cb), conn);
    g_signal_connect (G_OBJECT (book_view), "sequence_complete",
		      G_CALLBACK (evo_addr_seqcompl_cb), 
		      conn);
  }
}

void evo_addr_bookloaded_cb(EBook *book, EBookStatus status, gpointer data) {
  evolution_connection *conn = data;
  if (status == E_BOOK_STATUS_SUCCESS) {
    conn->addr_mode = EVO_ADDR_MODE_GETVIEW;
    e_book_get_book_view (book,"(contains \"full_name\" \"\")",
    			  evo_addr_view_cb, conn);
  } else {
    // If load of addressbook failed, don't lock up
    if (conn->callback)
      (conn->callback)(NULL, conn);
  }  
}

void evo_addr_connect(evolution_connection *conn) {
  char *path;

  if (conn->commondata.object_types & SYNC_OBJECT_TYPE_PHONEBOOK) {
    conn->nodbs++;
    conn->ebook = e_book_new();
    
    if (conn->addressbookpath && strlen(conn->addressbookpath) > 0)
      path = g_strdup_printf("file://%s", conn->addressbookpath);
    else
      path = g_strdup_printf ("file://%s/evolution/local/Contacts/addressbook.db", 
			      g_get_home_dir());
    e_book_load_uri(conn->ebook, path, evo_addr_bookloaded_cb, conn);
    g_free(path);
  }
}

void evo_addr_disconnect(evolution_connection *conn) {
  dd(printf("Disconnecting view...\n"));
  if (conn->ebookview)
    g_object_unref (G_OBJECT (conn->ebookview));
  dd(printf("Disconnecting ebook...\n"));
  if (conn->ebook) {
    e_book_unload_uri(conn->ebook); 
    g_object_unref (G_OBJECT(conn->ebook));
  }
  dd(printf("Disconnecting addressbook done.\n"));
  conn->ebook = NULL;
}
 
void evo_addr_get_changes(GList *changes,
			  evolution_connection *conn, evo_sync_cb cb) {
  if (conn->ebook) {
    conn->addr_mode = EVO_ADDR_MODE_GETCHANGES;
    conn->addr_changes = changes;
    conn->callback = cb;
    e_book_get_changes (conn->ebook, conn->changedbname, evo_addr_view_cb, conn);
  } else {
    (cb)(changes, conn);
  }
}
 
void evo_addr_get_all(GList *changes,
		      evolution_connection *conn, evo_sync_cb cb) {
  if (conn->ebook) {
    conn->addr_mode = EVO_ADDR_MODE_GETALL;
    conn->addr_changes = changes;
    conn->callback = cb;
    e_book_get_book_view (conn->ebook,"(contains \"full_name\" \"\")",
    			  evo_addr_view_cb, conn);
  } else {
    (cb)(changes, conn);
  }
}

void evo_addr_add_cb (EBook *book, EBookStatus status, const char *id, 
		      gpointer data) {
  evolution_connection *conn = data;
  if (status == E_BOOK_STATUS_SUCCESS) {
    syncobj_modify_result *result = g_list_nth_data(conn->modify_results,
						    conn->modify_no);
    if (result) {
      if (result->returnuid)
	g_free(result->returnuid);
      result->returnuid = g_strdup(id);
      result->result = SYNC_MSG_REQDONE;
    }
  } else {
    evo_addr_modify_next(conn, FALSE);
  }
}


void evo_addr_modify_cb (EBook *book, EBookStatus status, gpointer data) {
  evolution_connection *conn = data;
  dd(printf("Modify CB\n"));
  if (status != E_BOOK_STATUS_SUCCESS)
    evo_addr_modify_next(conn, TRUE);
}

// Add or replace an UID field in the vcard. The returned vcard is g_malloced.
// If uid is NULL, the UID field is removed.
char *evo_addr_set_uid(char *vcard, char* uid) {
  char *olduid = evo_addr_get_uid(vcard);
  GString* buf = g_string_new("");
  char *newcard;
  
  if (olduid) {
    char *pos = vcard;
    // We need to find it
    g_free(olduid);
    while (pos) {
      if (!strncmp(pos, "UID:", 4)) {
	if (uid) {
	  g_string_append(buf, "UID:");
	  g_string_append(buf, uid);
	  g_string_append(buf, "\r\n");
	}
      } else {
	char *end = strstr(pos, "\n");
	if (end) {
	  char *line = g_strndup(pos, end-pos+1);
	  g_string_append(buf, line);
	  g_free(line);
	} else { // End of card
	  g_string_append(buf, pos);
	}
      }
      pos = strstr(pos, "\n");
      if (pos)
	pos++;
    }
  } else {
    char *pos = vcard;
    while (pos) {
      char *end = strstr(pos, "\n");
      char *line = g_strndup(pos, end?(end-pos+1):strlen(pos));
      if (!strncmp(pos, "BEGIN:VCARD", 11)) {
	if (end) {
	  g_string_append(buf, line);
	  if (uid) {
	    g_string_append(buf, "UID:");
	    g_string_append(buf, uid);
	    g_string_append(buf, "\r\n");
	  }
	  g_string_append(buf, end+1);
	  pos = NULL;
	}
      } else {
	if (end) {
	  g_string_append(buf, line);
	} else { // End of card
	  g_string_append(buf, pos);
	}
      }
      g_free(line);
      if (pos) {
	pos = strstr(pos, "\n");
	if (pos)
	  pos++;
      }
    }
  }
  newcard = g_strdup(buf->str);
  g_string_free(buf, TRUE);
  return(newcard);
}

// If tryadd == TRUE, then a modification just failed, and we should
// try to add the same card instead
void evo_addr_modify_next(evolution_connection *conn,
			  gboolean tryadd) {
  changed_object *obj = NULL;
  

  if (!tryadd)
    conn->modify_no++;
  while ((obj = g_list_nth_data(conn->modify_objects, conn->modify_no)) &&
	 (obj->object_type != SYNC_OBJECT_TYPE_PHONEBOOK))
    conn->modify_no++;
  if (!obj) {
    if (conn->callback)
      (conn->callback)(NULL, conn); // We have done the modifications
    return;
  }
  switch (obj->change_type) {
  case SYNC_OBJ_MODIFIED:
  case SYNC_OBJ_ADDED: 
    {
      // Modify or add 
      // Remove PHOTO first
      char *tmp = sync_vtype_convert(obj->comp, VOPTION_REMOVEPHOTO, NULL);
      char *newcard = evo_addr_set_uid(tmp, obj->uid);
      g_free(tmp);
      conn->addr_mode = EVO_ADDR_MODE_MODIFIED;
      if (!obj->uid || tryadd) { // Add new card
	e_book_add_vcard (conn->ebook, newcard, evo_addr_add_cb, conn);
      } else { // Modify a card
	e_book_commit_vcard (conn->ebook, newcard, evo_addr_modify_cb, conn);
      }
      //g_free(newcard);
    }
    break;
  case SYNC_OBJ_HARDDELETED: 
    {
      if (obj->uid) {
	conn->addr_mode = EVO_ADDR_MODE_MODIFIED;
	e_book_remove_card_by_id (conn->ebook, obj->uid, 
				  evo_addr_modify_cb, conn);
      } else
	evo_addr_modify_next(conn, FALSE); // Do the next
    }
    break;
  default:
    evo_addr_modify_next(conn, FALSE);
  }
}


gboolean evo_addr_modify(gpointer data) {
  evolution_connection *conn = data;
  if (conn->ebook && conn->modify_objects) {
    conn->modify_no = -1;
    evo_addr_modify_next(conn, FALSE); // Start doing the modifications
  } else {
    if (conn->callback)
      (conn->callback)(NULL, conn); // We have done the modifications
  }
  return(FALSE);
}
