#include <msg/msg.h>
#include "sched_struct.h"
#include <stdio.h>
#include <stdlib.h>

/* Create a log channel to have nice and clean outputs. */
#include "xbt/log.h"
#include "xbt/asserts.h"
XBT_LOG_NEW_DEFAULT_CATEGORY(TD_MSG, "Messages specific for this example");


double task_computation_amount = 1000000;	/* default value overriden by the deployment file */
double task_communication_amount = 10000;	/* default value overriden by the deployment file */
double request_computation_amount = 1000;
double request_communication_amount = 1000;
double global_time = 0;
int heuristic = 0;

typedef enum {
  FASTEST_FIRST = 1
} HEURISTICS;

m_task_t work_task_create(void)
{
  static int number = 0;
  char buffer[128];

  sprintf(buffer, "Work Task (%d)", number++);

  return MSG_task_create(buffer, task_computation_amount,
			 task_communication_amount, NULL);
}

void work_task_destroy(m_task_t * task)
{
  MSG_task_destroy(*task);
  *task = MSG_TASK_UNINITIALIZED;
}

m_task_t request_task_create(void)
{
  static int number = 0;
  char buffer[128];
  request_data_t data = calloc(1, sizeof(struct s_request_data));

  sprintf(buffer, "Request Task (%d)", number++);

  data->who_am_i = MSG_host_self();

  return MSG_task_create(buffer, request_computation_amount,
			 request_communication_amount, data);
}

void request_task_destroy(m_task_t * task)
{
  free((*task)->data);
  MSG_task_destroy(*task);
  *task = MSG_TASK_UNINITIALIZED;
}

int sched_master(int argc, char *argv[])
{
  int nb_task = 0;
  xbt_fifo_t task_list = xbt_fifo_new();

  {				/* Creating all the tasks to proces */
    int i;
    xbt_assert0((argc == 4),
		"Usage: sched_master <number_of_tasks> <task_computation> <task_communication>");

    xbt_assert1(sscanf(argv[1], "%d", &nb_task) == 1, "Invalid number %s",
		argv[1]);
    xbt_assert1(sscanf(argv[2], "%gl", (float *)&task_computation_amount) == 1,
		"Invalid computation amount %s", argv[2]);
    xbt_assert1(sscanf(argv[3], "%gl", (float *)&task_communication_amount) == 1,
		"Invalid communication amount %s", argv[2]);

    for (i = 0; i < nb_task; i++)
      xbt_fifo_push(task_list, work_task_create());
  }

  DEBUG1("Got %d task to process.", xbt_fifo_size(task_list));
  DEBUG0("Waiting for some slave to contact me.");

  while (xbt_fifo_size(task_list) > 0) {
    m_task_t task = xbt_fifo_shift(task_list);
    m_task_t request = MSG_TASK_UNINITIALIZED;
    m_host_t slave;
    double power;
    double bandwidth;

    MSG_task_get(&request, REQUEST_CHANNEL);
    MSG_task_execute(request);

    slave = ((request_data_t) request->data)->who_am_i;
    power = ((request_data_t) request->data)->computing_power;
    bandwidth = ((request_data_t) request->data)->bandwidth;

    DEBUG4
	("Received \"%s\" from \"%s\". Here is its computing power : %f and its bandwidth : %f ",
	 request->name, MSG_host_get_name(slave), power, bandwidth);
    request_task_destroy(&request);

    MSG_task_put(task, slave, GENERAL_CHANNEL);
  }

  DEBUG0("No more Task to process. Bye!");
  return 0;
}

int sched_slave(int argc, char *argv[])
{
  m_host_t father = NULL;

  xbt_assert0((argc == 2), "Usage: sched_slave <master_name>");
  xbt_assert1((father =
	       MSG_get_host_by_name(argv[1])), "Unknown host '%s'",
	      argv[1]);

  while (1) {
    m_task_t request = request_task_create();
    m_task_t task = MSG_TASK_UNINITIALIZED;

    DEBUG0("I have nothing to do. Let's ask some work to my father");
    MSG_task_put(request, father, REQUEST_CHANNEL);
    MSG_task_get(&task, GENERAL_CHANNEL);
    DEBUG1("Great ! Some Work ! Processing %s", task->name);
    MSG_task_execute(task);
    work_task_destroy(&task);
  }
}

static int by_power(const void *a, const void *b)
{
  double power_a =
      (((request_data_t) (*((m_task_t *) a))->data))->computing_power;
  double power_b =
      (((request_data_t) (*((m_task_t *) b))->data))->computing_power;
  if (power_a >= power_b)
    return -1;
  if (power_a == power_b)
    return 0;
  return 1;
}

static void *extract_fastest(xbt_fifo_t request_list)
{
  int i;
  int n = xbt_fifo_size(request_list);
  void **tab = calloc(n, sizeof(void *));

  for (i = 0; i < n; i++)
    tab[i] = xbt_fifo_shift(request_list);
  qsort(tab, n, sizeof(void *), by_power);
  for (i = 0; i < n; i++)
    xbt_fifo_push(request_list, tab[i]);

  return (xbt_fifo_shift(request_list));
}

int sched_smarter_master(int argc, char *argv[])
{
  int nb_task = 0;
  xbt_fifo_t task_list = xbt_fifo_new();
  int max_request_number = 1;
  xbt_fifo_t pending_request = xbt_fifo_new();

  {				/* Creating all the tasks to proces */
    int i;
    xbt_assert0((argc == 6),
		"Usage: sched_smarter_master <number_of_tasks> <task_computation> <task_communication> <heuristic_nb> <max_pending_request>");

    xbt_assert1(sscanf(argv[1], "%d", &nb_task) == 1, "Invalid number %s",
		argv[1]);
    xbt_assert1(sscanf(argv[2], "%gl", (float *)&task_computation_amount) == 1,
		"Invalid computation amount %s", argv[2]);
    xbt_assert1(sscanf(argv[3], "%gl", (float *)&task_communication_amount) == 1,
		"Invalid communication amount %s", argv[2]);
    xbt_assert1(sscanf(argv[4], "%d", &heuristic) == 1,
		"Invalid heuristic number %s", argv[4]);
    xbt_assert1(sscanf(argv[5], "%d", &max_request_number) == 1,
		"Invalid request number %s", argv[5]);

    for (i = 0; i < nb_task; i++)
      xbt_fifo_push(task_list, work_task_create());
  }

  DEBUG1("Got %d task to process.", xbt_fifo_size(task_list));
  DEBUG0("Waiting for some slave to contact me.");

  while (xbt_fifo_size(task_list) > 0) {
    if ((xbt_fifo_size(pending_request) < max_request_number)) {
      m_task_t request = MSG_TASK_UNINITIALIZED;
      MSG_task_get(&request, REQUEST_CHANNEL);
      MSG_task_execute(request);
      xbt_fifo_push(pending_request, request);
    } else {
      m_task_t request = MSG_TASK_UNINITIALIZED;
      m_task_t task = xbt_fifo_shift(task_list);
      m_host_t slave;
      double power, bandwidth;

      switch (heuristic) {
		  case FASTEST_FIRST:
		  {
			  request = extract_fastest(pending_request);
			  break;
		  }
		  default:
		  {
			  request = xbt_fifo_shift(pending_request);
		  }
      }

      slave = ((request_data_t) request->data)->who_am_i;
      power = ((request_data_t) request->data)->computing_power;
      bandwidth = ((request_data_t) request->data)->bandwidth;

      DEBUG4
	  ("Received \"%s\" from \"%s\". Here is its computing power : %f and its bandwidth : %f ",
	   request->name, MSG_host_get_name(slave), power, bandwidth);
      request_task_destroy(&request);

      task->data = calloc(1, sizeof(double));
      (*((double *) task->data)) = ((double) MSG_get_clock());
      MSG_task_put(task, slave, GENERAL_CHANNEL);
    }
  }
  DEBUG0("No more Task to process. Bye!");
  return 0;
}

int sched_smarter_slave(int argc, char *argv[])
{
  double processing_time = 10.0;
  double transfer_time = 0.02;
  m_host_t father = NULL;

  xbt_assert0((argc == 2), "Usage: sched_smarter_slave <master_name>");
  xbt_assert1((father =
	       MSG_get_host_by_name(argv[1])), "Unknown host '%s'",
	      argv[1]);

  while (1) {
    m_task_t request = request_task_create();
    m_task_t task = MSG_TASK_UNINITIALIZED;

    /* Send a request */
    ((request_data_t) request->data)->computing_power =
	1 / processing_time;
    ((request_data_t) request->data)->bandwidth = 1 / transfer_time;
    DEBUG0("I have nothing to do. Let's ask some work to my father");
    MSG_task_put(request, father, REQUEST_CHANNEL);
    transfer_time = MSG_get_clock();

    /* Get some work */
    MSG_task_get(&task, GENERAL_CHANNEL);
    transfer_time =
	MSG_get_clock() - MAX(transfer_time, (*((double *) task->data)));

    /* Let's work! */
    DEBUG1("Great ! Some Work ! Processing %s", task->name);
    processing_time = MSG_get_clock();
    MSG_task_execute(task);
    free(task->data);
    work_task_destroy(&task);
    processing_time = MSG_get_clock() - processing_time;
  }
}
