/*
 * To disable the checks done by this program, see below at the beginning
 * of the function 'main'.
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "database.h"

#define T_TRACER 1
#include "T.h"

/* VCD specific defines and includes
 * If the codebase changes, it may need to be updated
 */
#define ENABLE_USE_CPU_EXECUTION_TIME
#include "../LOG/vcd_signal_dumper.c"

/*
 * Dummy needed by assertions.h
 */
void exit_function(const char *file, const char *function, const int line, const char *s, const int assert)
{
  if (assert) {
    abort();
  } else {
    exit(EXIT_SUCCESS);
  }
}

void err(char *fmt, ...) __attribute__((format(printf, 1, 2)));
void err(char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);

  printf("\x1b[31m");
  printf("error: ");
  vprintf(fmt, ap);
  printf("\n"
"\x1b[33m\n"
"You probably added a VCD trace (variable or function) but you did not\n"
"update T_messages.txt and/or T_defs.h in common/utils/T/\n"
"\n"
"Be sure to add the new trace to T_messages.txt, at the right place in the\n"
"file. Do not forget to define VCD_NAME with an identical value as found\n"
"in the array eurecomVariablesNames or eurecomFunctionsNames.\n"
"\n"
"Be sure to update VCD_NUM_FUNCTIONS, VCD_NUM_VARIABLES, VCD_FIRST_FUNCTION\n"
"and VCD_FIRST_VARIABLE in T_defs.h\n"
"\n"
"The same procedure has to be followed when you delete a VCD trace.\n"
"Delete it in T_messages.txt as well and update T_defs.h\n"
"\n"
"You can disable those VCD checks at development time.\n"
"To disable the VCD checks see the file common/utils/T/check_vcd.c\n"
"Do not push any modification that disables the VCD checks to the\n"
"main repository.\n");

  printf("\x1b[m\n");

  va_end(ap);

  exit(1);
}

int main(int argc, char *argv[])
{
  /* to disable the checks done by this program, uncomment the following
   * line, ie. remove the leading '//'
   */
  //return 0;

  if (argc != 3) err("usage: %s <T_messages.txt> <vcd_signal_dumper.h>", argv[0]);
  char *T_msg_txt = argv[1];
  char *vcd_sig_dmp = argv[2];
  void *database = parse_database(T_msg_txt);
  int number_of_events;
  int first_var = -1;
  int last_var;
  int first_fun = -1;
  int last_fun;
  char *prefix;
  int prefix_len;
  char *name;
  char *vcd_name;
  int i;
  FILE *in;
  char *l = NULL;
  size_t lsize;

  if (database == NULL) err("something wrong with T_messages.txt");

  /* check the value of VCD_NUM_FUNCTIONS */
  if (VCD_NUM_FUNCTIONS != sizeof(eurecomFunctionsNames) / sizeof(char *))
    err("VCD_NUM_FUNCTIONS (%d) must be equal to %zd",
        VCD_NUM_FUNCTIONS,
        sizeof(eurecomFunctionsNames) / sizeof(char *));

  /* check the value of VCD_NUM_VARIABLES */
  if (VCD_NUM_VARIABLES != sizeof(eurecomVariablesNames) / sizeof(char *))
    err("VCD_NUM_VARIABLES (%d) must be equal to %zd",
        VCD_NUM_VARIABLES,
        sizeof(eurecomVariablesNames) / sizeof(char *));

  number_of_events = number_of_ids(database);
  if (number_of_events == 0) err("no event defined in T_messages.txt");

  /* T_messages.txt ends with VCD VARIABLES followed by VCD FUNCTIONS
   * followed by nothing.
   * Let's check.
   */

  /* check VCD VARIABLES traces in T_messages.txt */
  prefix = "VCD_VARIABLE_";
  prefix_len = strlen(prefix);

  for (i = 0; i < number_of_events; i++) {
    name = event_name_from_id(database, i);
    if (strncmp(name, prefix, prefix_len)) continue;
    first_var = i;
    break;
  }
  if (first_var == -1)
    err("no VCD_VARIABLE_ found in T_messages.txt");
  for (; i < number_of_events; i++) {
    name = event_name_from_id(database, i);
    if (strncmp(name, prefix, prefix_len)) break;
  }
  last_var = i-1;

  /* check VCD FUNCTIONS traces in T_messages.txt */
  if (i == number_of_events)
    err("no VCD_FUNCTION_ found in T_messages.txt");

  prefix = "VCD_FUNCTION_";
  prefix_len = strlen(prefix);

  first_fun = i;

  name = event_name_from_id(database, i);
  if (strncmp(name, prefix, prefix_len))
    err("last VCD_VARIABLE_ not followed by a VCD_FUNCTION_ in T_messages.txt");

  for (; i < number_of_events; i++) {
    name = event_name_from_id(database, i);
    if (strncmp(name, prefix, prefix_len)) break;
  }

  if (i != number_of_events)
    err("T_messages.txt does not end with a VCD_FUNCTION_ trace");

  last_fun = i-1;

  if (first_var != (unsigned)VCD_FIRST_VARIABLE)
    err("VCD_FIRST_VARIABLE is not correct in T_defs.h");
  if (first_fun != (unsigned)VCD_FIRST_FUNCTION)
    err("VCD_FIRST_FUNCTION is not correct in T_defs.h");
  if (last_var-first_var+1 != VCD_NUM_VARIABLES)
    err("VCD_NUM_VARIABLES is not correct in T_defs.h");
  if (last_fun-first_fun+1 != VCD_NUM_FUNCTIONS)
    err("VCD_NUM_FUNCTIONS is not correct in T_defs.h");

  /* check that VCD_NAME is identical to
   * eurecomVariablesNames[x]/eurecomFunctionsNames[x]
   */
  prefix = "VCD_VARIABLE_";
  prefix_len = strlen(prefix);

  for (i = 0; i < number_of_events; i++) {
    name = event_name_from_id(database, i);
    if (strncmp(name, prefix, prefix_len)) continue;
    vcd_name = event_vcd_name_from_id(database, i);
    if (vcd_name == NULL)
      err("%s has no VCD_NAME in T_messages.txt", name);
    if (strcmp(vcd_name, eurecomVariablesNames[i - first_var]))
      err("%s has a wrong VCD_NAME in T_messages.txt", name);
  }

  prefix = "VCD_FUNCTION_";
  prefix_len = strlen(prefix);

  for (i = 0; i < number_of_events; i++) {
    name = event_name_from_id(database, i);
    if (strncmp(name, prefix, prefix_len)) continue;
    vcd_name = event_vcd_name_from_id(database, i);
    if (vcd_name == NULL)
      err("%s has no VCD_NAME in T_messages.txt", name);
    if (strcmp(vcd_name, eurecomFunctionsNames[i - first_fun]))
      err("%s has a wrong VCD_NAME in T_messages.txt", name);
  }

  /* check IDs - these checks are difficult because we parse
   * common/utils/LOG/vcd_signal_dumper.h which is expected to
   * be formatted as is:
   * - define vcd_signal_dump_variables then vcd_signal_dump_functions
   * - one VCD_XXXX per line starting with two spaces
   *   followed by ',' or '=' with no space in between
   * - no #ifdef / #if is taken into account
   * - we require VCD_SIGNAL_DUMPER_VARIABLES_END and
   *   VCD_SIGNAL_DUMPER_FUNCTIONS_END at the end of each array,
   *   each on a line of its own with two spaces before and nothing after.
   *
   * If these checks fail, consider formatting
   * common/utils/LOG/vcd_signal_dumper.h as expected here, if
   * it makes sense of course. Otherwise, change the code below.
   *
   * In common/utils/LOG/vcd_signal_dumper.h a valid name is
   * either VCD_SIGNAL_DUMPER_VARIABLES_ABC or
   * VCD_SIGNAL_DUMPER_FUNCTIONS_XYZ
   * and in T_messages.txt the corresponding name has to be
   * VCD_VARIABLE_ABC or VCD_FUNCTION_XYZ
   */
  i = first_var;
  in = fopen(vcd_sig_dmp, "r");
  if (in == NULL) err("could not open %s\n", vcd_sig_dmp);
  while (1) {
    char *x = "  VCD_SIGNAL_DUMPER_VARIABLES_";
    ssize_t r;
    free(l);
    l = NULL;
    lsize = 0;
    r = getline(&l, &lsize, in);
    if (r == -1) break;
    if (!strcmp(l, "  VCD_SIGNAL_DUMPER_VARIABLES_END\n")) break;
    /* remove ',' or '=' if found */
    { char *s=l; while (*s) { if (*s==','||*s=='=') { *s=0; break; } s++; } }
    if (strncmp(l, x, strlen(x))) continue;
    if (!(i >= first_var && i <= last_var))
      err("T_messages.txt is not correct with respect to VCD VARIABLES");
    name = event_name_from_id(database, i);
    if (strcmp(l+strlen(x), name+strlen("VCD_VARIABLE_")))
      err("%s is not correct in T_messages.txt", name);
    i++;
  }
  if (i != last_var + 1) err("VCD VARIABLES wrong in T_messages.txt");
  while (1) {
    char *x = "  VCD_SIGNAL_DUMPER_FUNCTIONS_";
    ssize_t r;
    free(l);
    l = NULL;
    lsize = 0;
    r = getline(&l, &lsize, in);
    if (r == -1) break;
    if (!strcmp(l, "  VCD_SIGNAL_DUMPER_FUNCTIONS_END\n")) break;
    /* remove ',' or '=' if found */
    { char *s=l; while (*s) { if (*s==','||*s=='=') { *s=0; break; } s++; } }
    if (strncmp(l, x, strlen(x))) continue;
    if (!(i >= first_fun && i <= last_fun))
      err("T_messages.txt is not correct with respect to VCD FUNCTIONS");
    name = event_name_from_id(database, i);
    if (strcmp(l+strlen(x), name+strlen("VCD_FUNCTION_")))
      err("%s is not correct in T_messages.txt", name);
    i++;
  }
  free(l);
  fclose(in);
  free_database(database);
  if (i != last_fun + 1) err("VCD FUNCTIONS wrong in T_messages.txt");

  return 0;
}
