/*
 * The olsr.org Optimized Link-State Routing daemon(olsrd)
 * Copyright (c) 2004, Andreas Tønnesen(andreto@olsr.org)
 * 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.
 *
 * $Id: scheduler.c,v 1.43 2007/09/17 22:24:22 bernd67 Exp $
 */


#include "defs.h"
#include "scheduler.h"
#include "log.h"
#include "tc_set.h"
#include "link_set.h"
#include "duplicate_set.h"
#include "mpr_selector_set.h"
#include "mid_set.h"
#include "mpr.h"
#include "olsr.h"
#include "build_msg.h"

/* Timer data, global. Externed in defs.h */
clock_t now_times;              /* current idea of times(2) reported uptime */
struct timeval now;		/* current idea of time */
struct tm *nowtm;		/* current idea of time (in tm) */

/* Lists */
static struct timeout_entry *timeout_functions;
static struct event_entry *event_functions;

static olsr_bool link_changes; /* is set if changes occur in MPRS set */ 

void
signal_link_changes(olsr_bool val)
{
  link_changes = val;
}

static void 
trigger_dijkstra(void *foo __attribute__((unused)))
{
  OLSR_PRINTF(3, "Triggering Dijkstra\n");

  changes_neighborhood = OLSR_TRUE;
  changes_topology = OLSR_TRUE;
  changes_force = OLSR_TRUE;
}

/**
 *Main scheduler event loop. Polls at every
 *sched_poll_interval and calls all functions
 *that are timed out or that are triggered.
 *Also calls the olsr_process_changes()
 *function at every poll.
 *
 *
 *@return nada
 */

void
scheduler(void)
{
  struct timespec remainder_spec;
  struct timespec sleeptime_spec;

  /*
   *Used to calculate sleep time
   */
  clock_t end_of_loop;
  struct timeval time_used;
  struct timeval interval;
  struct timeval sleeptime_val;

  olsr_u32_t interval_usec;

  struct event_entry *entry;
  struct timeout_entry *time_out_entry;

  struct interface *ifn;

  /* Global buffer for times(2) calls. Do not remove - at least OpenBSD needs it. */
  struct tms tms_buf;
 
  link_changes = OLSR_FALSE;

  if(olsr_cnf->lq_level > 1 && olsr_cnf->lq_dinter > 0.0)
    olsr_register_scheduler_event(trigger_dijkstra, NULL, olsr_cnf->lq_dinter, 0, NULL);

  interval_usec = (olsr_u32_t)(olsr_cnf->pollrate * 1000000);

  interval.tv_sec = interval_usec / 1000000;
  interval.tv_usec = interval_usec % 1000000;

  OLSR_PRINTF(1, "Scheduler started - polling every %0.2f seconds\n", olsr_cnf->pollrate);
  OLSR_PRINTF(3, "Max jitter is %f\n\n", olsr_cnf->max_jitter);

  /* Main scheduler event loop */
  for(;;)
    {
      /* Update now_times */
      now_times = times(&tms_buf);

      /* Update the global timestamp - kept for plugin compat */
      gettimeofday(&now, NULL);
      nowtm = localtime((time_t *)&now.tv_sec);

      while (nowtm == NULL)
	{
	  nowtm = localtime((time_t *)&now.tv_sec);
	}


      /* Run timout functions (before packet generation) */

      time_out_entry = timeout_functions;
      
      while(time_out_entry)
	{
	  time_out_entry->function();
	  time_out_entry = time_out_entry->next;
	}

      /* Update */
      
      olsr_process_changes();


      /* Check for changes in topology */
      if(link_changes)
        {
	  OLSR_PRINTF(3, "ANSN UPDATED %d\n\n", get_local_ansn());
	  increase_local_ansn();
          link_changes = OLSR_FALSE;
	}


      /* Check scheduled events */
      entry = event_functions;

      /* UPDATED - resets timer upon triggered execution */
      while(entry)
	{
	  entry->since_last += olsr_cnf->pollrate;

	  /* Timed out */
	  if((entry->since_last > entry->interval) ||
	     /* Triggered */
	     ((entry->trigger != NULL) &&
	      (*(entry->trigger) == 1)))
	    {
	      /* Run scheduled function */
	      entry->function(entry->param);

	      /* Set jitter */
	      entry->since_last = (float) random()/RAND_MAX;
	      entry->since_last *= olsr_cnf->max_jitter;
	      
	      /* Reset trigger */
	      if(entry->trigger != NULL)
		*(entry->trigger) = 0;
	      
	      //OLSR_PRINTF(3, "Since_last jitter: %0.2f\n", entry->since_last);

	    }

	  entry = entry->next;
	}



      /* looping trough interfaces and emmittin pending data */
      for (ifn = ifnet; ifn ; ifn = ifn->int_next) 
	{ 
	  if(net_output_pending(ifn) && TIMED_OUT(ifn->fwdtimer)) 
	    net_output(ifn);
	}


      end_of_loop = times(&tms_buf);

      //printf("Tick diff: %d\n", end_of_loop - now_times);
      time_used.tv_sec = ((end_of_loop - now_times) * olsr_cnf->system_tick_divider) / 1000;
      time_used.tv_usec = ((end_of_loop - now_times) * olsr_cnf->system_tick_divider) % 1000;

      //printf("Time used: %d.%04d\n", time_used.tv_sec, time_used.tv_usec);

      if(timercmp(&time_used, &interval, <))
	{
	  timersub(&interval, &time_used, &sleeptime_val);
	  
	  // printf("sleeptime_val = %u.%06u\n",
	  //        sleeptime_val.tv_sec, sleeptime_val.tv_usec);
	  
	  sleeptime_spec.tv_sec = sleeptime_val.tv_sec;
	  sleeptime_spec.tv_nsec = sleeptime_val.tv_usec * 1000;
	  
	  while(nanosleep(&sleeptime_spec, &remainder_spec) < 0)
	    sleeptime_spec = remainder_spec;
	}

#if defined WIN32
      // the Ctrl-C signal handler thread asks us to exit

      if (olsr_win32_end_request)
        break;
#endif
      
    }//end for

#if defined WIN32
  // tell the Ctrl-C signal handler thread that we have exited

  olsr_win32_end_flag = TRUE;

  // the Ctrl-C signal handler thread will exit the process and
  // hence also kill us
  
  while (1)
    Sleep(1000);
#endif
}


/*
 *
 *@param initial how long utnil the first generation
 *@param trigger pointer to a boolean indicating that
 *this function should be triggered immediatley
 */
int
olsr_register_scheduler_event(void (*event_function)(void *), 
			      void *par,
			      float interval, 
			      float initial, 
			      olsr_u8_t *trigger)
{
  struct event_entry *new_entry;

  OLSR_PRINTF(3, "Scheduler event registered int: %0.2f\n", interval);

  /* check that this entry is not added already */
  new_entry = event_functions;
  while(new_entry)
    {
      if((new_entry->function == event_function) &&
	 (new_entry->param == par) &&
	 (new_entry->trigger == trigger) &&
	 (new_entry->interval == interval))
	{
	  fprintf(stderr, "Register scheduler event: Event alread registered!\n");
	  olsr_syslog(OLSR_LOG_ERR, "Register scheduler event: Event alread registered!\n");
	  return 0;
	}
      new_entry = new_entry->next;
    }

  new_entry = olsr_malloc(sizeof(struct event_entry), "add scheduler event");

  new_entry->function = event_function;
  new_entry->param = par;
  new_entry->interval = interval;
  new_entry->since_last = interval - initial;
  new_entry->next = event_functions;
  new_entry->trigger = trigger;

  event_functions = new_entry;

  return 1;
}



/*
 *
 *@param initial how long until the first generation
 *@param trigger pointer to a boolean indicating that
 *this function should be triggered immediatley
 */
int
olsr_remove_scheduler_event(void (*event_function)(void *), 
			    void *par,
			    float interval, 
			    float initial __attribute__((unused)), 
			    olsr_u8_t *trigger)
{
  struct event_entry *entry, *prev;

  prev = NULL;
  entry = event_functions;

  while(entry)
    {
      if((entry->function == event_function) &&
	 (entry->param == par) &&
	 (entry->trigger == trigger) &&
	 (0.0 > interval || entry->interval == interval))
	{
	  if(entry == event_functions)
	    {
	      event_functions = entry->next;
	    }
	  else
	    {
	      prev->next = entry->next;
	    }
          free(entry);
	  return 1;
	}

      prev = entry;
      entry = entry->next;
    }

  return 0;
}

/*
 * Sven-Ola, 2007: Since the original timing and flagging is changed (which
 * saves lots of CPU time - see LinkQualityDijkstraLimit) the original timeout
 * functions called every olsr_cnf->polltime uses too much CPU now. Because the
 * changes_xxx handling is switched off with LQDL, it should be OK to call
 * all timeout handlers at a much lower rate. To overcome UDP packet loss,
 * a very low pollrate is used.
 */

static float dijkstra_initial = 0.0;

int
olsr_register_scheduler_event_dijkstra(void (*event_function)(void *), 
			      void *par,
			      float interval, 
			      float initial, 
			      olsr_u8_t *trigger)
{
  if (1 < olsr_cnf->lq_level && 0.0 < olsr_cnf->lq_dinter)
  {
    dijkstra_initial += olsr_cnf->lq_dinter / 10.0;
    return olsr_register_scheduler_event(event_function, par, olsr_cnf->lq_dinter, dijkstra_initial, trigger);
  }
  return olsr_register_scheduler_event(event_function, par, interval, initial, trigger);
}

int
olsr_register_timeout_function(void (*time_out_function)(void), olsr_bool dijkstra_limit_ok)
{
  struct timeout_entry *new_entry;

  if (dijkstra_limit_ok && 1 < olsr_cnf->lq_level && 0.0 < olsr_cnf->lq_dinter)
  {
    dijkstra_initial += olsr_cnf->lq_dinter / 10.0;
    return olsr_register_scheduler_event(
      (void *)time_out_function,
      NULL,
      olsr_cnf->lq_dinter,
      dijkstra_initial,
      NULL);
  }
  
  /* check that this entry is not added already */
  new_entry = timeout_functions;
  while(new_entry)
    {
      if(new_entry->function == time_out_function)
	{
	  fprintf(stderr, "Register scheduler timeout: Event alread registered!\n");
	  olsr_syslog(OLSR_LOG_ERR, "Register scheduler timeout: Event alread registered!\n");
	  return 0;
	}
      new_entry = new_entry->next;
    }

  new_entry = olsr_malloc(sizeof(struct timeout_entry), "scheduler add timeout");

  new_entry->function = time_out_function;
  new_entry->next = timeout_functions;

  timeout_functions = new_entry;

  return 1;
}



int
olsr_remove_timeout_function(void (*time_out_function)(void), olsr_bool dijkstra_limit_ok)
{
  struct timeout_entry *entry, *prev;

  if (dijkstra_limit_ok && 1 < olsr_cnf->lq_level && 0.0 < olsr_cnf->lq_dinter)
  {
    return olsr_remove_scheduler_event(
      (void *)time_out_function,
      NULL,
      -1.0,
      -1.0,
      NULL);
  }
  
  /* check that this entry is not added already */
  entry = timeout_functions;
  prev = NULL;

  while(entry)
    {
      if(entry->function == time_out_function)
	{
	  if(entry == timeout_functions)
	    {
	      timeout_functions = entry->next;
	    }
	  else
	    {
	      prev->next = entry->next;
	    }
	  free(entry);
	  return 1;
	}
      prev = entry;
      entry = entry->next;
    }

  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1