/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) Telecom SudParis
 * See COPYING in top-level directory.
 */

#include "eztrace-lib/eztrace.h"
#include "eztrace-lib/eztrace_internals.h"

struct todo_dependency dependencies_status[128];
int nb_dependencies = 0;
int dependency_update = 0;

struct token {
  struct token* next;
  struct ezt_internal_todo *todo;
};
struct token *todo_list = NULL;


static void _register_todo(struct ezt_internal_todo *todo) {
  struct token* token = malloc(sizeof(struct token));
  token->todo = todo;
  token->next = todo_list;
  todo_list = token;
}

int _todo_can_run(struct ezt_internal_todo *todo) {
  eztrace_log(dbg_lvl_debug, "[EZT_Internals]\tCan %s run ?\n",todo->name);

  if(todo_get_status(todo->name) == init_complete) {
    eztrace_log(dbg_lvl_debug, "[EZT_Internals]\t\tAlready complete, so no!\n");
    return 0;
  }

  for(int i=0; i<todo->nb_dependencies; i++) {
    enum todo_status expected = todo->dependencies[i].status;
    enum todo_status actual =  todo_get_status(todo->dependencies[i].dep_name);
    if(actual < expected) {
      eztrace_log(dbg_lvl_debug,
		  "[EZT_Internals]\t\tRequired dependency %s: %s (current status: %s)\n",
		  todo->dependencies[i].dep_name,
		  STR_STATUS(expected),
		  STR_STATUS(actual));

      return 0;
    }
  }
  return 1;
}

#if 0
static void _free_todos() {
  struct token* t = todo_list;
  while(t) {
    struct token* next = t->next;
    free(t->todo);
    free(t);    
    t = next;
  }
  return;
}
#endif

static struct ezt_internal_todo* _todo_get(const char* name) {
  struct token* token = todo_list;
  while(token) {
    if(strcmp(token->todo->name, name) == 0)
      return token->todo;
    token = token->next;
  }
  return NULL;
}


void todo_print_list() {
  for(int i=0; i<nb_dependencies; i++) {
    printf("\t%s - %s\n", dependencies_status[i].dep_name, STR_STATUS(dependencies_status[i].status));
    struct ezt_internal_todo* todo = _todo_get(dependencies_status[i].dep_name);
    if(todo) {
      /* show dependencies */
      for(int j=0; j<todo->nb_dependencies; j++) {
	printf("\t\tdepend on %s - expected: %s - current status: %s\n",
	       todo->dependencies[j].dep_name,
	       STR_STATUS(todo->dependencies[j].status),
	       STR_STATUS(todo_get_status(todo->dependencies[j].dep_name)));
      }
    }
  }
}

void enqueue_todo(const char* name,
		  void (*function)(),
		  const char* dependency,
		  enum todo_status dep_status) {
  struct ezt_internal_todo* todo = NULL;

  if((todo = _todo_get(name))==NULL) {

    /* the todo may have been executed before it was enqueued. Check in the list of dependencies */
    enum todo_status current_status = todo_get_status(name);
    if(current_status <= not_initialized)
      current_status = not_initialized;

    todo = malloc(sizeof(struct ezt_internal_todo));
    todo_set_status(name, current_status);
    strncpy(todo->name, name, STRING_MAXLEN);  
    todo->nb_dependencies = 0;
    _register_todo(todo);
  }

  todo->todo_function = function;
  if(dependency) {
    strncpy(todo->dependencies[todo->nb_dependencies].dep_name, dependency, STRING_MAXLEN);
    todo->dependencies[todo->nb_dependencies].status = dep_status;
    todo->nb_dependencies++;
  }
  todo_progress();
}

void add_todo_dependency(const char* name, const char* dependency,
			 enum todo_status dep_status) {
  struct ezt_internal_todo* todo = NULL;
  if((todo = _todo_get(name))==NULL) {
    todo = malloc(sizeof(struct ezt_internal_todo));
    todo_set_status(name, not_initialized);
    strncpy(todo->name, name, STRING_MAXLEN);  
    todo->nb_dependencies = 0;
    _register_todo(todo);
  }

  assert(todo);

  for(int i=0; i<todo->nb_dependencies; i++) {
    if((strcmp(todo->dependencies[i].dep_name, name) == 0) &&
       todo->dependencies[i].status == dep_status) {
      /* the dependency already exists */
      return;
    }
      
  }
  assert(todo->nb_dependencies+1 < NB_DEPEND_MAX);
  int index = todo->nb_dependencies;
  strncpy(todo->dependencies[index].dep_name, dependency, STRING_MAXLEN);
  todo->dependencies[index].status = dep_status;
  todo->nb_dependencies++;
}

enum todo_status todo_get_status(const char* name) {
  for(int i=0; i<nb_dependencies; i++) {
    if(strcmp(dependencies_status[i].dep_name, name)==0) {
      return dependencies_status[i].status;
    }
  }
  return status_invalid;
}

void todo_set_status(const char* name,
		     enum todo_status status) {
  for(int i=0; i<nb_dependencies; i++) {
    if(strcmp(dependencies_status[i].dep_name, name)==0) {
      if(dependencies_status[i].status != status) {
#if 1
	eztrace_log(dbg_lvl_debug, "%s status changes from %s to %s\n",
		    name,
		    STR_STATUS(dependencies_status[i].status),
		    STR_STATUS(status));
#endif
	dependency_update++;
	assert(dependencies_status[i].status < status);
      }
      dependencies_status[i].status = status;
      return;
    }
  }

  /* new status */
  int i = nb_dependencies++;
  strncpy(dependencies_status[i].dep_name, name, STRING_MAXLEN);
  dependencies_status[i].status = status;
  dependency_update++;
}

void todo_progress() {
  int nb_loop = 0;
  eztrace_log(dbg_lvl_debug, "[EZT_Internals] todo_progress\n");

  do {
    nb_loop++;
    assert(nb_loop < 100);
    dependency_update = 0;
    struct token* token = todo_list;
    while(token) {
      struct ezt_internal_todo* todo = token->todo;
      int can_run = _todo_can_run(todo);
      if(can_run) {
	eztrace_log(dbg_lvl_debug, "[EZT_Internals] Running %s\n",todo->name);
	todo->todo_function();
      }
      token = token->next;
    }
  }while(dependency_update > 0);
}

/* wait until module_name initialization is complete */
void todo_wait(const char* todo_name, enum todo_status status) {
  int i=0;
  int already_warned  =0;
  while(todo_get_status(todo_name) != status) {
    if(i++ > 10000 && !already_warned) {
      /* There's probably something wrong */

      eztrace_log(dbg_lvl_debug, "[EZT_Internals] I've been waiting for %s for a while. There's probably something wrong:\n", todo_name);
      todo_print_list();
      already_warned = 1;
      todo_progress();
    }
    todo_progress();
  }
}

