/* SPDX-License-Identifier: Zlib */

#include "datastructures.h"

#include <stdlib.h>
#include <glib.h>

#include "log.h"

struct girara_list_s {
  void** start;                  /**> List start */
  size_t size;                   /**> The list size */
  girara_free_function_t free;   /**> The free function **/
  girara_compare_function_t cmp; /**> The sort function */
};

struct girara_list_iterator_s {
  girara_list_t* list; /**> The list */
  size_t index;        /**> The list index */
};

girara_list_t* girara_list_new(void) {
  return g_try_malloc0(sizeof(girara_list_t));
}

girara_list_t* girara_list_new_with_free(girara_free_function_t gfree) {
  girara_list_t* list = g_try_malloc0(sizeof(girara_list_t));
  if (list != NULL) {
    list->free = gfree;
  }
  return list;
}

girara_list_t* girara_sorted_list_new(girara_compare_function_t cmp) {
  girara_list_t* list = g_try_malloc0(sizeof(girara_list_t));
  if (list != NULL) {
    list->cmp = cmp;
  }
  return list;
}

girara_list_t* girara_sorted_list_new_with_free(girara_compare_function_t cmp, girara_free_function_t gfree) {
  girara_list_t* list = g_try_malloc0(sizeof(girara_list_t));
  if (list != NULL) {
    list->free = gfree;
    list->cmp  = cmp;
  }
  return list;
}

void girara_list_set_free_function(girara_list_t* list, girara_free_function_t gfree) {
  g_return_if_fail(list != NULL);
  list->free = gfree;
}

void girara_list_clear(girara_list_t* list) {
  if (list == NULL) {
    return;
  }

  if (list->free) {
    for (size_t idx = 0; idx != list->size; ++idx) {
      list->free(list->start[idx]);
    }
  }
  g_free(list->start);
  list->start = NULL;
  list->size  = 0;
}

void girara_list_free(girara_list_t* list) {
  if (list != NULL) {
    girara_list_clear(list);
    g_free(list);
  }
}

void girara_list_append(girara_list_t* list, void* data) {
  g_return_if_fail(list != NULL);

  void** new_start = g_realloc_n(list->start, list->size + 1, sizeof(void*));
  g_return_if_fail(new_start != NULL);

  list->start               = new_start;
  list->start[list->size++] = data;
  if (list->cmp != NULL) {
    girara_list_sort(list, list->cmp);
  }
}

void girara_list_prepend(girara_list_t* list, void* data) {
  g_return_if_fail(list != NULL);

  if (list->cmp != NULL) {
    girara_list_append(list, data);
  } else {
    list->start = g_realloc_n(list->start, list->size + 1, sizeof(void*));
    memmove(list->start + 1, list->start, list->size * sizeof(void*));
    list->start[0] = data;
    ++list->size;
  }
}

void girara_list_remove(girara_list_t* list, void* data) {
  g_return_if_fail(list != NULL);

  ssize_t pos = girara_list_position(list, data);
  if (pos == -1) {
    return;
  }

  if (list->free) {
    list->free(list->start[pos]);
  }
  memmove(list->start + pos, list->start + pos + 1, (list->size - pos - 1) * sizeof(void*));
  --list->size;
}

void* girara_list_nth(girara_list_t* list, size_t n) {
  g_return_val_if_fail(list != NULL, NULL);
  g_return_val_if_fail(n < list->size, NULL);

  return list->start[n];
}

void girara_list_set_nth(girara_list_t* list, size_t n, void* data) {
  g_return_if_fail(list != NULL);
  g_return_if_fail(n < list->size);
  g_return_if_fail(list->cmp == NULL);

  if (list->free != NULL) {
    list->free(list->start[n]);
  }

  list->start[n] = data;
}

bool girara_list_contains(girara_list_t* list, void* data) {
  g_return_val_if_fail(list != NULL, false);
  for (size_t idx = 0; idx != list->size; ++idx) {
    if (list->start[idx] == data) {
      return true;
    }
  }
  return false;
}

void* girara_list_find(const girara_list_t* list, girara_compare_function_t compare, const void* data) {
  g_return_val_if_fail(list != NULL && compare != NULL, NULL);

  for (size_t idx = 0; idx != list->size; ++idx) {
    if (compare(list->start[idx], data) == 0) {
      return list->start[idx];
    }
  }

  return NULL;
}

girara_list_iterator_t* girara_list_iterator(girara_list_t* list) {
  g_return_val_if_fail(list != NULL, NULL);

  if (list->size == 0) {
    return NULL;
  }

  girara_list_iterator_t* iter = g_try_malloc0(sizeof(girara_list_iterator_t));
  if (iter != NULL) {
    iter->list = list;
  }
  return iter;
}

girara_list_iterator_t* girara_list_iterator_copy(girara_list_iterator_t* iter) {
  g_return_val_if_fail(iter != NULL, NULL);

  return g_memdup2(iter, sizeof(girara_list_iterator_t));
}

girara_list_iterator_t* girara_list_iterator_next(girara_list_iterator_t* iter) {
  if (girara_list_iterator_is_valid(iter) == false) {
    return NULL;
  }

  ++iter->index;
  if (iter->index < iter->list->size) {
    return iter;
  }
  return NULL;
}

bool girara_list_iterator_has_next(girara_list_iterator_t* iter) {
  if (girara_list_iterator_is_valid(iter) == false) {
    return false;
  }

  return iter->index + 1 < iter->list->size;
}

girara_list_iterator_t* girara_list_iterator_previous(girara_list_iterator_t* iter) {
  if (girara_list_iterator_is_valid(iter) == false) {
    return NULL;
  }

  if (iter->index == 0) {
    iter->index = iter->list->size;
    return NULL;
  }
  --iter->index;
  return iter;
}

bool girara_list_iterator_has_previous(girara_list_iterator_t* iter) {
  if (girara_list_iterator_is_valid(iter) == false || iter->index == 0) {
    return false;
  }

  return true;
}

void girara_list_iterator_remove(girara_list_iterator_t* iter) {
  if (girara_list_iterator_is_valid(iter) == false) {
    return;
  }

  if (iter->list->free) {
    iter->list->free(iter->list->start[iter->index]);
  }
  memmove(iter->list->start + iter->index, iter->list->start + iter->index + 1,
          (iter->list->size - iter->index - 1) * sizeof(void*));
  --iter->list->size;
}

bool girara_list_iterator_is_valid(girara_list_iterator_t* iter) {
  return iter != NULL && iter->list != NULL && iter->index < iter->list->size;
}

void* girara_list_iterator_data(girara_list_iterator_t* iter) {
  g_return_val_if_fail(girara_list_iterator_is_valid(iter), NULL);
  return girara_list_nth(iter->list, iter->index);
}

void girara_list_iterator_set(girara_list_iterator_t* iter, void* data) {
  g_return_if_fail(girara_list_iterator_is_valid(iter));
  g_return_if_fail(iter->list->cmp == NULL);

  girara_list_set_nth(iter->list, iter->index, data);
}

void girara_list_iterator_free(girara_list_iterator_t* iter) {
  g_free(iter);
}

size_t girara_list_size(girara_list_t* list) {
  g_return_val_if_fail(list != NULL, 0);
  return list->size;
}

ssize_t girara_list_position(girara_list_t* list, void* data) {
  g_return_val_if_fail(list != NULL, -1);

  for (size_t idx = 0; idx != list->size; ++idx) {
    if (list->start[idx] == data) {
      return idx;
    }
  }
  return -1;
}

typedef struct {
  girara_compare_function_t compare;
} comparer_t;

static int comparer(const void* p1, const void* p2, void* data) {
  comparer_t* compare = data;
  return compare->compare(*(const void**)p1, *(const void**)p2);
}

void girara_list_sort(girara_list_t* list, girara_compare_function_t compare) {
  g_return_if_fail(list != NULL);
  if (list->start == NULL || compare == NULL) {
    return;
  }

  comparer_t comp = {compare};
#if GLIB_CHECK_VERSION(2, 81, 1)
  g_sort_array(list->start, list->size, sizeof(void*), comparer, &comp);
#else
  g_qsort_with_data(list->start, list->size, sizeof(void*), comparer, &comp);
#endif
}

void girara_list_foreach(girara_list_t* list, girara_list_callback_t callback, void* data) {
  g_return_if_fail(list != NULL && callback != NULL);
  if (list->start == NULL) {
    return;
  }

  for (size_t idx = 0; idx != list->size; ++idx) {
    callback(list->start[idx], data);
  }
}

static void list_append(void* data, void* userdata) {
  girara_list_t* list = userdata;
  girara_list_append(list, data);
}

girara_list_t* girara_list_merge(girara_list_t* list, girara_list_t* other) {
  g_return_val_if_fail(list != NULL, NULL);
  if (other == NULL) {
    return list;
  }

  if (list->free != other->free) {
    girara_warning("girara_list_merge: merging lists with different free functions!");
  }
  other->free = NULL;

  girara_list_foreach(other, list_append, list);
  return list;
}
