/*
    Silky - A GTK+ client for SILC.
    Copyright (C) 2003, 2004, 2005 Toni Willberg

    - Buddy list management functions

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    http://silky.sourceforge.net/

*/

#include <silcincludes.h>
#include <silcclient.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <glib.h>
#include <glade/glade.h>

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "ui_buddylist.h"
#include "buddylist.h"
#include "xmllayout.h"
#include "xmlconfig.h"
#include "log.h"
#include "keys.h"
#include "support.h"

SilkyBuddy *buddies = NULL;

extern xmlDocPtr config_buddies;
extern SilkyXMLElement xml_elements[NUM_CONFIG][NUM_CM];
extern gchar *buddiespath;
extern silkyStruct *silky;

SilkyBuddy *buddy_find_by_nick(gchar *nick) {
  SilkyBuddy *b;

  for( b = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(b->nickname, nick) ) return b;
  }

  return NULL;
}

SilkyBuddy *buddy_find_by_fp(gchar *fp) {
  SilkyBuddy *b;

  for( b = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(b->fingerprint, fp) ) return b;
  }

  return NULL;
}



/*
  Adds watch for fingerprint, loads pubkey from file.
  Stolen code from SILC Client's SILC_CLIENT_CMD_FUNC(watch)
  input: hashed fingerprint
*/
gboolean watch_add_fingerprint(gchar *fingerprint) {
  SilcPublicKey pk;
  SilcBuffer idp, buffer = NULL, args = NULL;
  const gchar *pubkey;

  debug_info("adding watch for %s", fingerprint);
  g_assert(silky->conn);
  g_assert(fingerprint);
  pubkey = gen_keyfile_path(fingerprint, "client");
  if (!pubkey) {
    debug_message("public key not found");
    return FALSE;
  }

  if (!silc_pkcs_load_public_key(pubkey, &pk, SILC_PKCS_FILE_PEM)) {
    if (!silc_pkcs_load_public_key(pubkey, &pk, SILC_PKCS_FILE_BIN)) {
      debug_info("Couldn't load public key");
      return FALSE;
    }
  }

  debug("loaded pubkey %s", pubkey);

  args = silc_buffer_alloc_size(2);
  silc_buffer_format(args,
		     SILC_STR_UI_SHORT(1),
		     SILC_STR_END);
  buffer = silc_pkcs_public_key_payload_encode(pk);
  args = silc_argument_payload_encode_one(args, buffer->data, buffer->len, 0x00);

  idp = silc_id_payload_encode(silky->conn->local_id, SILC_ID_CLIENT);

  silc_client_command_send(silky->client, silky->conn,
			   SILC_COMMAND_WATCH, 0, 2,
			   1, idp->data, idp->len,
			   4, args->data, args->len);
  silc_buffer_free(buffer);
  silc_pkcs_public_key_free(pk);
  silc_buffer_free(args);
  silc_buffer_free(idp);

  return TRUE;
}

/*
  Sends WATCH command
*/
void watch_add(gchar *nick) {

  SilcBuffer idp;

  if (!silky || !silky->conn || !silky->conn->local_id) {
    debug("can't find own id, aborting");
    return;
  }

  debug_info("adding watch for '%s'", nick);

  idp = silc_id_payload_encode(silky->conn->local_id, SILC_ID_CLIENT);
  if (!idp) {
    debug("unable to encode payload");
    return;
  }

  silc_client_command_send(silky->client, silky->conn,
				 SILC_COMMAND_WATCH, 0, 4,
				 1, idp->data, idp->len,
				 2, nick, strlen(nick),
				 3, NULL, NULL,
				 4, NULL, NULL
				 );

  silc_buffer_free(idp);
  return;
}


void watch_del(gchar *nick) {
  SilcBuffer idp;

  if (!silky || !silky->conn || !silky->conn->local_id) {
    debug("can't find own id, aborting");
    return;
  }

  debug_info("deleting watch for '%s'", nick);

  idp = silc_id_payload_encode(silky->conn->local_id, SILC_ID_CLIENT);
  silc_client_command_send(silky->client, silky->conn,
				 SILC_COMMAND_WATCH, 0, 4,
				 1, idp->data, idp->len,
				 2, NULL, NULL,
				 3, nick, strlen(nick),
				 4, NULL, NULL
				 );

  silc_buffer_free(idp);
  return;
}



gboolean buddy_add(gchar *nick, gchar *fingerprint) {
  SilkyBuddy *b, *newb;


  if( buddy_find_by_fp(fingerprint) ) {
    debug_info("Buddy with this fingerprint is already in the list, ignoring");
    return FALSE;
  }


  debug("creating struct for new buddy");
  newb = malloc( sizeof( SilkyBuddy ) );
  newb->next = NULL;

  /* let's make sure that no string in here is NULL */
  if( nick ) newb->nickname = g_strdup(nick);
    else newb->nickname = g_strdup("");
  newb->realname = NULL;
  if( fingerprint ) newb->fingerprint = g_strdup(fingerprint);
    else newb->fingerprint = g_strdup("");

  newb->keyverified = 0;

  newb->languages = NULL;
  newb->preferred_contact = NULL;
  newb->timezone = NULL;
  newb->geolocation = NULL;
  newb->client_id = NULL;

  newb->notes = NULL;

  debug_info("Adding '%s' (%s)", newb->nickname, newb->fingerprint);

  if( !buddies ) {
    debug("first buddy, creating linked list");
    buddies = newb;
  }  else {
    debug("there are already some buddies, appending");
    for( b = buddies; b->next; b = b->next ) {}
    b->next = newb;
  }

  debug("starting to watch for this buddy");
  if (silky->conn)
    watch_add_fingerprint(fingerprint);

 return TRUE;
}

/**
 * buddy_set_mode_by_fp:
 * @fingerprint: the fingerprint of user to modify
 * @mode: new mode of the user
 *
 * This function changes the user mode of a user stored in Buddy List.
 *
 * Returns: gboolean
 **/
gboolean buddy_set_mode_by_fp(gchar *fingerprint, SilcUInt32 mode) {
 SilkyBuddy *b;

 debug_info("setting umode %d ");

  for ( b = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(b->fingerprint, fingerprint) ) {
      b->mode = mode;
      return TRUE;
    }
  }

  return FALSE;
}

SilcUInt32 buddy_get_mode_by_fp(gchar *fingerprint) {
	SilkyBuddy *b = buddy_find_by_fp(fingerprint);

	if( !b ) {
		debug("Buddy not found");
		return -1;
	}

	if( b->mode ) {
		return( b->mode );
	}

	return -1;
}

/**
 * buddy_init_watchlist:
 *
 * This function sends WATCH command to server for every buddy
 *
 * Returns: nothing
 **/
void buddy_init_watchlist() {
  SilkyBuddy *b;

  g_assert(silky->conn);

  debug_info("sending watch command for all users in the buddy list, if any");
  for ( b = buddies; b; b = b->next ) {
    watch_add_fingerprint(b->fingerprint);
  }
}

/**
 * buddy_set_keyverified_by_fp:
 *
 * This function sets 'keyverified' parameter of the buddy entity
 *
 * Returns: gboolean
 **/
gboolean buddy_set_keyverified_by_fp(const gchar *fingerprint, const gint keyverified) {
  SilkyBuddy *b = buddy_find_by_fp((gchar *)fingerprint);

  if( !b ) return FALSE;

  debug_info("setting keyverified to '%d'", keyverified);

  b->keyverified = keyverified;
  return TRUE;
}



gboolean buddy_set_nick_by_fp(gchar *fingerprint, gchar *nick) {
  SilkyBuddy *b;

  if( !nick ) {
    debug("nick == NULL");
    return FALSE;
  }

  debug_info("setting nick to '%s'", nick);

  for ( b = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(b->fingerprint, fingerprint) ) {
      if (b->nickname) {
	g_free(b->nickname);
      }
      b->nickname = g_strdup(nick);
      return TRUE;
    }
  }

  return FALSE;
}

gboolean buddy_set_realname_by_fp(gchar *fingerprint, gchar *realname) {
  SilkyBuddy *b;

  if( !realname ) {
    debug("realname == NULL");
    return FALSE;
  }

  debug_info("setting realname to '%s'", realname);

  for ( b = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(b->fingerprint, fingerprint) ) {
      if (b->realname) {
	g_free(b->realname);
      }
      b->realname = g_strdup(realname);
      return TRUE;
    }
  }

  return FALSE;
}

gboolean buddy_set_languages_by_fp(gchar *fingerprint, gchar *languages) {
  SilkyBuddy *b;

  if( !languages ) {
    debug("languages == NULL");
    return FALSE;
  }

  b = buddy_find_by_fp(fingerprint);
  if (b) {
    if (b->languages) {
      g_free(b->languages);
    }
    b->languages = g_strdup(languages);
    return TRUE;
  }

  return FALSE;
}

gboolean buddy_set_pref_contact_by_fp(gchar *fingerprint, gchar *pc) {
  SilkyBuddy *b;

  if( !pc ) {
    debug("pc == NULL");
    return FALSE;
  }

  b = buddy_find_by_fp(fingerprint);
  if (b) {
    if (b->preferred_contact) {
      g_free(b->preferred_contact);
    }
    b->preferred_contact = g_strdup(pc);
    return TRUE;
  }

  return FALSE;
}

gboolean buddy_set_timezone_by_fp(gchar *fingerprint, gchar *tz) {
  SilkyBuddy *b;

  if( !tz ) {
    debug("tz == NULL");
    return FALSE;
  }

  b = buddy_find_by_fp(fingerprint);
  if (b) {
    if (b->timezone) {
      g_free(b->timezone);
    }
    b->timezone = g_strdup(tz);
    return TRUE;
  }

  return FALSE;
}

gboolean buddy_set_clientid_by_fp(gchar *fingerprint, SilcClientID *id) {
  SilkyBuddy *b;
  gchar *str_fingerprint = silc_fingerprint(fingerprint, 20 );
  debug("setting clientid for %s", str_fingerprint );

  b = buddy_find_by_fp(fingerprint);
  if (b) {
    if (b->client_id) {
      g_free(b->client_id);
    }
    b->client_id = id;
    //    b->client_id = silc_id_dup(id, SILC_ID_CLIENT); /* copy */
    return TRUE;
  }

  return FALSE;
}

gboolean buddy_set_geolocation_by_fp(gchar *fingerprint, gchar *gl) {
  SilkyBuddy *b;

  if( !gl ) {
    debug("gl == NULL");
    return FALSE;
  }

  b = buddy_find_by_fp(fingerprint);
  if (b) {
    if (b->geolocation) {
      g_free(b->geolocation);
    }
    b->geolocation = g_strdup(gl);
    return TRUE;
  }

  return FALSE;
}


gboolean buddy_remove_by_fp(gchar *fingerprint) {
  SilkyBuddy *b, *prevb;
  gint found = 0;

  debug_info("trying to remove buddy");

  if( !buddies ) {
    debug("No buddies, returning");
    return FALSE;
  }

  for( b = buddies, prevb = buddies; b; b = b->next ) {
    if( !g_ascii_strcasecmp(fingerprint, b->fingerprint) ) {
      debug("found it, nick '%s'", b->nickname);
      found++;
      break;
    }
    prevb = b;
  }

  if( !found ) {
    debug("could not find buddy to remove, returning");
    return FALSE;
  }

  debug("removing buddy from list");
  if( b == buddies ) {
    debug("it is first buddy");
    buddies = buddies->next;
  } else {
    debug("not first buddy");
    prevb->next = b->next;
  }

  debug("freeing buddy from memory");
  if (b) {
    g_free(b);
  }
  return TRUE;
}


/*
  This loads buddylist from the file, and calls parse_buddies()
  Called from main.c
*/
gboolean init_buddies(gchar *buddiespath) {
  debug_info("Trying to load buddies from %s", buddiespath);
  if( g_file_test(buddiespath, G_FILE_TEST_EXISTS) ) {
    debug("file found");

    if( !(config_buddies = xml_read_config(CONFIG_BUDDIES, buddiespath)) ) {
      debug("Failed to load buddies from file %s", buddiespath);
      return FALSE;
    }

    if( !parse_buddies() ) {
      debug("Failed to parse buddies, none loaded");
      /* FIXME display warning to user? */
      return TRUE;
    }

    debug("Buddies succesfully loaded, continuing");
    return TRUE;
  }

  return FALSE;
}


/*
  Parses the buddylist XML file.
  Adds buddies the UI list.
*/
gboolean parse_buddies() {
  xmlXPathObjectPtr result;
  xmlXPathContextPtr context;
  xmlNodePtr node, n;
  gint i;

  gchar *nick = NULL;
  gchar *realname = NULL;
  gchar *fp = NULL;
  gchar *keyverified = NULL;

  xml_cleanup_config(CONFIG_BUDDIES);

  debug_info("Populating SilkyBuddies list...");

  context = xmlXPathNewContext(config_buddies);

  result = xmlXPathEval(g_strdup_printf("/%s/%s", xml_elements[CONFIG_BUDDIES][CB_ROOT].name, xml_elements[CONFIG_BUDDIES][CB_BUDDY].name), context);
  if( !result ) {
    debug("No result found for buddies");
    return FALSE;
  }

  if( xmlXPathNodeSetIsEmpty(result->nodesetval) ) {
    debug_info("no buddies found.");
    return TRUE;
  }

  xmlXPathFreeContext(context);
  for( i = 0; i < result->nodesetval->nodeNr; i++ ) {
    node = result->nodesetval->nodeTab[i];

    for( n = node->children; n && g_ascii_strcasecmp(n->name, xml_elements[CONFIG_BUDDIES][CB_BUDDY].name) ; n = n->next ) {
      if( !g_ascii_strcasecmp(n->name, xml_elements[CONFIG_BUDDIES][CB_BUDDY_NICKNAME].name) )
        nick = g_strdup(xmlNodeGetContent(n));

      if( !g_ascii_strcasecmp(n->name, xml_elements[CONFIG_BUDDIES][CB_BUDDY_REALNAME].name) )
        realname = g_strdup(xmlNodeGetContent(n));

      if( !g_ascii_strcasecmp(n->name, xml_elements[CONFIG_BUDDIES][CB_BUDDY_FINGERPRINT].name) )
        fp = g_strdup(xmlNodeGetContent(n));

      if( !g_ascii_strcasecmp(n->name, xml_elements[CONFIG_BUDDIES][CB_BUDDY_KEYVERIFIED].name) )
        keyverified = g_strdup(xmlNodeGetContent(n));
    }

    if( (!fp || !g_utf8_strlen(fp, -1)) || (!nick || !g_utf8_strlen(nick, -1)) ) {
      debug("Fingerprint empty, ignoring buddy");
    } else {
      debug("Adding buddy '%s', fingerprint '%s'", nick, fp);
      buddy_add(nick, fp);
      buddy_set_realname_by_fp(fp, realname);
      buddy_set_keyverified_by_fp(fp, atoi(keyverified));
    }
  }

  xmlXPathFreeObject(result);
  if( !i ) debug("no buddies found.");
  return TRUE;
}


/*
  Saves the buddylist file
*/
gboolean save_buddies() {
  SilkyBuddy *b;
  xmlNodePtr bn, n;

  debug_info("Starting to save buddies to file '%s'", buddiespath);

  xmlFreeDoc(config_buddies);

  config_buddies = xml_create_config(CONFIG_BUDDIES);

  for( b = buddies; b; b = b->next ) {
    debug("Saving '%s' (%s)", b->nickname, b->fingerprint);
    bn = xmlNewTextChild(xmlDocGetRootElement(config_buddies), NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY].name, NULL);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_NICKNAME].name, b->nickname);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_REALNAME].name, b->realname);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_FINGERPRINT].name, b->fingerprint);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_KEYVERIFIED].name, g_strdup_printf("%d", b->keyverified));
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_NOTES].name, b->notes);

    bn = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_ATTR].name, NULL);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_ATTR_LANGUAGES].name, b->languages);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_ATTR_PREFCONTACT].name, b->preferred_contact);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_ATTR_TIMEZONE].name, b->timezone);
    n = xmlNewTextChild(bn, NULL, xml_elements[CONFIG_BUDDIES][CB_BUDDY_ATTR_GEOLOCATION].name, b->geolocation);

}

  xml_save_config(CONFIG_BUDDIES, buddiespath);
  return TRUE;
}

gchar *buddy_get_notes_by_fp(gchar *fingerprint) {
	SilkyBuddy *b = buddy_find_by_fp(fingerprint);

	if( !b ) {
		debug("Buddy not found");
		return NULL;
	}

	if( b->notes ) {
		return( g_strdup(b->notes) );
	}

	return NULL;
}

gboolean buddy_set_notes_by_fp(gchar *fingerprint, gchar *notes) {
	SilkyBuddy *b = buddy_find_by_fp(fingerprint);

	if( !notes ) {
		debug("notes == NULL");
		return FALSE;
	}

	if( !b ) {
		debug("Buddy with such fingerprint not found.");
		return FALSE;
	}

	if( b->notes != NULL ) {
		g_free(b->notes);
	}

	b->notes = g_strdup(notes);
	debug("Set notes: \"%s\"", notes);
	return TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1