/**
 * Delete an address
 *
 * If session != 0, just the session is deleted, the address itself still exists
 * If session == 0, remove full address
 * If session == 0 and addrlen == 0, destroy inbound address
 *
 * @param cls unused
 * @param key unused
 * @param value the 'struct ATS_Address'
 * @return GNUNET_OK (continue to iterate)
 */
static int
destroy_by_session_id (void *cls, const struct GNUNET_HashCode * key, void *value)
{
  const struct ATS_Address *info = cls;
  struct ATS_Address *aa = value;

  GNUNET_assert (0 ==
                 memcmp (&aa->peer, &info->peer,
                         sizeof (struct GNUNET_PeerIdentity)));
  /* session == 0, remove full address  */
  if ((info->session_id == 0) && (0 == strcmp (info->plugin, aa->plugin)) &&
      (aa->addr_len == info->addr_len) &&
      (0 == memcmp (info->addr, aa->addr, aa->addr_len)))
  {

    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Deleting address for peer `%s': `%s' %u\n",
                GNUNET_i2s (&aa->peer), aa->plugin, aa->session_id);

    if (GNUNET_YES == destroy_address (aa))
      recalculate_assigned_bw ();
    return GNUNET_OK;
  }
  /* session != 0, just remove session */
  if (aa->session_id != info->session_id)
    return GNUNET_OK;           /* irrelevant */
  if (aa->session_id != 0)
    GNUNET_break (0 == strcmp (info->plugin, aa->plugin));
  /* session died */
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Deleting session for peer `%s': `%s' %u\n",
              GNUNET_i2s (&aa->peer), aa->plugin, aa->session_id);
  aa->session_id = 0;

  if (GNUNET_YES == aa->active)
  {
    aa->active = GNUNET_NO;
    active_addr_count--;
    recalculate_assigned_bw ();
  }

  /* session == 0 and addrlen == 0 : destroy address */
  if (aa->addr_len == 0)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Deleting session and address for peer `%s': `%s' %u\n",
                GNUNET_i2s (&aa->peer), aa->plugin, aa->session_id);
    (void) destroy_address (aa);
  }
  else
  {
    /* session was set to 0, update address */
#if HAVE_LIBGLPK
  if (ats_mode == MLP)
    GAS_mlp_address_update (mlp, addresses, aa);
#endif
  }

  return GNUNET_OK;
}
int
GAS_addresses_in_use (const struct GNUNET_PeerIdentity *peer,
                      const char *plugin_name, const void *plugin_addr,
                      size_t plugin_addr_len, uint32_t session_id, int in_use)
{
#if DEBUG_ATS
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Received `%s' message for peer `%s': %i\n", "ADDRESS_IN_USE",
              GNUNET_i2s (peer), in_use);
#endif

  struct ATS_Address *old;

  if (GNUNET_NO == running)
    return GNUNET_SYSERR;

  old = lookup_address (peer, plugin_name, plugin_addr, plugin_addr_len, session_id, NULL, 0);
  if (NULL == old)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Trying to set unknown address `%s', %s %u %s \n",
                GNUNET_i2s (peer),
                plugin_name, session_id,
                (GNUNET_NO == in_use) ? "NO" : "YES");
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if (old->used == in_use)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Address in use called multiple times for peer `%s': %s -> %s \n",
                GNUNET_i2s (peer),
                (GNUNET_NO == old->used) ? "NO" : "YES",
                (GNUNET_NO == in_use) ? "NO" : "YES");
    return GNUNET_SYSERR;
  }
  old->used = in_use;
#if HAVE_LIBGLPK
  if (ats_mode == MLP)
     GAS_mlp_address_update (mlp, addresses, old);
#endif
  return GNUNET_OK;
}
static void
check (void *cls, char *const *args, const char *cfgfile,
       const struct GNUNET_CONFIGURATION_Handle *cfg)
{
#if !HAVE_LIBGLPK
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "GLPK not installed!");
  ret = 1;
  return;
#endif
  struct ATS_Address addr[10];
  struct ATS_PreferedAddress *res[10];
  struct MLP_information *mlpi;
  struct GAS_MLP_SolutionContext ctx;

  stats = GNUNET_STATISTICS_create("ats", cfg);

  addresses = GNUNET_CONTAINER_multihashmap_create (10);

  mlp = GAS_mlp_init (cfg, NULL, MLP_MAX_EXEC_DURATION, MLP_MAX_ITERATIONS);
  mlp->auto_solve = GNUNET_NO;

  struct GNUNET_PeerIdentity p[10];

  /* Creating peer 1 */
  GNUNET_CRYPTO_hash_create_random(GNUNET_CRYPTO_QUALITY_WEAK, &p[0].hashPubKey);

  /* Creating peer 1 address 1 */
  addr[0].peer.hashPubKey = p[0].hashPubKey;
  struct GNUNET_ATS_Information a1_ats[3];
  set_ats (&a1_ats[0], GNUNET_ATS_QUALITY_NET_DISTANCE, 1);
  set_ats (&a1_ats[1], GNUNET_ATS_QUALITY_NET_DELAY, 0);
  set_ats (&a1_ats[2], GNUNET_ATS_ARRAY_TERMINATOR, 0);
  create_address (&addr[0], "dummy", 3, &a1_ats[0]);
  addr[0].atsp_network_type = GNUNET_ATS_NET_LAN;

  GNUNET_CONTAINER_multihashmap_put(addresses, &addr[0].peer.hashPubKey, &addr[0], GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);

  /* Add peer 1 address 1 */
  GAS_mlp_address_update (mlp, addresses, &addr[0]);
  mlpi = addr[0].mlp_information;

  GNUNET_assert (mlp != NULL);
  GNUNET_assert (mlp->addr_in_problem == 1);

  /* Update an peer 1 address 1  */
  set_ats (&a1_ats[1], GNUNET_ATS_QUALITY_NET_DELAY, 20);
  GAS_mlp_address_update (mlp, addresses, &addr[0]);
  GNUNET_assert (mlp->addr_in_problem == 1);


  /* Update an peer 1 address 1  */
  set_ats (&a1_ats[1], GNUNET_ATS_QUALITY_NET_DELAY, 10);
  GAS_mlp_address_update (mlp, addresses, &addr[0]);
  GNUNET_assert (mlp->addr_in_problem == 1);

  /* Update an peer 1 address 1  */
  set_ats (&a1_ats[1], GNUNET_ATS_QUALITY_NET_DELAY, 10);
  GAS_mlp_address_update (mlp, addresses, &addr[0]);
  GNUNET_assert (mlp->addr_in_problem == 1);

  /* Update an peer 1 address 1  */
  set_ats (&a1_ats[1], GNUNET_ATS_QUALITY_NET_DELAY, 30);
  GAS_mlp_address_update (mlp, addresses, &addr[0]);
  GNUNET_assert (mlp->addr_in_problem == 1);


  GNUNET_assert (GNUNET_OK == GAS_mlp_solve_problem(mlp, &ctx));
  GNUNET_assert (GNUNET_OK == ctx.lp_result);
  GNUNET_assert (GNUNET_OK == ctx.mlp_result);

  res[0] = GAS_mlp_get_preferred_address(mlp, addresses, &p[0]);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Preferred address `%s' outbound bandwidth: %u Bps\n",res[0]->address->plugin, res[0]->bandwidth_out);
  GNUNET_free (res[0]);

  /* Delete an address */
  GNUNET_CONTAINER_multihashmap_remove (addresses, &addr[0].peer.hashPubKey, &addr[0]);
  GAS_mlp_address_delete (mlp, addresses, &addr[0]);

  GNUNET_assert (mlp->addr_in_problem == 0);

  GAS_mlp_done (mlp);

  GNUNET_free (addr[0].plugin);
  GNUNET_CONTAINER_multihashmap_destroy (addresses);
  GNUNET_STATISTICS_destroy(stats, GNUNET_NO);

  ret = 0;
  return;
}
static void
check (void *cls, char *const *args, const char *cfgfile,
       const  struct GNUNET_CONFIGURATION_Handle *cfg)
{
  unsigned int c = 0;
  unsigned int c2 = 0;
  unsigned int ca = 0;
  int update = GNUNET_NO;
  int range = GNUNET_NO;
  int res;

#if !HAVE_LIBGLPK
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "GLPK not installed!");
  ret = 1;
  return;
#endif

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up %u peers with %u addresses per peer\n", peers, addresses);

  mlp = GAS_mlp_init (cfg, NULL, MLP_MAX_EXEC_DURATION, MLP_MAX_ITERATIONS);
  if (NULL == mlp)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to init MLP\n");
    ret = 1;
    if (GNUNET_SCHEDULER_NO_TASK != shutdown_task)
      GNUNET_SCHEDULER_cancel(shutdown_task);
    shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);
  }

  if (peers == 0)
    peers = DEF_PEERS;
  if (addresses == 0)
    addresses = DEF_ADDRESSES_PER_PEER;

  p = GNUNET_malloc (peers * sizeof (struct ATS_Peer));
  a = GNUNET_malloc (peers * addresses * sizeof (struct ATS_Address));

  amap = GNUNET_CONTAINER_multihashmap_create(addresses * peers, GNUNET_NO);

  mlp->auto_solve = GNUNET_NO;
  if (start == 0)
      start = 0;
  if (end == 0)
      end = -1;
  if ((start != -1) && (end != -1))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Solving problem starting from %u to %u\n", start , end);
    range = GNUNET_YES;
  }
  else
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Solving problem for %u peers\n", peers);

  if ((update_percentage >= 0) && (update_percentage <= 100))
  {
    update = GNUNET_YES;
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Benchmarking with existing presolution and %u%% updated addresses\n", update_percentage);
  }
  else if ((update_percentage > 100) && (update_percentage != UINT_MAX))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Invalid percentage: %u\n", update_percentage);
    ret = 1;
    return;
  }

  for (c=0; c < peers; c++)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up peer %u\n", c);
    GNUNET_CRYPTO_hash_create_random(GNUNET_CRYPTO_QUALITY_NONCE, &p[c].id.hashPubKey);

    for (c2=0; c2 < addresses; c2++)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up address %u for peer %u\n", c2, c);
      /* Setting required information */
      a[ca].mlp_information = NULL;
      a[ca].prev = NULL;
      a[ca].next = NULL;

      /* Setting address */
      a[ca].peer = p[c].id;
      a[ca].plugin = GNUNET_strdup("test");
      a[ca].atsp_network_type = GNUNET_ATS_NET_LOOPBACK;

      a[ca].ats = GNUNET_malloc (DEF_ATS_VALUES * sizeof (struct GNUNET_ATS_Information));
      a[ca].ats[0].type = GNUNET_ATS_QUALITY_NET_DELAY;
      a[ca].ats[0].value = GNUNET_CRYPTO_random_u32(GNUNET_CRYPTO_QUALITY_WEAK, DEF_ATS_MAX_DELAY);
      a[ca].ats[1].type = GNUNET_ATS_QUALITY_NET_DISTANCE;
      a[ca].ats[1].value = GNUNET_CRYPTO_random_u32(GNUNET_CRYPTO_QUALITY_WEAK, DEF_ATS_MAX_DISTANCE);
      a[ca].ats_count = 2;
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Setting up address %u\n", ca);
      GNUNET_CONTAINER_multihashmap_put (amap, &a[ca].peer.hashPubKey, &a[ca], GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
      GAS_mlp_address_update(mlp, amap, &a[ca]);
      ca++;
    }

    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Problem contains %u peers and %u adresses\n", mlp->c_p, mlp->addr_in_problem);

    if (((GNUNET_YES == range) && (((start >= 0) && ((c+1) >= start)) && (c <= end))) || ((c+1) == peers))
    {
      GNUNET_assert ((c+1) == mlp->c_p);
      GNUNET_assert ((c+1) * addresses == mlp->addr_in_problem);

      /* Solving the problem */
      struct GAS_MLP_SolutionContext ctx;

      res = GAS_mlp_solve_problem(mlp, &ctx);

      if (GNUNET_NO == update)
      {
        if (GNUNET_OK == res)
        {
          GNUNET_assert (GNUNET_OK == ctx.lp_result);
          GNUNET_assert (GNUNET_OK == ctx.mlp_result);
          if (GNUNET_YES == numeric)
            printf ("%u;%u;%llu;%llu\n",mlp->c_p, mlp->addr_in_problem, (unsigned long long) ctx.lp_duration.rel_value, (unsigned long long) ctx.mlp_duration.rel_value);
          else
            GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Problem solved for %u peers with %u address successfully (LP: %llu ms / MLP: %llu ms)\n",
                mlp->c_p, mlp->addr_in_problem, ctx.lp_duration.rel_value, ctx.mlp_duration.rel_value);
        }
        else
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Solving problem with %u peers and %u addresses failed\n", c, c2);
      }
      else
      {
        struct GAS_MLP_SolutionContext uctx;
        /* Update addresses */
        update_addresses (a, (c+1) * c2, update_percentage);

        /* Solve again */
        res = GAS_mlp_solve_problem(mlp, &uctx);

        if (GNUNET_OK == res)
        {
          GNUNET_assert (GNUNET_OK == uctx.lp_result);
          GNUNET_assert (GNUNET_OK == uctx.mlp_result);
          if (GNUNET_YES == numeric)
            printf ("%u;%u;%llu;%llu;%llu;%llu\n",mlp->c_p, mlp->addr_in_problem,
                (unsigned long long) ctx.lp_duration.rel_value, (unsigned long long) ctx.mlp_duration.rel_value,
                (unsigned long long) uctx.lp_duration.rel_value, (unsigned long long) uctx.mlp_duration.rel_value);
          else
            GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Updated problem solved for %u peers with %u address successfully (Initial: LP/MLP: %llu/%llu ms, Update: %llu/%llu ms)\n",
                mlp->c_p, mlp->addr_in_problem,
                (unsigned long long) ctx.lp_duration.rel_value, (unsigned long long) ctx.mlp_duration.rel_value,
                (unsigned long long) uctx.lp_duration.rel_value, (unsigned long long) uctx.mlp_duration.rel_value);
        }
        else
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Solving updated problem with %u peers and %u addresses failed\n", c, c2);
      }
    }
  }

  if (GNUNET_SCHEDULER_NO_TASK != shutdown_task)
    GNUNET_SCHEDULER_cancel(shutdown_task);
  shutdown_task = GNUNET_SCHEDULER_add_now (&do_shutdown, NULL);

}
static void
update_addresses (struct ATS_Address * a, unsigned int addrs, unsigned int percentage)
{
  if (percentage == 0)
    return;

  unsigned int ua = (addrs) * ((float) percentage / 100);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Updating %u of %u addresses per peer\n", ua, addrs);

  unsigned int updated[addrs];
  unsigned int u_types[DEF_ATS_VALUES];
  unsigned int updates = 0;
  unsigned int u_type = 0;
  unsigned int u_val = 0;
  unsigned int cur = 0;

  u_types[0] = 0;
  u_types[1] = 0;

  for (cur = 0; cur < addrs; cur ++)
  {
    updated[cur] = 0;
  }
  cur = 0;

  while (updates < ua)
  {
    cur = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, addrs);
    if (0 == updated[cur])
    {
      u_type = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, DEF_ATS_VALUES);
      switch (u_type) {
        case 0:
            do
            {
            u_val = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, DEF_ATS_MAX_DELAY);
            GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating DELAY from %u to %u\n",a[cur].ats[u_type].value, u_val);
            }
            while (a[cur].ats[u_type].value == u_val);
          break;
        case 1:
          do
          {
            u_val = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, DEF_ATS_MAX_DISTANCE);
            GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating DISTANCE from %u to %u\n",a[cur].ats[u_type].value, u_val);
          }
          while (a[cur].ats[u_type].value == u_val);
          break;
        default:
          GNUNET_break (0);
          break;
      }
      u_types[u_type]++;

      a[cur].ats[u_type].value = u_val;
      updated[cur] = 1;
      GAS_mlp_address_update(mlp, amap, &a[cur]);
      updates++;
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Updated %u delay and %u distance values\n", u_types[0], u_types[1]);

}
void
GAS_addresses_update (const struct GNUNET_PeerIdentity *peer,
                      const char *plugin_name, const void *plugin_addr,
                      size_t plugin_addr_len, uint32_t session_id,
                      const struct GNUNET_ATS_Information *atsi,
                      uint32_t atsi_count)
{
  struct ATS_Address *aa;
  struct ATS_Address *old;
  uint32_t i;

  if (GNUNET_NO == running)
    return;

  GNUNET_assert (NULL != addresses);

  aa = create_address (peer,
                       plugin_name,
                       plugin_addr, plugin_addr_len,
                       session_id);

  aa->mlp_information = NULL;
  aa->ats = GNUNET_malloc (atsi_count * sizeof (struct GNUNET_ATS_Information));
  aa->ats_count = atsi_count;
  memcpy (aa->ats, atsi, atsi_count * sizeof (struct GNUNET_ATS_Information));

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating address for peer `%s' %u\n",
              GNUNET_i2s (peer),
              session_id);

  /* Get existing address or address with session == 0 */
  old = find_address (peer, aa);
  if (old == NULL)
  {
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_CONTAINER_multihashmap_put (addresses,
                                                      &peer->hashPubKey, aa,
                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
#if DEBUG_ATS
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Added new address for peer `%s' %X\n",
                GNUNET_i2s (peer), aa);
#endif
    old = aa;
  }
  else
  {
#if DEBUG_ATS
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Updated existing address for peer `%s' %p old session %u new session %u\n",
                GNUNET_i2s (peer), old,
                old->session_id, session_id);
#endif
    GNUNET_free_non_null (old->ats);
    old->session_id = session_id;
    old->ats = NULL;
    old->ats_count = 0;
    old->ats = aa->ats;
    old->ats_count = aa->ats_count;
    GNUNET_free (aa->plugin);
    GNUNET_free (aa);
  }
  for (i = 0; i < atsi_count; i++)
    switch (ntohl (atsi[i].type))
    {
    case GNUNET_ATS_UTILIZATION_UP:
      old->atsp_utilization_out.value__ = atsi[i].value;
      break;
    case GNUNET_ATS_UTILIZATION_DOWN:
      old->atsp_utilization_in.value__ = atsi[i].value;
      break;
    case GNUNET_ATS_QUALITY_NET_DELAY:
      old->atsp_latency.rel_value = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_QUALITY_NET_DISTANCE:
      old->atsp_distance = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_WAN:
      old->atsp_cost_wan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_LAN:
      old->atsp_cost_lan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_WLAN:
      old->atsp_cost_wlan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_NETWORK_TYPE:
      old->atsp_network_type = ntohl (atsi[i].value);
      break;

    default:
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Received unsupported ATS type %u\n", ntohl (atsi[i].type));
      GNUNET_break (0);
      break;
    }
#if HAVE_LIBGLPK
  if (ats_mode == MLP)
    GAS_mlp_address_update (mlp, addresses, old);
#endif
}
void
GAS_addresses_update (const struct GNUNET_PeerIdentity *peer,
                      const char *plugin_name, const void *plugin_addr,
                      size_t plugin_addr_len, uint32_t session_id,
                      const struct GNUNET_ATS_Information *atsi,
                      uint32_t atsi_count)
{
  struct ATS_Address *old;
  uint32_t i;

  if (GNUNET_NO == running)
    return;

  GNUNET_assert (NULL != addresses);

  /* Get existing address */
  old = lookup_address (peer, plugin_name, plugin_addr, plugin_addr_len,
                       session_id, atsi, atsi_count);
  if (old == NULL)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Tried to update unknown address for peer `%s' `%s' session id %u\n",
                GNUNET_i2s (peer), plugin_name, session_id);
    GNUNET_break (0);
    return;
  }

  for (i = 0; i < atsi_count; i++)
    switch (ntohl (atsi[i].type))
    {
    case GNUNET_ATS_UTILIZATION_UP:
      old->atsp_utilization_out.value__ = atsi[i].value;
      break;
    case GNUNET_ATS_UTILIZATION_DOWN:
      old->atsp_utilization_in.value__ = atsi[i].value;
      break;
    case GNUNET_ATS_QUALITY_NET_DELAY:
      old->atsp_latency.rel_value = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_QUALITY_NET_DISTANCE:
      old->atsp_distance = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_WAN:
      old->atsp_cost_wan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_LAN:
      old->atsp_cost_lan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_COST_WLAN:
      old->atsp_cost_wlan = ntohl (atsi[i].value);
      break;
    case GNUNET_ATS_NETWORK_TYPE:
      old->atsp_network_type = ntohl (atsi[i].value);
      break;

    default:
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Received unsupported ATS type %u\n", ntohl (atsi[i].type));
      GNUNET_break (0);
      break;
    }
#if HAVE_LIBGLPK
  if (ats_mode == MLP)
    GAS_mlp_address_update (mlp, addresses, old);
#endif
}