/* Jungle Monkey
 * Copyright (C) 1999-2001  The Regents of the University of Michigan
 *
 * 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
 */

#include <string.h>
#include <stdlib.h>

#include "jmmsp.h"
#include "jmmsp_debug.h"

#include "util/util.h"
#include "util/elf.h"


static void jmmsp_reset (JMMSP* jmmsp);
static void jmmsp_packet_cb (Btp* btp, const void* buffer, 
			     guint length, gpointer user_data);
static void jmmsp_error_cb (Btp* btp, gpointer user_data);


JMMSP*    
jmmsp_create (BPeer* bpeer, const gchar* name)
{
  JMMSP* jmmsp;
  Btp* btp;
  GURL* url;

  g_return_val_if_fail (name, NULL);

  /* Create a new Btp session */
  btp = btp_create (bpeer, name);
  if (btp == NULL)
    return NULL;

  /* Create the URL */
  url = gnet_url_clone (btp_get_url (btp));
  gnet_url_set_protocol (url, "jmmsp");

  /* Create a new JMMSP */
  jmmsp = g_new0 (JMMSP, 1);
  jmmsp->peer 	  = bpeer;
  jmmsp->url  	  = url;
  jmmsp->btp  	  = btp;
  jmmsp->is_local = TRUE;

  /* Initialize BTP */
  btp->packet_func 	= jmmsp_packet_cb;
  btp->error_func  	= jmmsp_error_cb;
  btp->packet_user_data = jmmsp;
  btp->error_user_data  = jmmsp;

  return jmmsp;
}


JMMSP*	  
jmmsp_join (BPeer* bpeer, const GURL* url)
{
  JMMSP* jmmsp;
  Btp* btp;

  g_return_val_if_fail (url, NULL);

  /* Join the BTP */
  btp = btp_join (bpeer, url);
  if (!btp)
    return NULL;

  /* Create a new JMMSP */
  jmmsp = g_new0 (JMMSP, 1);
  jmmsp->peer = bpeer;
  jmmsp->url  = gnet_url_clone (url);
  jmmsp->btp  = btp;

  /* Initialize BTP */
  btp->packet_func 	= jmmsp_packet_cb;
  btp->error_func  	= jmmsp_error_cb;
  btp->packet_user_data = jmmsp;
  btp->error_user_data  = jmmsp;

  return jmmsp;
}


JMMSP*	  
jmmsp_setup (BPeer* bpeer, const GURL* url)
{
  JMMSP* jmmsp;

  g_return_val_if_fail (url, NULL);

  /* Create a new JMMSP */
  jmmsp = g_new0 (JMMSP, 1);
  jmmsp->peer = bpeer;
  jmmsp->url  = gnet_url_clone (url);

  return jmmsp;
}



gboolean
jmmsp_rejoin (JMMSP* jmmsp)
{
  Btp* btp;

  g_return_val_if_fail (jmmsp, TRUE);
  g_return_val_if_fail (!jmmsp->btp, TRUE);

  /* Join the BTP */
  btp = btp_join (jmmsp->peer, jmmsp->url);
  if (!btp)
    return TRUE;
  
  jmmsp->btp = btp;

  /* Initialize BTP */
  btp->packet_func 	= jmmsp_packet_cb;
  btp->error_func  	= jmmsp_error_cb;
  btp->packet_user_data = jmmsp;
  btp->error_user_data  = jmmsp;

  /* We aren't down any more */
  jmmsp->is_down = FALSE;

  return FALSE;
}



void
jmmsp_leave (JMMSP* jmmsp)
{
  g_return_if_fail (jmmsp);
  
  /* Close the BTP */
  if (jmmsp->btp)
    {
      jmmsp_reset (jmmsp);
    }
}


void
jmmsp_delete (JMMSP* jmmsp)
{
  GSList* i;

  if (!jmmsp)
    return;

  gnet_url_delete (jmmsp->url);
  jmmsp_reset (jmmsp);

  /* Call destroy on each handler, then delete handler */
  for (i = jmmsp->handlers; i != NULL; i = i->next)
    {
      JMMSPHandler* handler = (JMMSPHandler*) i->data;
      JMMSPDestroyFunc destroy;

      destroy = handler->funcs->destroy;
      if (destroy)
	(*destroy)(handler, jmmsp);

      g_free (handler);
    }

  g_slist_free (jmmsp->handlers);

  memset (jmmsp, 0, sizeof(*jmmsp));
  g_free (jmmsp);
}


static void
jmmsp_reset (JMMSP* jmmsp)
{
  g_return_if_fail (jmmsp);

  /* Delete the BTP */
  if (jmmsp->btp)
    {
      btp_leave (jmmsp->btp);
      jmmsp->btp = NULL;
    }
}


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

static void
jmmsp_packet_cb (Btp* btp, const void* buffer, 
		 guint length, gpointer user_data)
{
  JMMSP* jmmsp = (JMMSP*) user_data;
  GList* nodes;
  GList* i;

/*    JMMSPP (0x01, "jmmsp_packet_cb\n"); */
/*    fwrite (buffer, length, 1, stderr); */

  nodes = elf_read_list (buffer, length);
  for (i = nodes; i != NULL; i = i->next)
    {
      ElfNode* node = (ElfNode*) i->data;

      if (!strcasecmp(node->name, "query"))
	{
	  gchar* keyword;
	  GSList* i;

	  /* Parse */
	  keyword = elf_get_attribute (node, "keyword");
	  if (!keyword)
	    continue;

	  JMMSPP (0x01, "jmmsp_packet_cb: QUERY %s\n", keyword);

	  /* Dispatch */
	  for (i = jmmsp->handlers; i != NULL; )
	    {
	      JMMSPHandler* handler = (JMMSPHandler*) i->data;
	      JMMSPQueryFunc queryf;

	      i = i->next;

	      queryf = handler->funcs->query;
	      if (queryf && handler->is_enabled)
		(queryf)(handler, jmmsp, keyword);
	    }
	}

      else if (!strcmp(node->name, "reply"))
	{
	  JMMSPReply reply;
	  gchar* keyword;
	  gchar* url;
	  gchar* length;
	  GSList* i;

	  /* Parse */
	  keyword       = elf_get_attribute (node, "keyword");
	  reply.name    = elf_get_attribute (node, "name");
	  url           = elf_get_attribute (node, "url");
	  if (!keyword || !reply.name || !url)
	    continue;
	  reply.url = gnet_url_new (url);
	  if (!reply.url)
	    continue;

	  length = elf_get_attribute (node, "length");
	  if (length)
	    reply.length = atoi(length);
	  else
	    reply.length = -1;

	  JMMSPP (0x01, "jmmsp_packet_cb: REPLY %s\n", reply.name);

	  /* Dispatch */
	  for (i = jmmsp->handlers; i != NULL; )
	    {
	      JMMSPHandler* handler = (JMMSPHandler*) i->data;
	      JMMSPReplyFunc replyf;

	      i = i->next;

	      replyf = handler->funcs->reply;
	      if (replyf && handler->is_enabled)
		(replyf)(handler, jmmsp, keyword, &reply);
	    }

	  gnet_url_delete (reply.url);
	}
    }

  elf_delete_list (nodes);
}


static void
jmmsp_error_cb (Btp* btp, gpointer user_data)
{
  JMMSP* jmmsp = (JMMSP*) user_data;
  GSList* i;

  JMMSPP (1, "jmmsp_error_cb: DOWN\n");

  jmmsp_reset (jmmsp);
  jmmsp->is_down = TRUE;
  
  /* Dispatch */
  for (i = jmmsp->handlers; i != NULL; )
    {
      JMMSPHandler* handler = (JMMSPHandler*) i->data;
      JMMSPErrorFunc error;

      i = i->next;

      error = handler->funcs->error;
      if (error && handler->is_enabled)
	(error)(handler, jmmsp);
    }

  if (jmmsp->error_func)
    (jmmsp->error_func)(jmmsp, jmmsp->user_data);

}




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

void
jmmsp_send_query (JMMSP* jmmsp, const gchar* keyword, gint ttl)
{
  gchar*  buffer;
  guint   length;
  GSList* i;

  g_return_if_fail (jmmsp);
  g_return_if_fail (keyword);

  if (!jmmsp->btp)
    return;
  
  /* Create a query */
  elf_write_format (&buffer, &length, "query", "s", "keyword", keyword);
  g_return_if_fail (buffer);

  /* Send the query */
  btp_send (jmmsp->btp, buffer, length);
  g_free (buffer);

  /* Dispatch */
  for (i = jmmsp->handlers; i != NULL; )
    {
      JMMSPHandler* handler = (JMMSPHandler*) i->data;
      JMMSPQueryFunc queryf;

      i = i->next;

      queryf = handler->funcs->query;
      if (queryf && handler->is_enabled && handler->loopback)
	(queryf)(handler, jmmsp, keyword);
    }
}


void
jmmsp_send_reply (JMMSP* jmmsp, const gchar* keyword, 
		  JMMSPReply* reply, gint ttl)
{
  gchar*  buffer;
  guint   length;
  gchar*  url_str;
  GSList* i;

  g_return_if_fail (jmmsp);
  g_return_if_fail (keyword);
  g_return_if_fail (reply);

  if (!jmmsp->btp)
    return;
  
  url_str = gnet_url_get_nice_string (reply->url);
  g_return_if_fail (url_str);

  /* Create a reply */
  if (reply->length >= 0)
    elf_write_format (&buffer, &length, "reply", "sssd", 
		      "keyword", keyword, 
		      "name", reply->name, 
		      "url", url_str, 
		      "length", reply->length);
  else
    elf_write_format (&buffer, &length, "reply", "sss", 
		      "keyword", keyword, 
		      "name", reply->name, 
		      "url", url_str);

  g_free (url_str);
  g_return_if_fail (buffer);

  /* Send the reply */
  btp_send (jmmsp->btp, buffer, length);
  g_free (buffer);

  /* Dispatch */
  for (i = jmmsp->handlers; i != NULL; )
    {
      JMMSPHandler* handler = (JMMSPHandler*) i->data;
      JMMSPReplyFunc replyf;

      i = i->next;

      replyf = handler->funcs->reply;
      if (replyf && handler->is_enabled && handler->loopback)
	(replyf)(handler, jmmsp, keyword, reply);
    }
}


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


void
jmmsp_add_handler (JMMSP* jmmsp, JMMSPHandler* handler)
{
  g_return_if_fail (jmmsp);
  g_return_if_fail (handler);

  if (g_slist_find (jmmsp->handlers, handler) ||
      g_slist_find (handler->jmmsps, jmmsp))
    return;

  jmmsp->handlers = g_slist_prepend (jmmsp->handlers, handler);
  handler->jmmsps = g_slist_prepend (handler->jmmsps, jmmsp);
}


void
jmmsp_remove_handler (JMMSP* jmmsp, JMMSPHandler* handler)
{
  g_return_if_fail (jmmsp);
  g_return_if_fail (handler);

  jmmsp->handlers = g_slist_remove (jmmsp->handlers, handler);
  handler->jmmsps = g_slist_remove (handler->jmmsps, jmmsp);
}


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

void
jmmsp_handler_init (JMMSPHandler* handler, JMMSPHandlerFuncs* funcs)
{
  g_return_if_fail (handler);
  g_return_if_fail (funcs);

  handler->is_enabled = TRUE;
  handler->funcs      = funcs;
  handler->loopback   = FALSE;
}


void
jmmsp_handler_uninit (JMMSPHandler* handler)
{
  GSList* i;

  g_return_if_fail (handler);

  for (i = handler->jmmsps; i != NULL; i = i->next)
    {
      JMMSP* jmmsp = (JMMSP*) i->data;
      jmmsp->handlers = g_slist_remove (jmmsp->handlers, handler);
    }

  g_slist_free (handler->jmmsps);
  handler->jmmsps = NULL;
}


void
jmmsp_handler_set_enabled (JMMSPHandler* handler, gboolean is_enabled)
{
  handler->is_enabled = is_enabled;

}


gboolean
jmmsp_handler_is_enabled (JMMSPHandler* handler)
{
  return handler->is_enabled;
}


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

void
jmmsp_reply_delete (JMMSPReply* reply)
{
  if (!reply)
    return;

  g_free (reply->name);
  gnet_url_delete (reply->url);

  g_free (reply);
}


JMMSPReply* 
jmmsp_reply_clone (const JMMSPReply* reply)
{
  JMMSPReply* new_reply;

  g_return_val_if_fail (reply, NULL);

  new_reply = g_new0 (JMMSPReply, 1);

  new_reply->name = 	g_strdup(reply->name);
  new_reply->url = 	gnet_url_clone(reply->url);
  new_reply->length = 	reply->length;

  return new_reply;
}


gint
jmmsp_reply_equal (gconstpointer a, gconstpointer b)
{
  const JMMSPReply* ma = (const JMMSPReply*) a;
  const JMMSPReply* mb = (const JMMSPReply*) b;

  if (!SAFESTRCMP(ma->name, mb->name) &&
      gnet_url_equal(ma->url, mb->url) &&
      ma->length == mb->length)
    return 1;

  return 1;
}


guint
jmmsp_reply_hash (gconstpointer p)
{
  const JMMSPReply* r = (const JMMSPReply*) p;
  guint h = 0;

  if (r->name) h ^= g_str_hash (r->name);
  if (r->url)  h ^= gnet_url_hash (r->url);
  h ^= r->length;

  return h;
}



#define SAFESTRLEN(A) ((A)? (strlen(A)) : (0))

guint
jmmsp_reply_size (const JMMSPReply* reply)
{
  g_return_val_if_fail (reply, 0);

  return SAFESTRLEN(reply->name) + 
    SAFESTRLEN(reply->url->protocol) +
    SAFESTRLEN(reply->url->hostname) + 
    SAFESTRLEN(reply->url->resource);
}


gboolean
jmmsp_reply_matches (const JMMSPReply* reply, const gchar* query)
{
  g_return_val_if_fail (reply, FALSE);
  g_return_val_if_fail (query, FALSE);

  /* Ignore NULL queries */
  if (!*query)
    return FALSE;
  
  if (reply->name && strcasestr (reply->name, query))
    return TRUE;

  if (reply->url && reply->url->resource && 
      strcasestr (reply->url->resource, query))
    return TRUE;

  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1