/*
 * The olsr.org Optimized Link-State Routing daemon (olsrd)
 *
 * (c) by the OLSR project
 *
 * See our Git repository to find out who worked on this file
 * and thus is a copyright holder on it.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * 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.
 * * Neither the name of olsr.org, olsrd 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT OWNER 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.
 *
 * Visit http://www.olsr.org for more information.
 *
 * If you find this software useful feel free to make a donation
 * to the project. For more information see the website or contact
 * the copyright holders.
 *
 */

#include <math.h>

#include "common.h"
#include "nodes.h"

void
init_nodes(void)
{

  nodes.next = &nodes;
  nodes.prev = &nodes;
}

/*
 *Insert a new node in the list
 *NB! The list is NOT checked for duplicates!!
 */
struct node *
insert_node(struct node *n, olsr_u8_t vtime)
{
  struct node *new_node;

  printf("Inserting node %s\n", ip_to_string((union olsr_ip_addr *)&n->addr));

  if ((new_node = malloc(sizeof(struct node))) == 0) {
    fprintf(stderr, "OUT OF MEMORY!\n");
    exit(EXIT_FAILURE);
  }

  memcpy(new_node, n, sizeof(struct node));

  /* queue */
  nodes.next->prev = new_node;
  new_node->next = nodes.next;
  nodes.next = new_node;
  new_node->prev = &nodes;

  new_node->hna.next = &new_node->hna;
  new_node->hna.prev = &new_node->hna;
  new_node->mid.next = &new_node->mid;
  new_node->mid.prev = &new_node->mid;
  new_node->mpr.next = &new_node->mpr;
  new_node->mpr.prev = &new_node->mpr;

  update_timer_node(&n->addr, vtime);

  return new_node;
}

/**
 *Add a new node to the set
 */
int
add_node(union olsr_ip_addr *node, olsr_u8_t vtime)
{
  struct node new;
  struct node *tmp_nodes;
  struct timeval tmp_timer;
  double dbl_time;
  olsr_u32_t time_value;
  struct mid *tmp_mid;

  dbl_time = me_to_double(vtime);
  time_value = (olsr_u32_t) dbl_time *1000;

  tmp_timer.tv_sec = time_value / 1000;
  tmp_timer.tv_usec = (time_value - (tmp_timer.tv_sec * 1000)) * 1000;

  /* Check if node exists */
  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      //printf("updating timer for node %s\n", ip_to_string(node));
      //printf("Updatimng timer for: %s\n", ip_to_string(node));
      //printf("Secs: %d, usecs: %d\n", (int)tmp_timer.tv_sec, (int)tmp_timer.tv_usec);
      gettimeofday(&now, (struct timezone *)NULL);
      timeradd(&now, &tmp_timer, &tmp_nodes->timer);
      return 0;
    }
    /* Check MID */
    for (tmp_mid = tmp_nodes->mid.next; tmp_mid != &tmp_nodes->mid; tmp_mid = tmp_mid->next) {
      if (memcmp(&tmp_mid->alias, node, ipsize) == 0) {
        //printf("updating timer for node %s\n", ip_to_string(node));
        //printf("Updatimng timer for (MID): %s\n", ip_to_string(&tmp_nodes->addr));
        //printf("Secs: %d, usecs: %d\n", (int)tmp_timer.tv_sec, (int)tmp_timer.tv_usec);
        gettimeofday(&now, (struct timezone *)NULL);
        timeradd(&now, &tmp_timer, &tmp_nodes->timer);
        return 0;
      }
    }
  }

  /* New node */
  memset(&new, 0, sizeof(struct node));
  memcpy(&new.addr, node, ipsize);
  new.display = 1;
  printf("1:");
  insert_node(&new, vtime);
  update_nodes_list(&new);

  return 1;
}

int
update_timer_node(union olsr_ip_addr *node, olsr_u8_t vtime)
{
  struct node *tmp_nodes;
  struct timeval tmp_timer;
  double dbl_time;
  olsr_u32_t time_value;

  dbl_time = me_to_double(vtime);
  time_value = (olsr_u32_t) dbl_time *1000;

  tmp_timer.tv_sec = time_value / 1000;
  tmp_timer.tv_usec = (time_value - (tmp_timer.tv_sec * 1000)) * 1000;

  //printf("Updatimng timer for: %s\n", ip_to_string(node));
  //printf("Secs: %d, usecs: %d\n", (int)tmp_timer.tv_sec, (int)tmp_timer.tv_usec);

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      //printf("updating timer for node %s\n", ip_to_string(node));
      gettimeofday(&now, (struct timezone *)NULL);
      timeradd(&now, &tmp_timer, &tmp_nodes->timer);
      if (tmp_nodes->display)
        update_nodes_list(tmp_nodes);
      return 1;
    }
  }

  return 0;
}

/**
 *Updates the hold time for the mpr 'mpr' registered on
 *the node 'node'. Adds the mpr to the node if not already
 *registered.
 *@param node the node that has chosen the MPR
 *@param mpr the MPR chosen by the node
 *@param vtime the expiration time
 *@return 0 if node was added, 1 if not
 */
int
update_timer_mpr(union olsr_ip_addr *node, union olsr_ip_addr *mpr, olsr_u8_t vtime)
{
  struct node *tmp_nodes;
  struct mpr *tmp_mpr;
  struct timeval tmp_timer;
  double dbl_time;
  olsr_u32_t time_value;

  dbl_time = me_to_double(vtime);
  time_value = (olsr_u32_t) dbl_time *1000;

  tmp_timer.tv_sec = time_value / 1000;
  tmp_timer.tv_usec = (time_value - (tmp_timer.tv_sec * 1000)) * 1000;

  //printf("Updatimng MPR timer for: %s\n", ip_to_string(node));
  //printf("Secs: %d, usecs: %d\n", (int)tmp_timer.tv_sec, (int)tmp_timer.tv_usec);

  //printf("Updatimng timer for: %s\n", ip_to_string(node));
  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      for (tmp_mpr = tmp_nodes->mpr.next; tmp_mpr != &tmp_nodes->mpr; tmp_mpr = tmp_mpr->next) {
        if (memcmp(&tmp_mpr->addr, mpr, ipsize) == 0) {
          //printf("updating timer for MPR %s ", ip_to_string(mpr));
          //printf("node %s\n", ip_to_string(node));
          gettimeofday(&now, (struct timezone *)NULL);
          timeradd(&now, &tmp_timer, &tmp_mpr->timer);
          return 1;
        }
      }
      /* Only add if parent is added */
      add_mpr(node, mpr, &tmp_timer);
      return 0;
    }
  }

  return 0;
}

int
add_mid_node(union olsr_ip_addr *node, union olsr_ip_addr *alias, olsr_u8_t vtime)
{

  struct node *tmp_nodes;
  struct mid *tmp_mid;
  struct node new, *inserted;

  //printf("MID_add: %s\n", ip_to_string(alias));

  //update_timer_node(node, vtime);

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      for (tmp_mid = tmp_nodes->mid.next; tmp_mid != &tmp_nodes->mid; tmp_mid = tmp_mid->next) {
        if (memcmp(&tmp_mid->alias, alias, ipsize) == 0)
          return 0;
      }

      /* we didn't find the address */
      printf("(1)NEW MID %s ", ip_to_string(alias));
      printf("ADDED FOR %s\n", ip_to_string(node));
      if ((tmp_mid = malloc(sizeof(struct mid))) == 0) {
        fprintf(stderr, "OUT OF MEMORY\n");
        exit(EXIT_FAILURE);
      }

      memcpy(&tmp_mid->alias, alias, ipsize);

      tmp_nodes->mid.next->prev = tmp_mid;
      tmp_mid->next = tmp_nodes->mid.next;
      tmp_nodes->mid.next = tmp_mid;
      tmp_mid->prev = &tmp_nodes->mid;

      remove_node_addr(alias);  // Remove if already registered as a node

      update_nodes_list(tmp_nodes);
      return 1;

    }
  }

  /*New node */

  printf("ADDING NEW NODE %s FROM MID...\n", ip_to_string(node));
  /* We don't know wery much... */
  memset(&new, 0, sizeof(struct node));
  memcpy(&new.addr, node, ipsize);
  inserted = insert_node(&new, vtime);

  if ((tmp_mid = malloc(sizeof(struct mid))) == 0) {
    fprintf(stderr, "OUT OF MEMORY!\n");
    exit(EXIT_FAILURE);
  }

  memcpy(&tmp_mid->alias, alias, ipsize);

  tmp_mid->next = &inserted->mid;
  tmp_mid->prev = &inserted->mid;
  inserted->mid.next = tmp_mid;
  inserted->mid.prev = tmp_mid;

  update_nodes_list(inserted);

  return 2;
}

int
add_hna_node(union olsr_ip_addr *node, union olsr_ip_addr *net, union olsr_ip_addr *mask, olsr_u8_t vtime)
{

  struct node *tmp_nodes;
  struct hna *tmp_hna;
  struct node new, *inserted;

  //printf("HNA: %s\n", ip_to_string(&net));

  update_timer_node(node, vtime);

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      for (tmp_hna = tmp_nodes->hna.next; tmp_hna != &tmp_nodes->hna; tmp_hna = tmp_hna->next) {
        if ((memcmp(&tmp_hna->net, net, ipsize) == 0) && (memcmp(&tmp_hna->mask, mask, ipsize) == 0))
          return 0;
      }

      //printf("NEW HNA ADDED FOR %s ", ip_to_string(node));
      //printf("net: %s \n", ip_to_string(&net));
      /* we didn't find the address */
      if ((tmp_hna = malloc(sizeof(struct hna))) == 0) {
        fprintf(stderr, "OUT OF MEMORY\n");
        exit(EXIT_FAILURE);
      }

      memcpy(&tmp_hna->net, net, ipsize);
      memcpy(&tmp_hna->mask, mask, ipsize);

      /* queue */
      tmp_nodes->hna.next->prev = tmp_hna;
      tmp_hna->next = tmp_nodes->hna.next;
      tmp_nodes->hna.next = tmp_hna;
      tmp_hna->prev = &tmp_nodes->hna;

      update_nodes_list(tmp_nodes);
      return 1;
    }
  }

  printf("ADDING NEW NODE %s FROM HNA...\n", ip_to_string(node));
  /* We don't know wery much... */
  memset(&new, 0, sizeof(struct node));
  memcpy(&new.addr, node, ipsize);
  inserted = insert_node(&new, vtime);

  if ((tmp_hna = malloc(sizeof(struct hna))) == 0) {
    fprintf(stderr, "OUT OF MEMORY!\n");
    exit(EXIT_FAILURE);
  }

  memcpy(&tmp_hna->net, net, ipsize);
  memcpy(&tmp_hna->mask, mask, ipsize);

  tmp_hna->next = &inserted->hna;
  tmp_hna->prev = &inserted->hna;
  inserted->hna.next = tmp_hna;
  inserted->hna.prev = tmp_hna;

  update_nodes_list(inserted);

  return 2;
}

/**
 *Add the MPR mpr to the node nodes selected MPRs.
 *Nodes are NOT added if they are not yet registered!
 *
 *@param node the node that has chosen an MPR
 *@param mpr the MPR choosen by node
 *@param tmp_timer the expiration time
 *@return negative if node already registered or node not found
 */
int
add_mpr(union olsr_ip_addr *node, union olsr_ip_addr *mpr, struct timeval *tmp_timer)
{

  struct node *tmp_nodes;
  struct mpr *mprs;
  struct mpr *tmp_mpr;

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      for (mprs = tmp_nodes->mpr.next; mprs != &tmp_nodes->mpr; mprs = mprs->next) {
        if (memcmp(&mprs->addr, mpr, ipsize) == 0)
          return 0;
      }

      //printf("Adding MPR %s to ", ip_to_string(mpr));
      //printf("%s\n", ip_to_string(node));
      /* Add mpr */

      if ((tmp_mpr = malloc(sizeof(struct mpr))) == 0) {
        fprintf(stderr, "OUT OF MEMORY\n");
        exit(EXIT_FAILURE);
      }

      memcpy(&tmp_mpr->addr, mpr, ipsize);

      gettimeofday(&now, (struct timezone *)NULL);
      timeradd(&now, tmp_timer, &tmp_mpr->timer);

      /* queue */
      tmp_nodes->mpr.next->prev = tmp_mpr;
      tmp_mpr->next = tmp_nodes->mpr.next;
      tmp_nodes->mpr.next = tmp_mpr;
      tmp_mpr->prev = &tmp_nodes->mpr;

      update_nodes_list(tmp_nodes);
      return 1;

    }
  }

  return 1;
}

int
remove_node(struct node *node)
{
  struct hna *tmp_hna, *tmp_hna2;
  struct mid *tmp_mid, *tmp_mid2;
  struct mpr *tmp_mpr, *tmp_mpr2;

  printf("Remove node %s\n", ip_to_string(&node->addr));

  tmp_hna = node->hna.next;
  while (tmp_hna != &node->hna) {
    tmp_hna2 = tmp_hna;
    tmp_hna = tmp_hna->next;
    free(tmp_hna2);
  }
  tmp_mpr = node->mpr.next;
  while (tmp_mpr != &node->mpr) {
    tmp_mpr2 = tmp_mpr;
    tmp_mpr = tmp_mpr->next;
    free(tmp_mpr2);
  }
  tmp_mid = node->mid.next;
  while (tmp_mid != &node->mid) {
    tmp_mid2 = tmp_mid;
    tmp_mid = tmp_mid->next;
    free(tmp_mid2);
  }

  /* Gemove form GUI */
  remove_nodes_list(&node->addr);

  /* Dequeue */
  node->prev->next = node->next;
  node->next->prev = node->prev;

  free(node);

  return 1;
}

/*
 * Remove based on address
 */

int
remove_node_addr(union olsr_ip_addr *node)
{
  struct node *tmp_nodes;
  struct hna *tmp_hna, *tmp_hna2;
  struct mid *tmp_mid, *tmp_mid2;
  struct mpr *tmp_mpr, *tmp_mpr2;

  printf("Remove node %s\n", ip_to_string(node));

  tmp_nodes = nodes.next;

  while (tmp_nodes != &nodes) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      printf("(2)Deleting node %s\n", ip_to_string((union olsr_ip_addr *)&tmp_nodes->addr));

      tmp_hna = tmp_nodes->hna.next;
      while (tmp_hna != &tmp_nodes->hna) {
        tmp_hna2 = tmp_hna;
        tmp_hna = tmp_hna->next;
        free(tmp_hna2);
      }
      tmp_mpr = tmp_nodes->mpr.next;
      while (tmp_mpr != &tmp_nodes->mpr) {
        tmp_mpr2 = tmp_mpr;
        tmp_mpr = tmp_mpr->next;
        free(tmp_mpr2);
      }
      tmp_mid = tmp_nodes->mid.next;
      while (tmp_mid != &tmp_nodes->mid) {
        tmp_mid2 = tmp_mid;
        tmp_mid = tmp_mid->next;
        free(tmp_mid2);
      }

      /* Gemove form GUI */
      remove_nodes_list(&tmp_nodes->addr);

      /* Dequeue */
      tmp_nodes->prev->next = tmp_nodes->next;
      tmp_nodes->next->prev = tmp_nodes->prev;

      free(tmp_nodes);

      return 1;
    }

    tmp_nodes = tmp_nodes->next;
  }

  return 0;
}

struct node *
find_node(char *ip)
{
  struct node *tmp_nodes;

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (strcmp(ip_to_string((union olsr_ip_addr *)&tmp_nodes->addr), ip) == 0)
      return tmp_nodes;
  }

  return NULL;
}

struct node *
find_node_t(union olsr_ip_addr *ip)
{
  struct node *tmp_nodes;

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, ip, ipsize) == 0)
      return tmp_nodes;
  }

  return 0;
}

/*
 *Remove timed out nodes
 */
gint
time_out_nodes(gpointer data __attribute__((unused)))
{
  struct node *tmp_nodes;
  struct node *node_to_delete;

  /* Wait before starting timing out */
  if (timeouts) {
    timeouts--;
    //printf("Waiting...\n");
    return 1;
  }
  //printf("Timing out nodes...\n");
  gettimeofday(&now, (struct timezone *)NULL);

  tmp_nodes = nodes.next;

  while (tmp_nodes != &nodes) {
    //printf("%s: %6d < %6d\n", ip_to_string(&tmp_nodes->addr), tmp_nodes->timer.tv_sec, now.tv_sec);
    if (timercmp(&tmp_nodes->timer, &now, <)) {
      printf("Node %s timed out...\n", ip_to_string((union olsr_ip_addr *)&tmp_nodes->addr));
      node_to_delete = tmp_nodes;

      tmp_nodes = tmp_nodes->next;

      remove_nodes_list(&node_to_delete->addr);
      remove_node(node_to_delete);
    } else
      tmp_nodes = tmp_nodes->next;
  }

  return 1;
}

/**
 *Timeout MPRs for a given node. Only called when user
 *is to see the registered MPRs of the node.
 *@param node the node whom MPRs should be timed out
 *@return negative if node not found
 */
int
time_out_mprs(union olsr_ip_addr *node)
{

  struct node *tmp_nodes;
  struct mpr *mpr_to_delete;
  struct mpr *tmp_mpr;

  gettimeofday(&now, (struct timezone *)NULL);

  /* W A R N I N G !
   *
   * THIS ALGORITHM HAS NOT BEEN TESTED PROPERLY!!!!!!
   * -Andreas
   */

  for (tmp_nodes = nodes.next; tmp_nodes != &nodes; tmp_nodes = tmp_nodes->next) {
    if (memcmp(&tmp_nodes->addr, node, ipsize) == 0) {
      tmp_mpr = tmp_nodes->mpr.next;

      while (tmp_mpr != &tmp_nodes->mpr) {
        if (timercmp(&tmp_mpr->timer, &now, <)) {
          printf("MPR %s OF NODE ", ip_to_string((union olsr_ip_addr *)&tmp_mpr->addr));
          printf("%s TIMIED OUT ", ip_to_string((union olsr_ip_addr *)&tmp_nodes->addr));
          fflush(stdout);

          mpr_to_delete = tmp_mpr;
          tmp_mpr = tmp_mpr->next;

          /* Dequeue */
          mpr_to_delete->next->prev = mpr_to_delete->prev;
          mpr_to_delete->prev->next = mpr_to_delete->next;
          /* Delete */
          free(mpr_to_delete);
        } else
          tmp_mpr = tmp_mpr->next;
      }

      return 1;
    }
  }

  return 0;
}

void
init_timer(olsr_u32_t time_value, struct timeval *hold_timer)
{
  olsr_u16_t time_value_sec = 0;
  olsr_u16_t time_value_msec = 0;

  time_value_sec = time_value / 1000;
  time_value_msec = time_value - (time_value_sec * 1000);

  hold_timer->tv_sec = time_value_sec;
  hold_timer->tv_usec = time_value_msec * 1000;

}

/**
 *Function that converts a mantissa/exponent 8bit value back
 *to double as described in RFC3626:
 *
 * value = C*(1+a/16)*2^b [in seconds]
 *
 *  where a is the integer represented by the four highest bits of the
 *  field and b the integer represented by the four lowest bits of the
 *  field.
 *
 *@param me the 8 bit mantissa/exponen value
 *
 *@return a double value
 */
double
me_to_double(olsr_u8_t me)
{
  int a = me >> 4;
  int b = me - a * 16;
  return (double)(VTIME_SCALE_FACTOR * (1 + (double)a / 16) * (double)pow(2, b));
}

/*
 * Local Variables:
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * End:
 */
