#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "utils.h"
#include "event.h"
#include "../T_defs.h"

void usage(void)
{
  printf(
    "usage: <number of tags> <input record file> <output curated record file>\n"
    "options:\n"
    "    -d <database file>        this option is mandatory\n"
    "    -e <event name>           trace given event (default CALIBRATION_CHANNEL_ESTIMATES)\n"
  );
  exit(1);
}

#define ERR printf("ERROR: read file %s failed\n", input_filename)

typedef struct {
  OBUF b;
  struct timespec t;
  int filled;
  int error;
} cache_t;

void clear_cache(int n, cache_t *c)
{
  int i;
  for (i = 0; i < n; i++) {
    c[i].filled = 0;
    c[i].error = 0;
    c[i].b.osize = 0;
  }
}

void store_in_cache(cache_t *c, int pos, OBUF *b)
{
  int i;
  for (i = 0; i < b->osize; i++)
    PUTC(&c[pos].b, b->obuf[i]);
}

int get_field(database_event_format *f, char *field, char *type)
{
  int i;
  for (i = 0; i < f->count; i++)
    if (!strcmp(f->name[i], field)) {
      if (strcmp(f->type[i], type)) break;
      return i;
    }
  printf("bad field %s, check that it exists and has type '%s'\n",field,type);
  exit(1);
}

void process_cache(FILE *out, cache_t *c, int n, int frame, int subframe)
{
  int i;
  struct tm *t;

  for (i = 0; i < n; i++)
    if (c[i].filled == 0 || c[i].error == 1)
      goto error;

  for (i = 0; i < n; i++)
    fwrite(c[i].b.obuf, c[i].b.osize, 1, out);

  clear_cache(n, c);
  return;

error:
  printf("ERROR: incorrect data at frame %d subframe %d", frame, subframe);
  for (i = 0; i < n; i++) if (c[i].filled) {
    t = localtime(&c[i].t.tv_sec);
    printf(" [tag %d time %2.2d:%2.2d:%2.2d.%9.9ld]", i,
           t->tm_hour, t->tm_min, t->tm_sec, c[i].t.tv_nsec);
  }
  printf("\n");
  clear_cache(n, c);
}

int main(int n, char **v)
{
  char *database_filename = NULL;
  void *database;
  int  channel_estimate_id;
  int  number_of_tags = -1;
  char *input_filename = NULL;
  char *output_filename = NULL;
  char *event_name = "CALIBRATION_CHANNEL_ESTIMATES";
  int  i;
  FILE *in;
  FILE *out;
  database_event_format f;
  int  frame_arg;
  int  subframe_arg;
  int  tag_arg;

  cache_t *cache;
  int     cur_frame = -1;
  int     cur_subframe = -1;
  int     frame;
  int     subframe;
  int     tag;

  for (i = 1; i < n; i++) {
    if (!strcmp(v[i], "-h") || !strcmp(v[i], "--help")) usage();
    if (!strcmp(v[i], "-d")) { if (i > n-2) usage();
      database_filename = v[++i]; continue; }
    if (!strcmp(v[i], "-e")) { if (i > n-2) usage();
      event_name = v[++i]; continue; }
    if (number_of_tags == -1) { number_of_tags = atoi(v[i]);
      if (number_of_tags <= 0) {usage();} continue; }
    if (input_filename == NULL) { input_filename = v[i]; continue; }
    if (output_filename == NULL) { output_filename = v[i]; continue; }
    usage();
  }

  if (database_filename == NULL) {
    printf("ERROR: provide a database file (-d)\n");
    exit(1);
  }

  if (output_filename == NULL || input_filename == NULL || number_of_tags == -1)
    usage();

  database = parse_database(database_filename);

  channel_estimate_id = event_id_from_name(database, event_name);
  f = get_format(database, channel_estimate_id);

  frame_arg    = get_field(&f, "frame",    "int");
  subframe_arg = get_field(&f, "subframe", "int");
  tag_arg      = get_field(&f, "tag",      "int");

  in = fopen(input_filename, "r");
  if (in == NULL) { perror(input_filename); abort(); }
  out = fopen(output_filename, "w");
  if (out == NULL) { perror(output_filename); abort(); }

  cache = calloc(number_of_tags, sizeof(cache_t));
  if (cache == NULL) { perror("malloc"); exit(1); }

  clear_cache(number_of_tags, cache);

  OBUF ebuf = { osize: 0, omaxsize: 0, obuf: NULL };

  while (1) {
    int type;
    int32_t length;
    char *v;
    int vpos = 0;
    struct timespec t;
    char *buf;

    /* read event from file */
    if (fread(&length, 4, 1, in) != 1) break;
    if (ebuf.omaxsize < length) {
      ebuf.omaxsize = (length + 65535) & ~65535;
      ebuf.obuf = realloc(ebuf.obuf, ebuf.omaxsize);
      if (ebuf.obuf == NULL) { printf("out of memory\n"); exit(1); }
    }
    v = ebuf.obuf;
    memcpy(v+vpos, &length, 4);
    vpos += 4;
#ifdef T_SEND_TIME
    if (length < sizeof(struct timespec)) { ERR; break; }
    if (fread(&t, sizeof(struct timespec), 1, in) != 1) { ERR; break; }
    memcpy(v+vpos, &t, sizeof(struct timespec));
    vpos += sizeof(struct timespec);
    length -= sizeof(struct timespec);
#endif
    if (length < sizeof(int)) { ERR; break; }
    if (fread(&type, sizeof(int), 1, in) != 1) { ERR; break; }
    memcpy(v+vpos, &type, sizeof(int));
    vpos += sizeof(int);
    length -= sizeof(int);
    if (length) if (fread(v+vpos, length, 1, in) != 1) { ERR; break; }
    buf = v + vpos;
    vpos += length;
    ebuf.osize = vpos;

    if (type != channel_estimate_id) continue;

    event e;
#ifdef T_SEND_TIME
    e = new_event(t, type, length, buf, database);
#else
    e = new_event(type, length, buf, database);
#endif

    frame    = e.e[frame_arg].i;
    subframe = e.e[subframe_arg].i;
    tag      = e.e[tag_arg].i;

    if (tag < 0 || tag >= number_of_tags) {
      struct tm *tt;
      tt = localtime(&t.tv_sec);
      printf("ERROR: invalid tag (%d), skipping event for frame %d subframe %d (time %2.2d:%2.2d:%2.2d.%9.9ld)\n",
             tag, frame, subframe,
             tt->tm_hour, tt->tm_min, tt->tm_sec, t.tv_nsec);
      continue;
    }

    if (cur_frame != frame || cur_subframe != subframe)
      if (cur_frame != -1)
        process_cache(out, cache, number_of_tags, cur_frame, cur_subframe);

    cur_frame = frame;
    cur_subframe = subframe;

    if (cache[tag].filled) {
      struct tm *tt;
      tt = localtime(&t.tv_sec);
      printf("ERROR: tag %d present twice at frame %d subframe %d (time %2.2d:%2.2d:%2.2d.%9.9ld)\n",
             tag, frame, subframe,
             tt->tm_hour, tt->tm_min, tt->tm_sec, t.tv_nsec);
      cache[tag].error = 1;
      continue;
    }

    store_in_cache(cache, tag, &ebuf);
    cache[tag].filled = 1;
    cache[tag].t = t;
  }

  if (fclose(in)) perror(input_filename);
  if (fclose(out)) perror(output_filename);
  free(cache);

  return 0;
}
