/*
 *  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 "globals.h"
#include "brandom.h"
#include "biniou.h"
#include "sequencemanager.h"
#include "pthread_utils.h"


// #define DEBUG_SEQUENCEMANAGER


SequenceManager_t *
SequenceManager_new(void)
{
  SequenceManager_t *sm = NULL;

  sm = xcalloc(1, sizeof(SequenceManager_t));
  sm->cur = Sequence_new(0);
  sm->next = Sequence_new(0);
  xpthread_mutex_init(&sm->mutex, NULL);

  return sm;
}


void
SequenceManager_delete(SequenceManager_t *sm)
{
  Sequence_delete(sm->cur);
  Sequence_delete(sm->next);
  xpthread_mutex_destroy(&sm->mutex);
  xfree(sm);
}


void
SequenceManager_move_selected_front(Sequence_t *seq)
{
  /* move selected plugin/layer up (towards the beginning of the sequence) */
  GList *list, *prev;
  const GList *where;
  const Layer_t *layer;
  const Plugin_t *P = NULL;

  list = seq->layers;
  if (g_list_length(list) <= 1) {
    return;
  }

  if (NULL == (where = Sequence_find(seq, plugins->selected))) {
    return;
  }

  layer = (const Layer_t *)where->data;
  P = layer->plugin;
  if ((Plugin_t *)((const Layer_t *)list->data)->plugin == P)
    /* already at the beginning */
  {
    return;
  }

  prev = g_list_previous(where);
  list = g_list_remove(list, (gpointer)layer);
  seq->layers = g_list_insert_before(list, prev, (gpointer)layer);

  Sequence_changed(seq);
  Sequence_display(seq);
}


void
SequenceManager_move_selected_back(Sequence_t *seq)
{
  /* move selected plugin/layer down (towards the end of the sequence) */
  GList *list, *next;
  const GList *where;
  const Layer_t *layer;
  const Plugin_t *P = NULL;

  list = seq->layers;
  if (g_list_length(list) <= 1) {
    return;
  }

  if (NULL == (where = Sequence_find(seq, plugins->selected))) {
    return;
  }

  layer = (const Layer_t *)where->data;
  P = layer->plugin;
  if ((Plugin_t *)((const Layer_t *)g_list_last(list)->data) == P)
    /* already at the end */
  {
    return;
  }

  next = g_list_next(where);
  next = g_list_next(next);
  list = g_list_remove(list, (gpointer)layer);
  seq->layers = g_list_insert_before(list, next, (gpointer)layer);

  Sequence_changed(seq);
  Sequence_display(seq);
}


void
SequenceManager_toggle_lens(Sequence_t *seq)
{
  if (seq->lens == plugins->selected) {
    seq->lens = NULL;
  } else {
    seq->lens = plugins->selected;
  }

  Sequence_changed(seq);
}


void
SequenceManager_select_previous_plugin(Sequence_t *seq)
{
  if (g_list_length(seq->layers)) {
    const GList *hou = Sequence_find(seq, plugins->selected);
    const Plugin_t *P;

    if (NULL == hou) {
      /* Selected plugin not in sequence, selecting last layer */
      P = ((const Layer_t *)g_list_last(seq->layers)->data)->plugin;
    } else {
      hou = g_list_previous(hou);
      if (NULL == hou) {
        hou = g_list_last(seq->layers);
      }
      P = ((const Layer_t *)hou->data)->plugin;
    }
    Plugins_select(plugins, P);
  }
}


void
SequenceManager_select_next_plugin(Sequence_t *seq)
{
  if (g_list_length(seq->layers)) {
    const GList *hou = Sequence_find(seq, plugins->selected);
    const Plugin_t *P;

    if (NULL == hou) {
      /* Selected plugin not in sequence, selecting first layer */
      P = ((const Layer_t *)g_list_first(seq->layers)->data)->plugin;
    } else {
      hou = g_list_next(hou);
      if (NULL == hou) {
        hou = g_list_first(seq->layers);
      }
      P = ((const Layer_t*)hou->data)->plugin;
    }
    Plugins_select(plugins, P);
  }
}


void
SequenceManager_default_layer_mode(const Sequence_t *seq)
{
  const GList *ptr = Sequence_find(seq, plugins->selected);

  if (NULL != ptr) {
    Layer_t *layer = (Layer_t *)ptr->data;
    layer->mode = LM_NORMAL;
  }
}


void
SequenceManager_prev_layer_mode(const Sequence_t *seq)
{
  const GList *ptr = Sequence_find(seq, plugins->selected);

  if (NULL != ptr) {
    Layer_t *layer = (Layer_t *)ptr->data;
    if (layer->mode == LM_NONE) {
      layer->mode = LM_RANDOM;
    } else {
      --layer->mode;
    }
  }
}


void
SequenceManager_next_layer_mode(const Sequence_t *seq)
{
  const GList *ptr = Sequence_find(seq, plugins->selected);

  if (NULL != ptr) {
    Layer_t *layer = (Layer_t *)ptr->data;
    if (layer->mode == LM_RANDOM) {
      layer->mode = LM_NONE;
    } else {
      ++layer->mode;
    }
  }
}


void
SequenceManager_set_layer_mode(const Sequence_t *seq, const char *plugin, const enum LayerMode mode)
{
  const Plugin_t *p = Plugins_find(plugin);

  if (NULL != p) {
    const GList *ptr = Sequence_find(seq, p);

    if (NULL != ptr) {
      Layer_t *layer = (Layer_t *)ptr->data;
      layer->mode = mode;
    }
  }
}


int
SequenceManager_lock(SequenceManager_t *sm)
{
#ifdef DEBUG_SEQUENCEMANAGER
  xdebug("Locking SequenceManager\n");
#endif
  int ret = xpthread_mutex_lock(&sm->mutex);
#ifdef DEBUG_SEQUENCEMANAGER
  xdebug("Locked SequenceManager\n");
#endif

  return ret;
}


int
SequenceManager_unlock(SequenceManager_t *sm)
{
#ifdef DEBUG_SEQUENCEMANAGER
  xdebug("Unlocking SequenceManager\n");
#endif
  int ret = xpthread_mutex_unlock(&sm->mutex);
#ifdef DEBUG_SEQUENCEMANAGER
  xdebug("Unlocked SequenceManager\n");
#endif

  return ret;
}


void
SequenceManager_reorder(Sequence_t *seq, const json_t *new_seq)
{
  if (json_is_array(new_seq)) {
    GList *list = seq->layers;
    GList *new_list = NULL;
    size_t index;
    json_t *value;

    json_array_foreach(new_seq, index, value) {
      if (json_is_string(value)) {
        Plugin_t *p = Plugins_find(json_string_value(value));
        if (NULL != p) {
          GList *where = Sequence_find(seq, p);
          const Layer_t *layer = (const Layer_t *)where->data;

          new_list = g_list_append(new_list, (gpointer)layer);
        } else {
          g_list_free(new_list);

          return;
        }
      } else {
        g_list_free(new_list);

        return;
      }
    }
    g_list_free(list);
    seq->layers = new_list;
    Sequence_changed(seq);
    Sequence_display(seq);
  }
}
