/**
 * \file
 * <!--
 * This file is part of BeRTOS.
 *
 * Bertos is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 *
 * Copyright 2004, 2008 Develer S.r.l. (http://www.develer.com/)
 * Copyright 1999, 2000, 2001 Bernie Innocenti <bernie@codewiz.org>
 * -->
 *
 * \brief Message test.
 *
 *
 * \author Daniele Basile <asterix@develer.com>
 *
 * $test$: cp bertos/cfg/cfg_proc.h $cfgdir/
 * $test$: echo  "#undef CONFIG_KERN" >> $cfgdir/cfg_proc.h
 * $test$: echo "#define CONFIG_KERN 1" >> $cfgdir/cfg_proc.h
 * $test$: cp bertos/cfg/cfg_signal.h $cfgdir/
 * $test$: echo  "#undef CONFIG_KERN_SIGNALS" >> $cfgdir/cfg_signal.h
 * $test$: echo "#define CONFIG_KERN_SIGNALS 1" >> $cfgdir/cfg_signal.h
 */

#include "cfg/cfg_timer.h"
#include <cfg/debug.h>
#include <cfg/test.h>
#include <cfg/compiler.h>

#include <kern/msg.h>
#include <kern/proc.h>
#include <kern/signal.h>

#include <mware/event.h>

#include <drv/timer.h>

/*
 * In the nightly build test, signals are disables, so this
 * code won't compile.
 * Since this code is used when we run "make check" it will be
 * compiled and therefor tested there.
 */
#if CONFIG_KERN_SIGNALS


// Global settings for the test.
#define MAX_GLOBAL_COUNT            11040
#define TEST_TIME_OUT_MS             5000
#define DELAY                           5

// Settings for the test message.
//Process 0
#define INC_PROC_T0                     1
#define DELAY_PROC_T0   INC_PROC_T0*DELAY
//Process 1
#define INC_PROC_T1                     3
#define DELAY_PROC_T1   INC_PROC_T1*DELAY
//Process 2
#define INC_PROC_T2                     5
#define DELAY_PROC_T2   INC_PROC_T2*DELAY
//Process 3
#define INC_PROC_T3                     7
#define DELAY_PROC_T3   INC_PROC_T3*DELAY
//Process 4
#define INC_PROC_T4                    11
#define DELAY_PROC_T4   INC_PROC_T4*DELAY
//Process 5
#define INC_PROC_T5                    13
#define DELAY_PROC_T5   INC_PROC_T5*DELAY

/*
 * These macros generate the code needed to create the test process functions.
 */
#define RECV_PROC(num, sig) \
static NORETURN void receiver_proc##num(void) \
{ \
	TestMsg *rec_msg; \
	for(;;) \
	{ \
		sig_wait(sig); \
		kprintf("Proc[%d]..get message\n", num); \
		rec_msg = containerof(msg_get(&test_port##num), TestMsg, msg); \
		timer_delay(rec_msg->delay); \
		rec_msg->result += rec_msg->val; \
		kprintf("Proc[%d]..process message val[%d],delay[%d],res[%d]\n", num, rec_msg->val, rec_msg->delay, rec_msg->result); \
		msg_reply(&rec_msg->msg); \
		process_num++; \
		kprintf("Proc[%d] reply\n", num); \
	} \
}

#define SEND_MSG(num) \
	do { \
		kprintf("Main send message to proc[%d]\n", num); \
		msg##num.msg.replyPort = &test_portMain; \
		msg_put(&test_port##num, &msg##num.msg); \
	} while(0)

#define RECV_STACK(num) PROC_DEFINE_STACK(receiver_stack##num, KERN_MINSTACKSIZE * 2)
#define RECV_INIT_PROC(num) proc_new(receiver_proc##num, NULL, sizeof(receiver_stack##num), receiver_stack##num)
#define RECV_INIT_MSG(num, proc, sig) msg_initPort(&test_port##num, event_createSignal(proc, sig))

// A test message with the parameters and a result.
typedef struct
{
	Msg msg;

	int val;
	int delay;
	int result;
} TestMsg;

// Global count to check if the test is going ok.
static int count = 0;
static int process_num;

// Our message port.
static MsgPort test_port0;
static MsgPort test_port1;
static MsgPort test_port2;
static MsgPort test_port3;
static MsgPort test_port4;
static MsgPort test_port5;

/*
 * Generate the process to test message.
 */
RECV_PROC(0, SIG_USER0)
RECV_PROC(1, SIG_USER1)
RECV_PROC(2, SIG_USER2)
RECV_PROC(3, SIG_USER3)
RECV_PROC(4, SIG_SYSTEM5)
RECV_PROC(5, SIG_SYSTEM6)
/*
 * These signal are already use from
 * main process and the sig_waitWithTimeout functions, so we don't
 * use it.
 *
 * RECV_PROC(6, SIG_SINGLE)
 * RECV_PROC(7, SIG_TIMEOUT)
 */

RECV_STACK(0);
RECV_STACK(1);
RECV_STACK(2);
RECV_STACK(3);
RECV_STACK(4);
RECV_STACK(5);

/*
 * Help function to fill the message to send
 */
static void fill_msg(TestMsg *msg, int val, int delay, int res)
{
	msg->val = val;
	msg->delay = delay;
	msg->result = res;
}

/**
 * Run signal test
 */
int msg_testRun(void)
{
	MsgPort test_portMain;
	TestMsg msg0;
	TestMsg msg1;
	TestMsg msg2;
	TestMsg msg3;
	TestMsg msg4;
	TestMsg msg5;
	TestMsg *reply;

	// Allocate and start the test process
    struct Process *recv0 = RECV_INIT_PROC(0);
    struct Process *recv1 = RECV_INIT_PROC(1);
    struct Process *recv2 = RECV_INIT_PROC(2);
    struct Process *recv3 = RECV_INIT_PROC(3);
    struct Process *recv4 = RECV_INIT_PROC(4);
    struct Process *recv5 = RECV_INIT_PROC(5);

	kprintf("Run Message test..\n");

	// Init port and message
    RECV_INIT_MSG(Main, proc_current(), SIG_SINGLE);
    RECV_INIT_MSG(0, recv0, SIG_USER0);
    RECV_INIT_MSG(1, recv1, SIG_USER1);
    RECV_INIT_MSG(2, recv2, SIG_USER2);
    RECV_INIT_MSG(3, recv3, SIG_USER3);
    RECV_INIT_MSG(4, recv4, SIG_SYSTEM5);
    RECV_INIT_MSG(5, recv5, SIG_SYSTEM6);

	// Fill-in first message and send it out.
	fill_msg(&msg0, INC_PROC_T0, DELAY_PROC_T0, 0);
	fill_msg(&msg1, INC_PROC_T1, DELAY_PROC_T1, 0);
	fill_msg(&msg2, INC_PROC_T2, DELAY_PROC_T2, 0);
	fill_msg(&msg3, INC_PROC_T3, DELAY_PROC_T3, 0);
	fill_msg(&msg4, INC_PROC_T4, DELAY_PROC_T4, 0);
	fill_msg(&msg5, INC_PROC_T5, DELAY_PROC_T5, 0);


	// Send and wait the message
	for (int i = 0; i < 23; ++i)
    {
		process_num = 0;
		SEND_MSG(0);
		SEND_MSG(1);
		SEND_MSG(2);
		SEND_MSG(3);
		SEND_MSG(4);
		SEND_MSG(5);
		while(1)
		{
			sigmask_t sigs = sig_waitTimeout(SIG_SINGLE, ms_to_ticks(TEST_TIME_OUT_MS));
			if (sigs & SIG_SINGLE)
			{
				// Wait for a reply...
				while ((reply = (TestMsg *)msg_get(&test_portMain)))
				{
					count += reply->result;
					kprintf("Main recv[%d] count[%d]\n", reply->result, count);
				}
			}
			
			if (process_num == 6)
				break;

			if (sigs & SIG_TIMEOUT)
			{
				kputs("Main: sig timeout\n");
				goto error;
			}
		}
    }

	if(count == MAX_GLOBAL_COUNT)
	{
		kprintf("Message test finished..ok!\n");
		return 0;
	}
	
error:
	kprintf("Message test finished..fail!\n");
	return -1;
}

int msg_testSetup(void)
{
	kdbg_init();

	kprintf("Init Timer..");
	timer_init();
	kprintf("Done.\n");

	kprintf("Init Process..");
	proc_init();
	kprintf("Done.\n");
	return 0;
}

int msg_testTearDown(void)
{
	kputs("TearDown Message test.\n");
	return 0;
}

TEST_MAIN(msg);

#endif /* CONFIG_KERN_SIGNALS */