/*
 *  Copyright 1994-2022 Olivier Girondel
 *  Copyright 2019-2022 Laurent Marsac
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou 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 lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include "globals.h"
#include "paths.h"

Paths_t *paths = NULL;
char **paths_list;


static inline int
in_range(char *name, uint16_t line, float vmin, float vmax, float value)
{
  if ((value < vmin) || (value > vmax)) {
    xerror("[p] Line %u, %s must be in [%f %f], found %s = %f\n", line, name, vmin, vmax, name, value);
    return 0;
  }

  return 1;
}


int
Path_load(Path_t *path, const char *filename)
{
  FILE *stream = fopen(filename, "r");

  if (NULL == stream) {
    xperror("fopen");
    return -1;
  }

  int ret = 1, ret_range = 1;
  float x, y, z, connect, radius, c;

  for (path->size = -1; ret > 0 && ret != EOF && ret_range; path->size++) {
    ret = fscanf(stream, "%f,%f,%f,%f,%f,%f", &x, &y, &z, &connect, &radius, &c);

    ret_range = in_range("x", path->size+1, 0.0f, 1.0f, x)
                & in_range("y", path->size+1, 0.0f, 1.0f, y)
                & in_range("z", path->size+1, 0.0f, 1.0f, z)
                & in_range("radius", path->size+1, 0.0f, RADIUS_MAX, radius);

    if ((connect != 0.0f) && (connect != 1.0f)) {
      printf("[p] Line %u, connect must be in 0 or 1, found connect = %f\n", path->size+1, connect);
      ret_range = 0;
    }
  }

  if ((ret == 0) || (ret_range == 0)) {
    fclose(stream);
    xerror("[p] File %s has bad format, read %d lines\n", filename, path->size);
    return -1;
  } else {
    VERBOSE(printf("[p] File %s loaded: %d points found\n", filename, path->size));
  }

  fseek(stream, 0, SEEK_SET);
  path->data = xcalloc(1, sizeof(Path_point_t) * path->size);
  ret = 1;
  for (uint32_t i = 0; ret > 0 && ret != EOF; i++) {
    ret = fscanf(stream, "%f,%f,%f,%f,%f,%f",
                 &(path->data[i].x), &(path->data[i].y), &(path->data[i].z),
                 &(path->data[i].connect), &(path->data[i].radius), &(path->data[i].c));
  }

  fclose(stream);
  path->filename = strdup(filename);

  return 0;
}

void
Path_delete(Path_t *path)
{
  xfree(path->name);
  xfree(path->filename);
  xfree(path->data);
  xfree(path);
}

static int
Paths_compare(const void *_a, const void *_b)
{
  Path_t **a = (Path_t **)_a;
  Path_t **b = (Path_t **)_b;

  assert(NULL != *a);
  assert(NULL != *b);
  assert(NULL != (*a)->name);
  assert(NULL != (*b)->name);

  return strcasecmp((*a)->name, (*b)->name);
}


void
Paths_new(const char *directoryname)
{
  DIR *          dir;
  struct dirent *entry;
  GSList *       tmp = NULL;
  uint16_t       size = 0;
  GSList *       t;

  if (NULL == directoryname) {
    return;
  }

  dir = opendir(directoryname);
  if (NULL == dir) {
    VERBOSE(printf("[-] Error while reading paths directory content: %s\n", strerror(errno)));

    return;
  }

  while (NULL != (entry = readdir(dir))) {
    uint32_t    hash;
    gchar      *filename;
    const char *sentry = entry->d_name;
    Path_t *    path;

    if (sentry[0] == '.') {
      continue;
    }

    /* we only look for filenames that end with
     * ".path" (arbitrarily choosen)
     */
    if (NULL == (filename = strrchr(sentry, '.')))
      /* does not have a trailing '.' -> next one */
    {
      continue;
    }

    if (strncmp(filename, ".path", 5 * sizeof(char))) {
      continue;
    }

    path = xcalloc(1, sizeof(Path_t));
    hash = FNV_hash(sentry);
    filename = g_strdup_printf("%s/%s", directoryname, sentry);

    if (Path_load(path, filename) != 0) {
      Path_delete(path);
      g_free(filename);
      continue;
    } else {
      xfree(path->name);
      path->name = strdup(sentry);
      path->id = hash;
      g_free(filename);

      /* we reuse char *filename here */
      if (NULL != (filename = strrchr(path->name, '.'))) {
        *filename = '\0';
      }
    }

    tmp = g_slist_prepend(tmp, (gpointer)path);
    size++;

    for (t = g_slist_next(tmp); NULL != t; t = g_slist_next(t))
      if (((Path_t *)t->data)->id == hash)
        xerror("Duplicated path hash: %s / %s, %li\n",
               ((Path_t *)t->data)->name, sentry, hash);
  }
  if (closedir(dir) == -1) {
    xperror("closedir");
  }

  if (size) {
    paths = xcalloc(1, sizeof(Paths_t));
    paths->paths = xcalloc(size, sizeof(Path_t *));
    uint16_t i;
    for (i = 0, t = tmp; NULL != t; t = g_slist_next(t), i++) {
      paths->paths[i] = (Path_t *)t->data;
    }
    g_slist_free(tmp);
    paths->size = size;

    qsort((void *)paths->paths, (size_t)paths->size, (size_t)sizeof(Path_t *),
          &Paths_compare);

    paths_list = xcalloc(paths->size, sizeof(char *));
    for (int32_t n = 0; n < paths->size; n++) {
      paths_list[n] = paths->paths[n]->name;
    }
    paths->shuffler = Shuffler_new(size); // default mode is shuffle
  }

  VERBOSE(printf("[c] Loaded %d paths\n", size));
}


void
Paths_delete(void)
{
  if (NULL != paths) {
    for (uint16_t n = 0; n < paths->size; n++) {
      Path_delete(paths->paths[n]);
    }
    xfree(paths->paths);
    Shuffler_delete(paths->shuffler);
    xfree(paths);
    xfree(paths_list);
  }
}


void
Path_scale_and_center(Path_point_t *dst, Path_point_t *src, uint32_t size, float user_scale)
{
  /* copy path and find max x,y and radius */
  float path_max_x = 0, path_max_y = 0;
  for (uint32_t n = 0; n < size; n++) {
    dst[n]          = src[n];
    path_max_x      = MAX(dst[n].x, path_max_x);
    path_max_y      = MAX(dst[n].y, path_max_y);
  }

  float scale = MIN(WIDTH, HEIGHT);
  if (path_max_y / path_max_x < (float)HEIGHT / (float)WIDTH) {
    scale = WIDTH;
  }
  scale *= user_scale;

  for (uint32_t n = 0; n < size; n++) {
    /* center path */
    dst[n].x =  WIDTH / 2 + scale * (dst[n].x - path_max_x / 2);
    dst[n].y = HEIGHT / 2 + scale * (dst[n].y - path_max_y / 2);

    /* crop coordinates on borders */
    dst[n].x = MAX(MIN(dst[n].x, MAXX), 0);
    dst[n].y = MAX(MIN(dst[n].y, MAXY), 0);
  }
}
