/*
 * Copyright (c) 2006, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 *
 */

/**
 * \file
 *         Radio neighborhood management
 * \author
 *         Adam Dunkels <adam@sics.se>
 */

/**
 * \addtogroup rimeneighbor
 * @{
 */

#include <limits.h>
#include <stdio.h>

#include "contiki.h"
#include "lib/memb.h"
#include "lib/list.h"

#include "net/rime/collect-neighbor.h"
#include "net/rime/collect.h"

#ifdef COLLECT_NEIGHBOR_CONF_MAX_COLLECT_NEIGHBORS
#define MAX_COLLECT_NEIGHBORS COLLECT_NEIGHBOR_CONF_MAX_COLLECT_NEIGHBORS
#else /* COLLECT_NEIGHBOR_CONF_MAX_COLLECT_NEIGHBORS */
#define MAX_COLLECT_NEIGHBORS 8
#endif /* COLLECT_NEIGHBOR_CONF_MAX_COLLECT_NEIGHBORS */

#define RTMETRIC_MAX COLLECT_MAX_DEPTH

MEMB(collect_neighbors_mem, struct collect_neighbor, MAX_COLLECT_NEIGHBORS);

#define MAX_AGE                      180
#define MAX_LE_AGE                   10
#define PERIODIC_INTERVAL            CLOCK_SECOND * 60

#define EXPECTED_CONGESTION_DURATION CLOCK_SECOND * 240
#define CONGESTION_PENALTY           8 * COLLECT_LINK_ESTIMATE_UNIT

#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif

/*---------------------------------------------------------------------------*/
static void
periodic(void *ptr)
{
  struct collect_neighbor_list *neighbor_list;
  struct collect_neighbor *n;

  neighbor_list = ptr;

  /* Go through all collect_neighbors and increase their age. */
  for(n = list_head(neighbor_list->list); n != NULL; n = list_item_next(n)) {
    n->age++;
    n->le_age++;
  }
  for(n = list_head(neighbor_list->list); n != NULL; n = list_item_next(n)) {
    if(n->le_age == MAX_LE_AGE) {
      collect_link_estimate_new(&n->le);
      n->le_age = 0;
    }
    if(n->age == MAX_AGE) {
      memb_free(&collect_neighbors_mem, n);
      list_remove(neighbor_list->list, n);
      n = list_head(neighbor_list->list);
    }
  }
  ctimer_set(&neighbor_list->periodic, PERIODIC_INTERVAL,
             periodic, neighbor_list);
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_init(void)
{
  static uint8_t initialized = 0;
  if(initialized == 0) {
    initialized = 1;
    memb_init(&collect_neighbors_mem);
  }
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_list_new(struct collect_neighbor_list *neighbors_list)
{
  LIST_STRUCT_INIT(neighbors_list, list);
  list_init(neighbors_list->list);
  ctimer_set(&neighbors_list->periodic, CLOCK_SECOND, periodic, neighbors_list);
}
/*---------------------------------------------------------------------------*/
struct collect_neighbor *
collect_neighbor_list_find(struct collect_neighbor_list *neighbors_list,
                           const linkaddr_t *addr)
{
  struct collect_neighbor *n;
  if(neighbors_list == NULL) {
    return NULL;
  }
  for(n = list_head(neighbors_list->list); n != NULL; n = list_item_next(n)) {
    if(linkaddr_cmp(&n->addr, addr)) {
      return n;
    }
  }
  return NULL;
}
/*---------------------------------------------------------------------------*/
int
collect_neighbor_list_add(struct collect_neighbor_list *neighbors_list,
                          const linkaddr_t *addr, uint16_t nrtmetric)
{
  struct collect_neighbor *n;

  if(addr == NULL) {
    PRINTF("collect_neighbor_list_add: attempt to add NULL addr\n");
    return 0;
  }

  if(neighbors_list == NULL) {
    return 0;
  }

  PRINTF("collect_neighbor_add: adding %d.%d\n", addr->u8[0], addr->u8[1]);

  /* Check if the collect_neighbor is already on the list. */
  for(n = list_head(neighbors_list->list); n != NULL; n = list_item_next(n)) {
    if(linkaddr_cmp(&n->addr, addr)) {
      PRINTF("collect_neighbor_add: already on list %d.%d\n",
             addr->u8[0], addr->u8[1]);
      break;
    }
  }

  /* If the collect_neighbor was not on the list, we try to allocate memory
     for it. */
  if(n == NULL) {
    PRINTF("collect_neighbor_add: not on list, allocating %d.%d\n",
           addr->u8[0], addr->u8[1]);
    n = memb_alloc(&collect_neighbors_mem);
    if(n != NULL) {
      list_add(neighbors_list->list, n);
    }
  }

  /* If we could not allocate memory, we try to recycle an old
     neighbor. XXX Should also look for the one with the worst
     rtmetric (not link esimate). XXX Also make sure that we don't
     replace a neighbor with a neighbor that has a worse metric. */
  if(n == NULL) {
    uint16_t worst_rtmetric;
    struct collect_neighbor *worst_neighbor;

    /* Find the neighbor that has the highest rtmetric. This is the
       neighbor that we are least likely to be using in the
       future. But we also need to make sure that the neighbor we are
       currently adding is not worst than the one we would be
       replacing. If so, we don't put the new neighbor on the list. */
    worst_rtmetric = 0;
    worst_neighbor = NULL;

    for(n = list_head(neighbors_list->list);
        n != NULL; n = list_item_next(n)) {
      if(n->rtmetric > worst_rtmetric) {
        worst_neighbor = n;
        worst_rtmetric = n->rtmetric;
      }
    }

    /* Only add this new neighbor if its rtmetric is lower than the
       one it would replace. */
    if(nrtmetric < worst_rtmetric) {
      n = worst_neighbor;
    }
    if(n != NULL) {
      PRINTF("collect_neighbor_add: not on list, not allocated, recycling %d.%d\n",
             n->addr.u8[0], n->addr.u8[1]);
    }
  }

  if(n != NULL) {
    n->age = 0;
    linkaddr_copy(&n->addr, addr);
    n->rtmetric = nrtmetric;
    collect_link_estimate_new(&n->le);
    n->le_age = 0;
    return 1;
  }
  return 0;
}
/*---------------------------------------------------------------------------*/
list_t
collect_neighbor_list(struct collect_neighbor_list *neighbors_list)
{
  if(neighbors_list == NULL) {
    return NULL;
  }

  return neighbors_list->list;
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_list_remove(struct collect_neighbor_list *neighbors_list,
                             const linkaddr_t *addr)
{
  struct collect_neighbor *n;

  if(neighbors_list == NULL) {
    return;
  }

  n = collect_neighbor_list_find(neighbors_list, addr);

  if(n != NULL) {
    list_remove(neighbors_list->list, n);
    memb_free(&collect_neighbors_mem, n);
  }
}
/*---------------------------------------------------------------------------*/
struct collect_neighbor *
collect_neighbor_list_best(struct collect_neighbor_list *neighbors_list)
{
  struct collect_neighbor *n, *best;
  uint16_t rtmetric;

  rtmetric = RTMETRIC_MAX;
  best = NULL;

  if(neighbors_list == NULL) {
    return NULL;
  }

  /*  PRINTF("%d: ", node_id);*/
  PRINTF("collect_neighbor_best: ");

  /* Find the neighbor with the lowest rtmetric + linkt estimate. */
  for(n = list_head(neighbors_list->list); n != NULL; n = list_item_next(n)) {
    PRINTF("%d.%d %d+%d=%d, ",
           n->addr.u8[0], n->addr.u8[1],
           n->rtmetric, collect_neighbor_link_estimate(n),
           collect_neighbor_rtmetric(n));
    if(collect_neighbor_rtmetric_link_estimate(n) < rtmetric) {
      rtmetric = collect_neighbor_rtmetric_link_estimate(n);
      best = n;
    }
  }
  PRINTF("\n");

  return best;
}
/*---------------------------------------------------------------------------*/
int
collect_neighbor_list_num(struct collect_neighbor_list *neighbors_list)
{
  if(neighbors_list == NULL) {
    return 0;
  }

  PRINTF("collect_neighbor_num %d\n", list_length(neighbors_list->list));
  return list_length(neighbors_list->list);
}
/*---------------------------------------------------------------------------*/
struct collect_neighbor *
collect_neighbor_list_get(struct collect_neighbor_list *neighbors_list, int num)
{
  int i;
  struct collect_neighbor *n;

  if(neighbors_list == NULL) {
    return NULL;
  }

  PRINTF("collect_neighbor_get %d\n", num);

  i = 0;
  for(n = list_head(neighbors_list->list); n != NULL; n = list_item_next(n)) {
    if(i == num) {
      PRINTF("collect_neighbor_get found %d.%d\n",
             n->addr.u8[0], n->addr.u8[1]);
      return n;
    }
    i++;
  }
  return NULL;
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_list_purge(struct collect_neighbor_list *neighbors_list)
{
  if(neighbors_list == NULL) {
    return;
  }

  while(list_head(neighbors_list->list) != NULL) {
    memb_free(&collect_neighbors_mem, list_pop(neighbors_list->list));
  }
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_update_rtmetric(struct collect_neighbor *n, uint16_t rtmetric)
{
  if(n != NULL) {
    PRINTF("%d.%d: collect_neighbor_update %d.%d rtmetric %d\n",
           linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],
           n->addr.u8[0], n->addr.u8[1], rtmetric);
    n->rtmetric = rtmetric;
    n->age = 0;
  }
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_tx_fail(struct collect_neighbor *n, uint16_t num_tx)
{
  if(n == NULL) {
    return;
  }
  collect_link_estimate_update_tx_fail(&n->le, num_tx);
  n->le_age = 0;
  n->age = 0;
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_tx(struct collect_neighbor *n, uint16_t num_tx)
{
  if(n == NULL) {
    return;
  }
  collect_link_estimate_update_tx(&n->le, num_tx);
  n->le_age = 0;
  n->age = 0;
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_rx(struct collect_neighbor *n)
{
  if(n == NULL) {
    return;
  }
  collect_link_estimate_update_rx(&n->le);
  n->age = 0;
}
/*---------------------------------------------------------------------------*/
uint16_t
collect_neighbor_link_estimate(struct collect_neighbor *n)
{
  if(n == NULL) {
    return 0;
  }
  if(collect_neighbor_is_congested(n)) {
    /*    printf("Congested %d.%d, sould return %d, returning %d\n",
           n->addr.u8[0], n->addr.u8[1],
           collect_link_estimate(&n->le),
           collect_link_estimate(&n->le) + CONGESTION_PENALTY);*/
    return collect_link_estimate(&n->le) + CONGESTION_PENALTY;
  } else {
    return collect_link_estimate(&n->le);
  }
}
/*---------------------------------------------------------------------------*/
uint16_t
collect_neighbor_rtmetric_link_estimate(struct collect_neighbor *n)
{
  if(n == NULL) {
    return 0;
  }
  return n->rtmetric + collect_link_estimate(&n->le);
}
/*---------------------------------------------------------------------------*/
uint16_t
collect_neighbor_rtmetric(struct collect_neighbor *n)
{
  if(n == NULL) {
    return 0;
  }

  return n->rtmetric;
}
/*---------------------------------------------------------------------------*/
void
collect_neighbor_set_congested(struct collect_neighbor *n)
{
  if(n == NULL) {
    return;
  }
  timer_set(&n->congested_timer, EXPECTED_CONGESTION_DURATION);
}
/*---------------------------------------------------------------------------*/
int
collect_neighbor_is_congested(struct collect_neighbor *n)
{
  if(n == NULL) {
    return 0;
  }

  if(timer_expired(&n->congested_timer)) {
    return 0;
  } else {
    return 1;
  }
}
/*---------------------------------------------------------------------------*/
/** @} */
