/*
 * Copyright (c) 2001-2004 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 lwIP TCP/IP stack.
 *
 * Author: Adam Dunkels <adam@sics.se>
 *
 */

#include "lwip/sys.h"
#include "lwip/opt.h"
#include "lwip/def.h"
#include "lwip/memp.h"

#if (NO_SYS == 0)

#if LWIP_SO_RCVTIMEO
void sys_mbox_fetch_timeout(sys_mbox_t mbox, void **msg, lwip_timediff_t timeout)
#else
void sys_mbox_fetch(sys_mbox_t mbox, void **msg)
#endif /* LWIP_SO_RCVTIMEO */
{
  lwip_timestamp_t jiffies_now;
  lwip_timediff_t jiffies_waited, jiffies_to_next_timer, jiffies_to_wait;
  struct lwip_thread_local_storage *tls;
  struct sys_timer *tmptimeout;
  sys_timer_handler h;
  void *arg;

#if LWIP_SO_RCVTIMEO  
  LWIP_ASSERT("timeout must be >= 0", (timeout >= 0));
#endif

  tls = sys_arch_get_threadlocalstorage();

again:
  if (!tls || !tls->first_timer) {
    /* no timeouts */
#if LWIP_SO_RCVTIMEO  
    jiffies_waited = sys_arch_mbox_fetch(mbox, msg, timeout);
    if (jiffies_waited == SYS_ARCH_TIMEOUT) {
      if (msg != NULL) {
        *msg = NULL;
      }
    }
#else
    /* return value is not needed */
    sys_arch_mbox_fetch(mbox, msg, 0);
#endif /* LWIP_SO_RCVTIMEO */
  } else {
    /* timeouts->next->time == 0 is valid since jiffies can wrap around */
    jiffies_now = sys_jiffies();
    jiffies_to_next_timer = TIMESTAMP_DIFF(tls->first_timer->time_expire, jiffies_now);
#if LWIP_SO_RCVTIMEO
    if ((timeout > 0) && (timeout < jiffies_to_next_timer)) {
      /* external timeout given and next timer doesn't expire earlier */
      jiffies_to_wait = timeout;
    } else {
      /* no external timeout given or next timer expires earlier */
      jiffies_to_wait = jiffies_to_next_timer;
    }
#else
    jiffies_to_wait = jiffies_to_next_timer;
#endif /* LWIP_SO_RCVTIMEO */
    if(jiffies_to_wait <= 0) {
      /* time_expire is in the past, execute timer callback now */
      jiffies_waited = SYS_ARCH_TIMEOUT;
    } else {
      /* time_expire is in the future -> proceed as normal */
      jiffies_waited = sys_arch_mbox_fetch(mbox, msg, jiffies_to_wait);
    }

    if (jiffies_waited == SYS_ARCH_TIMEOUT) {
      lwip_timestamp_t nnow, nowdiff;
#if LWIP_SO_RCVTIMEO
      if (timeout > 0) {
        /* external timeout given */
        if (timeout >= jiffies_to_next_timer) {
          /* timer expired, substract the time waited from timeout,
             call timer handler and fetch again */
          if (timeout > jiffies_to_next_timer) {
            timeout -= jiffies_to_next_timer;
          } else {
            /* timeout and next timer expired together */
            if (msg != NULL) {
              *msg = NULL;
              return;
            }
          }
        } else {
          /* timeout before any timer expired, return */
          if (msg != NULL) {
            *msg = NULL;
            return;
          }
        }
      }
#endif /* LWIP_SO_RCVTIMEO */

      /* If time == SYS_ARCH_TIMEOUT, a timeout occured before a message
         could be fetched. We should now call the timeout handler and
         deallocate the memory allocated for the timeout. */
      nnow = sys_jiffies();
      nowdiff = SYS_ARCH_MS_TO_JIFFIES(50);
      LWIP_ASSERT("check for timeout correctness", tls->first_timer->time_expire <= nnow + nowdiff);
      tmptimeout = tls->first_timer;
      tls->first_timer = tmptimeout->next;
      h   = tmptimeout->h;
      arg = tmptimeout->arg;
      memp_free(MEMP_SYS_TIMEOUT, tmptimeout);
      if (h != NULL) {
        lwip_timestamp_t jnow = sys_jiffies();
        LWIP_DEBUGF(SYS_DEBUG, ("sys_mbox_fetch calling h=%p(%p), jiffies=%d, ms=%d\n", h, (void *)arg,
          jnow, SYS_ARCH_JIFFIES_TO_MS(jnow)));
        h(arg);
      }
      
      /* We try again to fetch a message from the mbox. */
      goto again;
    }

  }
}

void
sys_timer_create_jiffies(lwip_timediff_t jiffies, sys_timer_handler h, void *arg)
{
  struct lwip_thread_local_storage *tls;
  struct sys_timer *new_timer, *t;
  lwip_timestamp_t time_expire;

  time_expire = TIMESTAMP_ADD(sys_jiffies(), jiffies);

  new_timer = memp_malloc(MEMP_SYS_TIMEOUT);
  if (new_timer == NULL) {
    LWIP_ASSERT("sys_timeout: new_timer != NULL", new_timer != NULL);
    return;
  }
  new_timer->next = NULL;
  new_timer->h = h;
  new_timer->arg = arg;
  new_timer->time_expire = time_expire;

  tls = sys_arch_get_threadlocalstorage();

  //LWIP_DEBUGF(SYS_DEBUG, ("sys_timer_create_jiffies: %p jiffies=%"U32_F" h=%p arg=%p, expire=%"U32_F"\n",
//    (void *)new_timer, jiffies, h, (void *)arg, time_expire));

  LWIP_ASSERT("sys_timer_create_jiffies: tls != NULL", tls != NULL);
  if (tls == NULL) {
    return;
  }

  /* Insert the new timer */
  if (tls->first_timer == NULL) {
    /* no timer yet, set as first */
    //LWIP_DEBUGF(SYS_DEBUG, ("sys_timer_create_jiffies: inserting as single\n"));
    tls->first_timer = new_timer;
    return;
  }

  if (tls->first_timer->time_expire > time_expire) {
    /* the new timer expires earlier than the previous first timer,
       set it as first in the list */
    //LWIP_DEBUGF(SYS_DEBUG, ("sys_timer_create_jiffies: inserting first next->expire=%"U32_F"\n", tls->first_timer->time_expire));
    new_timer->next = tls->first_timer;
    tls->first_timer = new_timer;
  } else {
    /* insert new timer at the correct place in the list */
    for(t = tls->first_timer; t != NULL; t = t->next) {
      if ((t->next == NULL) || (t->next->time_expire > new_timer->time_expire)) {
        new_timer->next = t->next;
        t->next = new_timer;
        break;
      }
    }
  }

}

/* Go through timer list (for this task only) and remove the first matching entry,
   even though the timer has not triggered yet.
*/
void
sys_timer_remove(sys_timer_handler h, void *arg)
{
  struct lwip_thread_local_storage *tls;
  struct sys_timer *prev_t, *t;

  tls = sys_arch_get_threadlocalstorage();

  LWIP_ASSERT("sys_timer_remove: tls != NULL", tls != NULL);
  if (tls == NULL)
    return;
  if (tls->first_timer == NULL)
    return;

  for (t = tls->first_timer, prev_t = NULL; t != NULL; prev_t = t, t = t->next) {
    if ((t->h == h) && (t->arg == arg)) {
      /* We have a match */
      /* Unlink from previous in list */
      if (prev_t == NULL)
        tls->first_timer = t->next;
      else
        prev_t->next = t->next;
      memp_free(MEMP_SYS_TIMEOUT, t);
      return;
    }
  }
  return;
}

#if MAYBE_USED_LATER
/* Go through timer list (for this task only) and execute any timers
 * that ran out at the current time.
*/
void
sys_check_timers(void)
{
  struct lwip_thread_local_storage *tls;
  struct sys_timer *prev_t, *t;
  lwip_timestamp_t time_now;

  time_now = sys_jiffies();

  tls = sys_arch_get_threadlocalstorage();
  LWIP_ASSERT("sys_timeout: tls != NULL", tls != NULL);
  if (tls->first_timer == NULL)
    return;

  for (t = tls->first_timer, prev_t = NULL; t != NULL; prev_t = t, t = t->next) {
    if (TIMESTAMP_DIFF(t->time_expire, time_now) <= 0) {
      sys_timer_handler h;
      void *arg;
      /* Timer expired */
      h = t->h;
      arg = t->arg;
      /* Unlink from previous in list */
      if (prev_t == NULL)
        tls->first_timer = t->next;
      else
        prev_t->next = t->next;
      /* free the memp entry */
      memp_free(MEMP_SYS_TIMEOUT, t);
      /* execute the timer callback */
      if (h != NULL) {
        LWIP_DEBUGF(SYS_DEBUG, ("sys_check_timers calling h=%p(%p)\n", h, (void *)arg));
        h(arg);
      }
    } else {
      /* return after the first timer which has not expired yet 
       * instead of running through the complete list */
      return;
    }
  }
}
#endif /* MAYBE_USED_LATER */

#endif /* NO_SYS */
