#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>

#include <rtl_sched.h>

#include "../rt_ipc.h"

static char *strs[] = { "Joey ", "Johnny ", "Dee Dee ", "Marky " };
static char sync_str[] = "gabba\n";

#define NUM_TASKS	(sizeof(strs) / sizeof(char *))

RT_TASK_IPC start_task, tasks[NUM_TASKS];

rt_sem_t sems[NUM_TASKS], sync_sem, prio_sem;

rt_mq_t mq_in, mq_out;

#define DBG_PRINT_SETUP		rt_sem_t print_sem;
#define DBG_PRINT_INIT		rt_sem_init(&print_sem, RT_SEM_BINARY, 1);
#define TAKE_PRINT		rt_sem_wait(&print_sem, RT_WAIT_FOREVER);
#define GIVE_PRINT		rt_sem_post(&print_sem);

DBG_PRINT_SETUP

/*
 *  Each task waits to receive the semaphore, prints its string, and
 *  passes the semaphore to the next task.  Then it sends a sync semaphore,
 *  and waits for another semaphore, and this time displays it in
 *  priority order.  Finally, message queues are tested.
 */
void task_code(int task_no)
{
  int i, ret;
  char buf[20];
  for (i=0 ; i<5 ; ++i)
  {
    rt_sem_wait(&sems[task_no], RT_WAIT_FOREVER);
    printk(strs[task_no]);
    if (task_no == NUM_TASKS-1)
      printk("\n");
    rt_sem_post(&sems[(task_no + 1) % NUM_TASKS]);
  }
  rt_sem_post(&sync_sem);
  rt_sem_wait(&prio_sem, RT_WAIT_FOREVER);
  printk(strs[task_no]);
  rt_task_delay(RELATIVE_TIME(RT_TICKS_PER_SEC));
  rt_sem_wait(&prio_sem, RELATIVE_TIME((task_no+1) * RT_TICKS_PER_SEC));
  printk(strs[task_no]);
  rt_sem_post(&sync_sem);

  /* message queue stuff */
  if ((ret = rt_mq_receive(&mq_in, buf, RT_WAIT_FOREVER)) != 0)
    printk("rt_mq_receive() failed with %d\n", ret);
  TAKE_PRINT; printk("\nreceived by task %d ", task_no);
  printk(buf); GIVE_PRINT;
  rt_mq_send(&mq_out, strs[task_no], RT_MQ_NORMAL, RT_WAIT_FOREVER);
  /* test receive timeout */
  rt_sem_wait(&sync_sem, RT_WAIT_FOREVER);
  if (rt_mq_receive(&mq_in, buf, RELATIVE_TIME(RT_TICKS_PER_SEC*(task_no+1))) == -ETIME)
    { TAKE_PRINT; printk("task %d timed out.\n", task_no); GIVE_PRINT; }

  rt_task_suspend(MAKE_RT_TASK(&tasks[task_no]));
}

/*
 * initialization task
 */
void start_task_code(int notused)
{
  int i;
  DBG_PRINT_INIT;
  if (rt_mq_init(&mq_in, NUM_TASKS, 20) || rt_mq_init(&mq_out, NUM_TASKS, 20))
  {
    printk("could not create message queue\n");
    return;
  }
  for (i=0 ; i<NUM_TASKS ; ++i)
  {
    if (rt_sem_init(&sems[i], RT_SEM_BINARY, 0) != 0)
    {
      printk("rt_sem_init failed\n");
      return;
    }
    if (rt_task_ipc_init(&tasks[i], task_code, i, 3000, NUM_TASKS-i) != 0)
    {
      printk("rt_task_ipc_init failed\n");
      return;
    }
    rt_task_wakeup(MAKE_RT_TASK(&tasks[i]));
  }	
  /* create the sync semaphore */
  if (rt_sem_init(&sync_sem, RT_SEM_COUNTING, 0) != 0)
  {
    printk("rt_sem_init failed on sync_sem\n");
    return;
  }
  /* create the priority-test semaphore */
  if (rt_sem_init(&prio_sem, RT_SEM_BINARY, 0) != 0)
  {
    printk("rt_sem_init failed on prio_sem\n");
    return;
  }

  /* pass the semaphore to the first task */
  rt_sem_post(&sems[0]);
  /* wait for each task to send the sync semaphore */
  for (i=0 ; i<NUM_TASKS ; ++i)
    rt_sem_wait(&sync_sem, RT_WAIT_FOREVER);
  printk(sync_str);
  /* post the priority-test semaphore -- the tasks should then run */
  /* in priority order */
  for (i=0 ; i<NUM_TASKS ; ++i)
    rt_sem_post(&prio_sem);
  printk("\n");
  for (i=0 ; i<NUM_TASKS ; ++i)
    rt_sem_wait(&sync_sem, RT_WAIT_FOREVER);
  printk(sync_str);

  /* now, test message queues */
  printk("testing message queues\n");
  for (i=0 ; i<NUM_TASKS ; ++i)
    if (rt_mq_send(&mq_in, strs[i], RT_MQ_NORMAL, RT_WAIT_FOREVER))
      printk("rt_mq_send() failed\n");
  for (i=0 ; i<NUM_TASKS ; ++i)
  {
    char buf[20];
    rt_mq_receive(&mq_out, buf, RT_WAIT_FOREVER);
    TAKE_PRINT; printk("\nreceived from mq_out: %s", buf); GIVE_PRINT;
  }
  for (i=0 ; i<NUM_TASKS ; ++i)
    rt_sem_post(&sync_sem);
  TAKE_PRINT; printk("\ninit task complete\n"); GIVE_PRINT;

  /* nothing more for this task to do */
  rt_task_suspend(MAKE_RT_TASK(&start_task));
}

int init_module(void)
{
  if (rt_task_ipc_init(&start_task, start_task_code, 0, 3000, 10) != 0)
    printk("Could not start init task\n");
  rt_task_wakeup(MAKE_RT_TASK(&start_task));
  return 0;
}

void cleanup_module(void)
{
  int i;
  for (i=0 ; i<NUM_TASKS ; ++i)
  {
    rt_sem_destroy(&sems[i]);
    rt_task_ipc_delete(&tasks[i]);
  }
  rt_sem_destroy(&sync_sem);
  rt_mq_destroy(&mq_in);
  rt_mq_destroy(&mq_out);
  rt_task_ipc_delete(&start_task);
}
