/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  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 "sequence.h"
#include "images.h"
#include "colormaps.h"
#include "globals.h"


static gint
Sequence_find_plugin(gconstpointer a, gconstpointer b)
{
  const Layer_t *la = (const Layer_t *)a;
  const Plugin_t *pb = (const Plugin_t *)b;

  assert(NULL != la);
  return (la->plugin == pb) ? 0 : 1;
}


GList *
Sequence_find(const Sequence_t *s, const Plugin_t *p)
{
  return g_list_find_custom(s->layers, (gconstpointer)p, &Sequence_find_plugin);
}


/* -1 if not found, position else */
int16_t
Sequence_find_position(const Sequence_t *s, const Plugin_t *p)
{
  int16_t pos = 0;
  GList *tmp;

  tmp = g_list_first(s->layers);
  while (NULL != tmp) {
    Layer_t *l = (Layer_t *)tmp->data;
    if (l->plugin == p) {
      return pos;
    }
    pos++;
    tmp = g_list_next(tmp);
  }

  return -1;
}


Sequence_t *
Sequence_new(const uint64_t id)
{
  Sequence_t *s = xcalloc(1, sizeof(Sequence_t));

  s->id = id;
  s->auto_colormaps = s->auto_images = -1; // undefined by default
  s->bandpass_min = 10;
  s->bandpass_max = 245;

  return s;
}


void
Sequence_clear(Sequence_t *s, const uint64_t new_id)
{
  GList *tmp;

  if (NULL == s) {
    xerror("Attempt to Sequence_clear() a NULL sequence\n");
  }

  s->id = new_id ? new_id : 0;

  if (NULL != s->name) {
    xfree(s->name);
  }

  tmp = g_list_first(s->layers);
  while (NULL != tmp) {
    Layer_t *l = (Layer_t *)tmp->data;
    Layer_delete(l);
    tmp = g_list_next(tmp);
  }
  g_list_free(s->layers);
  s->layers = NULL;

  s->lens = NULL;

  s->image_id = 0;
  s->auto_images = -1; // undefined

  s->cmap_id = 0;
  s->auto_colormaps = -1; // undefined

  Sequence_changed(s);
}


void
Sequence_changed(Sequence_t *s)
{
  assert(NULL != s);
  s->changed = 1;
}


void
Sequence_delete(Sequence_t *s)
{
  if (NULL != s) {
    // FIXME -q is not taken into account when running tests
    if (NULL == getenv("LEBINIOU_TEST")) {
      VERBOSE(printf("[s] Freeing sequence id %"PRIu64"\n", s->id));
    }

    GList *tmp = s->layers;
    while (NULL != tmp) {
      Layer_t *l = (Layer_t *)tmp->data;
      Layer_delete(l);
      tmp = g_list_next(tmp);
    }

    g_list_free(s->layers);
    if (NULL != s->name) {
      g_free(s->name);
    }
    if (NULL != s->params3d) {
      json_decref(s->params3d);
    }
    xfree(s);
  }
}


void
Sequence_display(const Sequence_t *s)
{
  GList *tmp;
  const char *cmap_name;
  const char *image_name;
  int after_lens = 0;

  if (NULL == s) {
    xerror("Attempt to display a NULL Sequence\n");
  } else {
    // FIXME -q is not taken into account when running tests
    if (NULL == getenv("LEBINIOU_TEST")) {
      VERBOSE(printf("[s] Sequence id: %"PRIu64"\n", s->id));
    }
  }

  uint8_t v = libbiniou_verbose;

  // Enable display when running tests
  if (NULL != getenv("LEBINIOU_TEST")) {
    libbiniou_verbose = 1;
  }

  VERBOSE(printf("[s] Name: %s\n", (NULL != s->name) ? s->name : "(none)"));

  if (NULL != images) {
    image_name = (s->image_id != 0) ? Images_name(s->image_id) : "current";
    VERBOSE(printf("[s] Image: %s\n", image_name));
  }

  cmap_name = (s->cmap_id != 0) ? Colormaps_name(s->cmap_id) : "current";
  VERBOSE(printf("[s] Colormap: %s\n", cmap_name));

  for (tmp = g_list_first(s->layers); NULL != tmp; tmp = g_list_next(tmp)) {
    Layer_t *layer = (Layer_t *)tmp->data;
    Plugin_t *P = layer->plugin;

    if (NULL != P) {
      if ((NULL != s->lens) && (P == s->lens)) {
        if (!after_lens) {
          VERBOSE(printf("[s] --- %s\n", P->name));
          after_lens = 1;
        } else {
          VERBOSE(printf("[s]     %s\n", P->name));
        }
      } else {
        if (after_lens) {
          VERBOSE(printf("[s]     %s\n", P->name));
        } else {
          VERBOSE(printf("[s]  |  %s\n", P->name));
        }
      }
    } else {
      xerror("Oops got a NULL plugin\n");
    }
  }
  libbiniou_verbose = v;
}


gint
Sequence_sort_func(gconstpointer a, gconstpointer b)
{
  const Sequence_t *sa = (const Sequence_t *)a;
  const Sequence_t *sb = (const Sequence_t *)b;

  assert(NULL != sa);
  assert(NULL != sb);

  return (sa->id > sb->id) ? 1 : (sa->id == sb->id) ? 0 : -1;
}


void
Sequence_insert(Sequence_t *s, Plugin_t *p)
{
  Layer_t *layer = Layer_new(p);

  assert(NULL != p);
  /* set layer mode */
  if (NULL != p->mode) {
    assert(*(p->mode) < NB_LAYER_MODES);
    layer->mode = (enum LayerMode)*(p->mode);
  }

  /* insert plugin in sequence */
  if (*p->options & BO_FIRST) {
    s->layers = g_list_prepend(s->layers, (gpointer)layer);
  } else if (*p->options & BO_LAST) {
    /* insert before lens if any, or at the end if no lens */
    GList *lens = (NULL != s->lens) ? Sequence_find(s, s->lens) : NULL;
    s->layers = g_list_insert_before(s->layers, lens, (gpointer)layer);
  } else {
    /* add to the end anyway if nothing specified */
    s->layers = g_list_append(s->layers, (gpointer)layer);
  }

  /* set as lens if it's a lens, and there is no lens yet */
  if ((*p->options & BO_LENS) && (NULL == s->lens)) {
    s->lens = (Plugin_t *)p;
  }

  Sequence_changed(s);
}


void
Sequence_insert_at_position(Sequence_t *s, const uint16_t position, Plugin_t *p) /* TODO LayerMode ? */
{
  Layer_t *layer = Layer_new(p);

  /* set layer mode */
  if (NULL != p->mode) {
    layer->mode = (enum LayerMode)*p->mode;
  }

  /* insert plugin in sequence */
  s->layers = g_list_insert(s->layers, (gpointer)layer, position);

  /* set as lens if it's a lens, and there is no lens yet */
  if ((*p->options & BO_LENS) && (NULL == s->lens)) {
    s->lens = (Plugin_t *)p;
  }

  Sequence_changed(s);
}


void
Sequence_remove(Sequence_t *s, const Plugin_t *p)
{
  const GList *p_layer = Sequence_find(s, p);
  Layer_t *layer = (Layer_t *)p_layer->data;

  /* remove plugin from sequence */
  Layer_delete(layer);

  s->layers = g_list_remove(s->layers, (gconstpointer)layer);

  /* unset if it is a lens we removed */
  if (s->lens == p) {
    s->lens = NULL;
  }

  Sequence_changed(s);
}


uint8_t
Sequence_size(const Sequence_t *seq)
{
  return g_list_length(seq->layers);
}

