#include "node.h"
#include "coap_timer.h"
#include "os_type.h"
#include "osapi.h"
#include "pm/swtimer.h"

static os_timer_t coap_timer;
static coap_tick_t basetime = 0;

void coap_timer_elapsed(coap_tick_t *diff){
  coap_tick_t now = system_get_time() / 1000;   // coap_tick_t is in ms. also sys_timer
  if(now>=basetime){
    *diff = now-basetime;
  } else {
    *diff = now + SYS_TIME_MAX -basetime;
  }
  basetime = now;
}

void coap_timer_tick(void *arg){
  if( !arg )
    return;
  coap_queue_t **queue = (coap_queue_t **)arg;
  if( !(*queue) )
    return;

  coap_queue_t *node = coap_pop_next( queue );
  /* re-initialize timeout when maximum number of retransmissions are not reached yet */
  if (node->retransmit_cnt < COAP_DEFAULT_MAX_RETRANSMIT) {
    node->retransmit_cnt++;
    node->t = node->timeout << node->retransmit_cnt;

    NODE_DBG("** retransmission #%d of transaction %d\n",
        node->retransmit_cnt, (((uint16_t)(node->pdu->pkt->hdr.id[0]))<<8)+node->pdu->pkt->hdr.id[1]);
    node->id = coap_send(node->pconn, node->pdu);
    if (COAP_INVALID_TID == node->id) {
      NODE_DBG("retransmission: error sending pdu\n");
      coap_delete_node(node);
    } else {
      coap_insert_node(queue, node);
    }
  } else {
    /* And finally delete the node */
    coap_delete_node( node );
  }

  coap_timer_start(queue);
}

void coap_timer_setup(coap_queue_t ** queue, coap_tick_t t){
  os_timer_disarm(&coap_timer);
  os_timer_setfn(&coap_timer, (os_timer_func_t *)coap_timer_tick, queue);
  SWTIMER_REG_CB(coap_timer_tick, SWTIMER_RESUME);
    //coap_timer_tick processes a queue, my guess is that it is ok to resume the timer from where it left off
  os_timer_arm(&coap_timer, t, 0);   // no repeat
}

void coap_timer_stop(void){
  os_timer_disarm(&coap_timer);
}

void coap_timer_update(coap_queue_t ** queue){
  if (!queue)
    return;
  coap_tick_t diff = 0;
  coap_queue_t *first = *queue;
  coap_timer_elapsed(&diff); // update: basetime = now, diff = now - oldbase, means time elapsed
  if (first) {
    // diff ms time is elapsed, re-calculate the first node->t
    if (first->t >= diff){
      first->t -= diff;
    } else {
      first->t = 0;  // when timer enabled, time out almost immediately
    }
  }
}

void coap_timer_start(coap_queue_t ** queue){
  if(*queue){ // if there is node in the queue, set timeout to its ->t.
    coap_timer_setup(queue, (*queue)->t);
  }
}
