/**
 * \file darray.c
 * \brief Implémentation des fonctions pour le type \ref darray
 */

#include "darray.h"
#include <stdio.h>
#include <string.h>

/**
 * `darrayNew` permet de créer un nouvel objet de type \ref darray ne
 * contenant aucun élément. Le seul paramètre, `element_size`, est utilisé afin
 * de connaître l’espace mémoire à allouer à chacun des éléments dans le
 * tableau. Cela implique qu’un objet \ref darray ne peut contenir que des
 * éléments du même type.
 *
 * \param[in] t_element_size Taille des objets stockés
 * \return Pointeur sur le nouvel objet \ref darray
 */
darray *darrayNew(uint64_t t_element_size) {
  darray *ret;
  ret = (darray *)malloc(sizeof(darray));
  ret->begin = NULL;
  ret->end = ret->begin;
  ret->element_size = t_element_size;
  ret->capacity = 0;
  return ret;
}

/**
 * `darrayInsert` insère l’élément `elem` avant l'élément pointé par `pos` dans
 * l’objet \ref darray. Cela décalera tous les éléments stockés dans \a `self`
 * pen d’un cran vers la fin du tableau et insérera à l’endroit pointé le nouvel
 * élément. Cette fonction modifie les membres `begin` et `end` et
 * potentiellement `capacity` de `self`.
 *
 * \param[in] t_self Objet \ref darray dans lequel on souhaite insérer un nouvel
 * élément
 * \param[in] t_pos Position à laquelle on souhaite insérer un nouvel élément
 * \param[in] t_elem Élément que l’on souhaite insérer
 */
void darrayInsert(darray *t_self, void *t_pos, void *t_elem) {
  char *itr;
  int64_t pos_aux;
  pos_aux = (char *)t_pos - (char *)t_self->begin;
  if (darraySize(t_self) >= t_self->capacity) {
    darrayExtend(t_self);
  }
  itr = (char *)t_self->begin + pos_aux;
  memmove(itr + t_self->element_size, itr, ((char *)t_self->end - itr));
  memcpy(itr, t_elem, t_self->element_size);
  (*t_self).end = (char *)t_self->end + t_self->element_size;
}

/**
 *  Étend la capacité d'un \ref darray en réallouant sa mémoire, multipliant
 *  sa capacité par deux. Si la réallocation mémoire ne réussit pas, le
 *  programme s’arrête immédiatement, renvoyant la valeur de \ref PTR_ERROR
 *
 *  \param[in] t_self L'objet darray à étendre
 */
void darrayExtend(darray *t_self) {
  void *new_array;
  uint64_t size;
  size = darraySize(t_self);
  new_array =
      realloc(t_self->begin, (darraySize(t_self) + 1) * t_self->element_size);
  if (!new_array) {
    fprintf(stderr, "Failed memory reallocation at %s:%d\nAborting...",
            __FILE__, __LINE__ - 2);
    exit(PTR_ERROR);
  }
  (*t_self).begin = new_array;
  ++(*t_self).capacity;
  (*t_self).end = (char *)t_self->begin + size * t_self->element_size;
}

/**
 * `darrayErase` supprime l’élément de objet \ref darray `self` pointé par
 * `pos`. Cela décalera tous les éléments suivants dans le tableau d’un cran
 * vers le début du tableau de manière à ce qu’il n’y aie pas d’élément vide
 * entre les membres `begin` et `end` de `self`. Par ailleurs, le membre `end`
 * de `self` se retrouve modifié par la fonction.
 *
 * \param[out] t_self Objet \ref darray dont on souhaite supprimer un élément
 * \param[in] t_pos Élément de `self` que l’on souhaite supprimer
 */
void darrayErase(darray *t_self, void *t_pos) {
  memmove(t_pos, (char *)t_pos + t_self->element_size,
          (((char *)t_self->end - t_self->element_size) - (char *)t_pos));
  (*t_self).end = (char *)t_self->end - t_self->element_size;
}

/**
 * `darrayPushBack` ajoute un nouvel élément `elem` à l’objet `self` à la fin du
 * tableau de ce dernier. Cette fonction modifie le membre `end` de `self`.
 *
 * \param[out] t_self Objet \ref darray à la fin duquel on souhaite ajouter un
 * nouvel élément
 * \param[in] t_elem Élément que l’on souhaite ajouter à la fin de `self`
 */
void darrayPushBack(darray *t_self, void *t_elem) {
  darrayInsert(t_self, t_self->end, t_elem);
}

/**
 * `darrayPopBack` permet de supprimer le dernier élément de l’objet \ref
 * darray passé en argument. Cette fonction modifie le membre `end` de ce
 * dernier objet.
 *
 * \param[out] t_self Objet dont on souhaite supprimer le dernier élément
 */
void darrayPopBack(darray *t_self) {
  darrayErase(t_self, (char *)t_self->end - t_self->element_size);
}

/**
 * `darrayDelete` supprime tous les éléments contenus par l’objet \ref darray
 * passé en argument avant de libérer la mémoire occupée par l’objet lui-même.
 * L’objet passé en argument ne sera plus utilisable après utilisation de cette
 * fonction.
 *
 * \param[out] t_self Objet \ref darray à supprimer
 */
void darrayDelete(darray *t_self) {
  free(t_self->begin);
  free(t_self);
}

/**
 * `darraySize` renvoie le nombre d’éléments contenu dans le \ref darray
 * `self` passé en arguments. Cette fonction ne modifie pas l’élément passé en
 * argument.
 *
 * \param[out] t_self Objet \ref darray dont on souhaite connaître le nombre
 * d’éléments
 * \return Nombre d’éléments contenus dans `self`
 */
uint64_t darraySize(darray *t_self) {
  return (uint64_t)((char *)t_self->end - (char *)t_self->begin) /
         t_self->element_size;
}

/**
 * `darrayGet` permet de récupérer un élément d’un objet \ref darray grâce à
 * son index dans le tableau de l’objet `self`. Si l’index est trop grand, alors
 * le pointeur `NULL` sera renvoyé, sinon un pointeur de type `void*` pointant
 * sur l’élément correspondant sera renvoyé. Cette fonction ne modifie pas
 * l’objet `self`.
 * \param[out] t_self Objet \ref darray duquel on souhaite obtenir un pointeur
 * sur l’élément à l’index `idx`
 * \param[in] t_idx Index de l’élément que l’on souhaite récupérer
 * \return Pointeur de type `void*` pointant sur l’élément si l’index est
 * valide, sur NULL sinon.
 */
void *darrayGet(darray *t_self, uint64_t t_idx) {
  if (t_idx >= darraySize(t_self)) {
    fprintf(stderr, "Error in `darrayGet`, out of bound idx: %zu (max: %zu)\n",
            t_idx, darraySize(t_self));
    exit(PTR_ERROR);
  }
  void *itr;
  itr = (char *)t_self->begin + t_idx * t_self->element_size;
  return itr;
}
