/* EtherApe
 * Copyright (C) 2001 Juan Toledo, Riccardo Ghetta
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "appdata.h"
#include "node_id.h"
#include "util.h"

/***************************************************************************
 *
 * node_id_t implementation
 *
 **************************************************************************/

void node_id_clear(node_id_t *a)
{
  memset(a, 0, sizeof(*a));
  a->node_type = APEMODE_DEFAULT;
}

/* Comparison function used to order the (GTree *) nodes
 * and canvas_nodes heard on the network */
gint node_id_compare(const node_id_t *na, const node_id_t *nb)
{
  const guint8 *ga;
  const guint8 *gb;
  int i;

  g_assert(na != NULL);
  g_assert(nb != NULL);
  if (na->node_type < nb->node_type)
    return -1;
  else if (na->node_type > nb->node_type)
    return 1;

  /* same node type, compare */
  switch (na->node_type)
  {
      case APEMODE_DEFAULT:
        return 0; /* default has only one value */
      case IP:
        ga = na->addr.ip.all8;
        gb = nb->addr.ip.all8;
        i = sizeof(na->addr.ip.all8);
        break;
      case TCP:
        ga = na->addr.tcp.host.all8;
        gb = nb->addr.tcp.host.all8;
        i = sizeof(na->addr.tcp.host.all8)+sizeof(na->addr.tcp.port);
        break;
      default:
        g_error(_("Unsupported ape mode in node_id_compare"));
      /* Fallthrough, default to compare-by-MAC */
      case LINK6:
        ga = na->addr.eth;
        gb = nb->addr.eth;
        i = sizeof(na->addr.eth);
        break;
  }

  i = memcmp(ga, gb, i);
  if (i > 1)
    i = 1;
  else if (i < -1)
    i = -1;
  return i;
}                               /* node_id_compare */

/* returns a newly allocated string with a human-readable id */
gchar *node_id_str(const node_id_t *id)
{
  gchar *msg;
  g_assert(id);

  switch (id->node_type)
  {
      case APEMODE_DEFAULT:
        msg = g_strdup("00:00:00:00:00:00");
        break;
      case LINK6:
        msg = g_strdup(ether_to_str(id->addr.eth));
        break;
      case IP:
        msg = g_strdup(address_to_str(&id->addr.ip));
        break;
      case TCP:
        msg = g_strdup_printf("%s:%u", address_to_str(&id->addr.tcp.host),
                              id->addr.tcp.port);
        break;
      default:
        g_error("node_id_type %d unknown", (int)(id->node_type));
        break;
  }

  return msg;
}


gchar *node_id_dump(const node_id_t *id)
{
  gchar *msg;
  g_assert(id);
  switch (id->node_type)
  {
      case APEMODE_DEFAULT:
        msg = g_strdup_printf("NONE: 00:00:00:00:00:00");
        break;
      case LINK6:
        msg = g_strdup_printf("LINK: %s", ether_to_str(id->addr.eth));
        break;
      case IP:
        msg = g_strdup_printf("%s: %s", type_to_str(&id->addr.ip),
                              address_to_str(&id->addr.ip));
        break;
      case TCP:
        msg = g_strdup_printf("TCP/UDP: %s:%u",
                              address_to_str(&id->addr.tcp.host),
                              id->addr.tcp.port);
        break;
      default:
        msg = g_strdup_printf("node_id_type %d unknown", (int)(id->node_type));
        break;
  }

  return msg;
}

gchar *node_id_xml(const node_id_t *id)
{
  gchar *msg;
  gchar *msgb;
  gchar *xml;
  g_assert(id);
  switch (id->node_type)
  {
      case APEMODE_DEFAULT:
        msg = g_strdup_printf(" ");
        break;
      case LINK6:
        msg = xmltag_escaped("link-id", "%s", ether_to_str(id->addr.eth));
        break;
      case IP:
        msg = xmltag_escaped(type_to_str(&id->addr.ip), "%s", address_to_str(&id->addr.ip));
        break;
      case TCP:
        xml = xmltag_escaped(type_to_str(&id->addr.tcp.host),
                     "%s",
                     address_to_str(&id->addr.tcp.host));
        msgb = xmltag_escaped("port", "%u", id->addr.tcp.port);
        msg = g_strdup_printf("%s %s", xml, msgb);
        g_free(xml);
        g_free(msgb);
        break;
      default:
        msg = xmltag("unknown", "node_id_type %d unknown", (int)(id->node_type));
        break;
  }
  xml = xmltag("id", "\n%s", msg);
  g_free(msg);

  return xml;
}

gchar *node_id_json(const node_id_t *id)
{
  gchar *msg;
  gchar *json;
  g_assert(id);
  switch (id->node_type)
  {
    case APEMODE_DEFAULT:
      msg = g_strdup_printf(" ");
      break;
    case LINK6:
      gchar *addr_eth = json_escape(ether_to_str(id->addr.eth));
      msg = jsontag("link-id", "\"%s\"", addr_eth);
      g_free(addr_eth);
      break;
    case IP:
      gchar *addr_ip = json_escape(address_to_str(&id->addr.ip));
      msg = jsontag(type_to_str(&id->addr.ip), "\"%s\"", addr_ip);
      g_free(addr_ip);
      break;
    case TCP:
      gchar *addr_tcp_host = json_escape(address_to_str(&id->addr.tcp.host));
      json = jsontag(type_to_str(&id->addr.tcp.host), "\"%s\"", addr_tcp_host);
      gchar *msgb = jsontag("port", "%u", id->addr.tcp.port);
      msg = g_strdup_printf("%s,%s", json, msgb);
      g_free(addr_tcp_host);
      g_free(json);
      g_free(msgb);
      break;
    default:
      msg = jsontag("unknown", "node_id_type %d unknown", (int)(id->node_type));
      break;
  }
  json = jsontag("id", "{%s}", msg);
  g_free(msg);

  return json;
}

/***************************************************************************
 *
 * name_t implementation
 *
 **************************************************************************/
static long node_name_count = 0;

long active_names(void)
{
  return node_name_count;
}

name_t *node_name_create(const node_id_t *node_id)
{
  name_t *name;

  g_assert(node_id);

  name = g_malloc(sizeof(name_t));
  g_assert(name);

  name->node_id = *node_id;
  name->accumulated = 0;
  name->numeric_name = NULL;
  name->res_name = NULL;
  ++node_name_count;
  {
    gchar *gg = node_id_dump(node_id);
    g_my_debug("node name created (%p): >%s<, total %ld", name,
               gg, node_name_count);
    g_free(gg);
  }
  return name;
}

void node_name_delete(name_t *name)
{
  if (name) {
    {
      gchar *gg = node_id_dump(&name->node_id);
      g_my_debug("node name delete (%p): >%s<, total %ld", name,
                 gg, node_name_count-1);
      g_free(gg);
    }
    if (name->res_name)
      g_string_free(name->res_name, TRUE);
    if (name->numeric_name)
      g_string_free(name->numeric_name, TRUE);
    g_free(name);
    --node_name_count;
  }
}

void node_name_assign(name_t *name, const gchar *nm, const gchar *num_nm,
                      gdouble sz)
{
  if (DEBUG_ENABLED) {
    gchar *msgid = node_id_dump(&name->node_id);
    g_my_debug(" node_name_assign: id %s, name %s, num.name %s\n",
               msgid, (nm) ? nm : "<none>", num_nm);
    g_free(msgid);
  }
  g_assert(name);
  if (!name->numeric_name)
    name->numeric_name = g_string_new(num_nm);
  else
    g_string_assign(name->numeric_name, num_nm);

  if (nm) {
    if (!name->res_name)
      name->res_name = g_string_new(nm);
    else
      g_string_assign(name->res_name, nm);
  }
  name->accumulated += sz;
}

gchar *node_name_dump(const name_t *name)
{
  gchar *msg;
  gchar *nid;
  if (!name)
    return g_strdup("name_t NULL");

  nid = node_id_dump(&name->node_id);
  msg = g_strdup_printf("node id: %s, name: %s, numeric_name: %s, solved: %d, "
                        "accumulated %f",
                        nid,
                        (name->res_name) ? name->res_name->str : "<none>",
                        name->numeric_name->str,
                        name->res_name != NULL,
                        name->accumulated);
  g_free(nid);
  return msg;
}

gchar *node_name_xml(const name_t *name)
{
  if (!name)
    return xmltag("name", "");

  gchar *nid = node_id_xml(&name->node_id);
  gchar *nres;
  if (name->res_name)
    nres = xmltag_escaped("resolved_name", "%s", name->res_name->str);
  else
    nres = g_strdup("");
  gchar *numeric_name = xmltag_escaped("numeric_name", "%s", name->numeric_name->str);
  gchar *msg = xmltag("name",
                      "\n%s%s%s\n"
                      "<accumulated>%.0f</accumulated>",
                      nid,
                      nres,
                      numeric_name,
                      name->accumulated);
  g_free(numeric_name);
  g_free(nid);
  g_free(nres);
  return msg;
}

gchar *node_name_json(const name_t *name)
{
  if (!name)
    return jsontag_arr_elm("{}");

  gchar *nid = node_id_json(&name->node_id);
  gchar *nres;
  if (name->res_name) {
    gchar *res_name = json_escape(name->res_name->str);
    nres = jsontag("resolved_name", "\"%s\"", res_name);
    g_free(res_name);
  } else {
    nres = g_strdup("\"resolved_name\":\"\"");
  }
  gchar *numeric_name = json_escape(name->numeric_name->str);
  gchar *msg = jsontag_arr_elm("{%s,%s,\"numeric_name\":\"%s\","
                               "\"accumulated\":%.0f}",
                               nid,
                               nres,
                               numeric_name,
                               name->accumulated);
  g_free(numeric_name);
  g_free(nid);
  g_free(nres);
  return msg;
}

/* compares by node id */
gint node_name_id_compare(const name_t *a, const name_t *b)
{
  g_assert(a != NULL);
  g_assert(b != NULL);
  return node_id_compare(&a->node_id, &b->node_id);
}

/* Comparison function to sort protocols by their accumulated traffic */
gint node_name_freq_compare(gconstpointer a, gconstpointer b)
{
  const name_t *name_a, *name_b;

  g_assert(a != NULL);
  g_assert(b != NULL);

  name_a = (const name_t *)a;
  name_b = (const name_t *)b;

  if (name_a->accumulated > name_b->accumulated)
    return -1;
  if (name_a->accumulated < name_b->accumulated)
    return 1;
  return 0;
}
