// CrossWorks Tasking Library.
//
// Copyright (c) 2004, 2006 Rowley Associates Limited.
//
// This file may be distributed under the terms of the License Agreement
// provided with this software.
//
// THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "ctl_api.h"
#include "ctl_impl.h"

#ifdef BUILD_BYTE_QUEUE
#define QUEUE_FN(n) ctl_byte_queue_##n
#define QUEUE_TYPE CTL_BYTE_QUEUE_t
#define QUEUE_ELEMENT_TYPE unsigned char
#define QUEUE_TEMP temp_byte
#else
#define QUEUE_FN(n) ctl_message_queue_##n
#define QUEUE_TYPE CTL_MESSAGE_QUEUE_t
#define QUEUE_ELEMENT_TYPE void *
#define QUEUE_TEMP temp_message
#endif

void
QUEUE_FN(init)(QUEUE_TYPE *m,
               QUEUE_ELEMENT_TYPE *queue,
               unsigned queue_size)
{  
  int enabled = ctl_global_interrupts_disable();
  m->q = queue;
  m->s = queue_size;
  m->front = m->n = 0;
  ctl_global_interrupts_set(enabled);
}

#define ctl_private_message_queue_is_full(MQ) (MQ->n==MQ->s)
#define ctl_private_message_queue_is_empty(MQ) (MQ->n==0)

static void
ctl_private_message_queue_join(QUEUE_TYPE *m, QUEUE_ELEMENT_TYPE msg)
{  
  m->q[(m->front+m->n)%(m->s)] = msg;
  m->n++;
}

static QUEUE_ELEMENT_TYPE
ctl_private_message_queue_leave(QUEUE_TYPE *m)
{
  QUEUE_ELEMENT_TYPE front = m->q[m->front];  
  m->front = (m->front+1)%(m->s);
  m->n--;
  return front;
}

unsigned 
QUEUE_FN(post)(QUEUE_TYPE *mq, 
               QUEUE_ELEMENT_TYPE message, 
               CTL_TIMEOUT_t t,
               CTL_TIME_t timeout)
{
  ctl_global_interrupts_disable();  
  if (ctl_interrupt_count)
    ctl_handle_error(CTL_UNSUPPORTED_CALL_FROM_ISR);
  ctl_task_executing->timeout = 0;  
  if (ctl_private_message_queue_is_full(mq)) 
    {
      unsigned type = CTL_STATE_MESSAGE_QUEUE_POST_WAIT;
      switch (t)
        {
          case CTL_TIMEOUT_NONE:
            ctl_task_executing->timeout = 0;
            break;
          case CTL_TIMEOUT_ABSOLUTE:
            ctl_task_executing->timeout = timeout;
            type |= CTL_STATE_TIMER_WAIT;  
            break;
          case CTL_TIMEOUT_DELAY:
            ctl_task_executing->timeout = ctl_current_time + timeout;
            type |= CTL_STATE_TIMER_WAIT;  
            break;
        }         
      ctl_task_executing->v.wait_object = mq;
      ctl_task_executing->u.QUEUE_TEMP = message;
      ctl_task_executing->state = type;                
    }
  else // queue not full
    {     
      CTL_TASK_t *t = 0;
      if (ctl_private_message_queue_is_empty(mq)) // queue is empty - see if someone is waiting to receive a message
        {          
          for (t=ctl_task_list; t; t=t->next)
            if ((t->state & CTL_STATE_MESSAGE_QUEUE_RECEIVE_WAIT) && (t->v.wait_object == mq))
              {
                t->v.timeout_occured = 0;
                t->u.QUEUE_TEMP = message;
                t->state = CTL_STATE_RUNNABLE;
                break;
              }
        }
      if (t == 0) // queue not empty || no one waiting to receive        
        ctl_private_message_queue_join(mq, message);  
      ctl_task_executing->v.timeout_occured = 0;
    }
  ctl_private_reschedule();  
  return ctl_task_executing->v.timeout_occured == 0;
}

unsigned
QUEUE_FN(receive)(QUEUE_TYPE *mq,
                  QUEUE_ELEMENT_TYPE *message,
                  CTL_TIMEOUT_t t,
                  CTL_TIME_t timeout)
{
  unsigned waitNeeded=0;
  ctl_global_interrupts_disable();  
  if (ctl_interrupt_count)
    ctl_handle_error(CTL_UNSUPPORTED_CALL_FROM_ISR);
  ctl_task_executing->u.QUEUE_TEMP = 0;  
  if (ctl_private_message_queue_is_empty(mq))
    {
      unsigned type = CTL_STATE_MESSAGE_QUEUE_RECEIVE_WAIT;
      switch (t)
        {
          case CTL_TIMEOUT_NONE:
            ctl_task_executing->timeout = 0;
            break;
          case CTL_TIMEOUT_ABSOLUTE:
            ctl_task_executing->timeout = timeout;
            type |= CTL_STATE_TIMER_WAIT;  
            break;
          case CTL_TIMEOUT_DELAY:
            ctl_task_executing->timeout = ctl_current_time + timeout;
            type |= CTL_STATE_TIMER_WAIT;  
            break;
        }       
      ctl_task_executing->v.wait_object = mq;          
      ctl_task_executing->state = type;                  
    }
  else // queue not empty
    {
      CTL_TASK_t *t = 0;
      unsigned wasFull = ctl_private_message_queue_is_full(mq);
      ctl_task_executing->u.QUEUE_TEMP = ctl_private_message_queue_leave(mq);
      ctl_task_executing->v.timeout_occured = 0;
      if (wasFull) // queue was full - see if someone is waiting to post a message
        {
          for (t=ctl_task_list; t; t=t->next)
            if ((t->state & CTL_STATE_MESSAGE_QUEUE_POST_WAIT) && (t->v.wait_object == mq))
              {
                ctl_private_message_queue_join(mq, t->u.QUEUE_TEMP);
                t->v.timeout_occured = 0;
                t->state = CTL_STATE_RUNNABLE;
                break;
              }
        }      
    }
  ctl_private_reschedule();  
  *message = ctl_task_executing->u.QUEUE_TEMP;
  return ctl_task_executing->v.timeout_occured == 0;
}

unsigned 
QUEUE_FN(post_nb)(QUEUE_TYPE *mq, 
                  QUEUE_ELEMENT_TYPE message)
{
  unsigned okay = 0;
  int enabled = ctl_global_interrupts_disable();  
  if (!ctl_private_message_queue_is_full(mq)) 
    {
      CTL_TASK_t *t = 0;
      if (ctl_private_message_queue_is_empty(mq)) // queue is empty - see if someone is waiting to receive a message
        {          
          for (t=ctl_task_list; t; t=t->next)
            if ((t->state & CTL_STATE_MESSAGE_QUEUE_RECEIVE_WAIT) && (t->v.wait_object == mq))
              {
                t->u.QUEUE_TEMP = message;
                t->v.timeout_occured = 0;
                t->state = CTL_STATE_RUNNABLE;
                break;
              }
        }
      if (t == 0) // queue not empty || no one waiting to receive        
        ctl_private_message_queue_join(mq, message);        
      okay = 1;
    }  
  if (ctl_interrupt_count==0)
    ctl_private_reschedule();
  else
    ctl_global_interrupts_set(enabled);
  return okay;
}

unsigned
QUEUE_FN(receive_nb)(QUEUE_TYPE *mq,
                     QUEUE_ELEMENT_TYPE *message)
{  
  unsigned okay = 0;
  int enabled = ctl_global_interrupts_disable();    
  if (!ctl_private_message_queue_is_empty(mq))
    {
      CTL_TASK_t *t = 0;
      unsigned wasFull = ctl_private_message_queue_is_full(mq);
      *message = ctl_private_message_queue_leave(mq);
      if (wasFull) // queue was full - see if someone is waiting to post a message
        {
          for (t=ctl_task_list; t; t=t->next)
            if ((t->state & CTL_STATE_MESSAGE_QUEUE_POST_WAIT) && (t->v.wait_object == mq))
              {
                ctl_private_message_queue_join(mq, t->u.QUEUE_TEMP);
                t->v.timeout_occured = 0;
                t->state = CTL_STATE_RUNNABLE;
                break;
              }
        } 
      okay = 1;
    }
  if (ctl_interrupt_count==0)
    ctl_private_reschedule();
  else
    ctl_global_interrupts_set(enabled);    
  return okay;
}
