/* This file is part of libbrandt.
 * Copyright (C) 2016 GNUnet e.V.
 *
 * libbrandt 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 3 of the License, or (at your option) any later
 * version.
 *
 * libbrandt 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
 * libbrandt.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file test_brandt.c
 * @brief testing API functions.
 * @author Markus Teich
 */

#include "platform.h"

#include <gnunet/gnunet_util_lib.h>

#include "brandt.h"
#include "crypto.h"
#include "test.h"
#include "util.h"


struct msg {
	uint16_t sender;
	uint16_t receiver;
	void     *buf;
	size_t   buf_len;
};


static uint16_t              *id;
static struct BRANDT_Auction **ad;
static uint16_t              bidders = 3;
static uint16_t              prizes = 8;


static void
bidder_start (void *arg)
{
	uint16_t i = *(uint16_t *)arg;

	weprintf ("starting bidder %d", i);
	BRANDT_bidder_start (ad[i], i, bidders);
}


static void
transfer_message (void *arg)
{
	struct msg      *m = (struct msg *)arg;
	struct msg_head *h = (struct msg_head *)m->buf;

	weprintf ("xfer msg %d %x from %d to %d", ntohl (
	              h->msg_type), arg, m->sender, m->receiver);
	BRANDT_got_message (ad[m->receiver], m->sender, m->buf, m->buf_len);
	free (arg);
}


static uint16_t
cb_start (void *auction_closure)
{
	uint16_t *s = (uint16_t *)auction_closure;

	if (!s || bidders != *s)
	{
		weprintf ("start callback called from bidder");
		_exit (1);
	}

	for (uint16_t i = 0; i < bidders; i++)
		GNUNET_SCHEDULER_add_now (&bidder_start, &id[i]);

	return bidders;
}


static int
cb_broadcast (void       *auction_closure,
              const void *msg,
              size_t     msg_len)
{
	uint16_t   *s = (uint16_t *)auction_closure;
	struct msg *m;

	for (uint16_t i = 0; i <= bidders; i++)
	{
		if (i == *s)
			continue;
		m = GNUNET_new (struct msg);
		m->sender = *s;
		m->receiver = i;
		m->buf = GNUNET_new_array (msg_len, unsigned char);
		memcpy (m->buf, msg, msg_len);
		m->buf_len = msg_len;
		GNUNET_SCHEDULER_add_now (&transfer_message, m);
	}
	return 0;
}


static int
cb_unicast (void       *auction_closure,
            const void *msg,
            size_t     msg_len)
{
	uint16_t   *s = (uint16_t *)auction_closure;
	struct msg *m;

	m = GNUNET_new (struct msg);
	m->sender = *s;
	m->receiver = bidders; /* == seller */
	m->buf = GNUNET_new_array (msg_len, unsigned char);
	memcpy (m->buf, msg, msg_len);
	m->buf_len = msg_len;
	GNUNET_SCHEDULER_add_now (&transfer_message, m);

	return 0;
}


static void
cb_result (void                 *auction_closure,
           struct BRANDT_Result results[],
           uint16_t             results_len)
{
	uint16_t *s = (uint16_t *)auction_closure;

	if (0 == results_len)
		weprintf ("result from agent %d: none", *s);

	for (uint16_t i = 0; i < results_len; i++)
		weprintf ("result from agent %d: bidder %d got status %d with price %d",
		          *s,
		          results[i].bidder,
		          results[i].status,
		          results[i].price);
}


static void
run_new_join (void *arg)
{
	int        *ret = arg;
	const char description[] = "test description for test_new_join";
	void       *desc;
	size_t     desc_len;

	ad = GNUNET_new_array (bidders + 1, struct BRANDT_Auction *);

	ad[bidders] = BRANDT_new (&cb_result,
	                          &cb_broadcast,
	                          &cb_start,
	                          &id[bidders],
	                          &desc,
	                          &desc_len,
	                          description,
	                          sizeof (description),
	                          GNUNET_TIME_absolute_get (),
	                          GNUNET_TIME_UNIT_MINUTES,
	                          prizes, /* amount of possible prizes */
	                          0,      /* m */
	                          1);     /* outcome public */
	if (!ad[bidders])
	{
		weprintf ("BRANDT_new() failed.");
		_exit (1);
	}

	for (uint16_t i = 0; i < bidders; i++)
	{
		ad[i] = BRANDT_join (&cb_result,
		                     &cb_broadcast,
		                     &cb_unicast,
		                     &id[i],
		                     desc,
		                     desc_len,
		                     description,
		                     sizeof (description),
		                     3);      /* bid */
		if (!ad[i])
		{
			weprintf ("BRANDT_join() failed.");
			_exit (1);
		}

		if (ad[bidders]->k != ad[i]->k ||
		    ad[bidders]->m != ad[i]->m ||
		    ad[bidders]->outcome_public != ad[i]->outcome_public ||
		    ad[bidders]->time_start.abs_value_us
		    != ad[i]->time_start.abs_value_us ||
		    ad[bidders]->time_round.rel_value_us
		    != ad[i]->time_round.rel_value_us ||
		    !ad[bidders]->seller_mode || /* todo: split out */
		    ad[i]->seller_mode)
		{
			weprintf ("error/mismatch in basic auction data");
			_exit (1);
		}
	}

	*ret = 1;
}


static int
test_new_join ()
{
	int ret = 0;

	id = GNUNET_new_array (bidders + 1, uint16_t);
	for (uint16_t i = 0; i <= bidders; i++)
		id[i] = i;

	GNUNET_SCHEDULER_run (&run_new_join, &ret);

	for (uint16_t i = 0; i <= bidders; i++)
		BRANDT_destroy (ad[i]);

	return ret;
}


int
main (int argc, char *argv[])
{
	struct GNUNET_CRYPTO_EccDlogContext *edc;

	edc = GNUNET_CRYPTO_ecc_dlog_prepare (1024, 16);
	BRANDT_init (edc);

	RUN (test_new_join);

	GNUNET_CRYPTO_ecc_dlog_release (edc);
	return ret;
}