/**
 *   \file ppm.c
 *   \brief Fichier de déclaration des fonctions de manipulation d'images ppm
 *
 *  Déclaration du corps des fonctions déclarées dans \ref ppm.h
 */

#include "ppm.h"
#include <assert.h>

#define RGB_COMPONENT_COLOR 255
#define CREATOR "CL"

/**
 *  \brief function description
 *
 *  Fonction d’ouverture de fichier selon le mode demandé. Si la fonction ne
 *  peut pas ouvrir le fichier, elle arrête le processus qui renverra la valeur
 *  `1`. En cas de succès, la fonction renverra un pointeur de fichier vers le
 *  fichier ouvert.
 *
 *  \param[in] filename Nom du fichier à ouvrir
 *  \param[in] mode Mode du fichier à ouvrir
 *  \return Pointeur de fichier
 */
FILE* get_file(const char *t_filename, const char* t_mode) {
  FILE* fp = fopen(t_filename, t_mode);
  if (!fp) {
    fprintf(stderr, "Unable to open file '%s'\n", t_filename);
    exit(FILE_IO_ERROR);
  }
  return fp;
}

/**
 *  Lit et vérifie le format du fichier passé en argument. Si le format n’est
 *  pas correct, la fonction arrête le processus qui renverra la valeur `1`.
 *
 *  \param[in] fb Fichier ppm où lire les données
 *  \param[in] filename Nom du fichier ouvert
 */
void read_file_format(FILE* t_fp, const char* t_filename) {
  char buff[16];
  if (!fgets(buff, sizeof(buff), t_fp)) {
    perror(t_filename);
    exit(FILE_IO_ERROR);
  }
  /* check file format */
  if (buff[0] != 'P' || buff[1] != '6') {
    fprintf(stderr, "Invalid image format (must be 'P6')\n");
    exit(FILE_FORMAT_ERROR);
  }
}

/**
 *  Vérifie si le header contient des commentaires et les ignore le cas échéant.
 *
 *  \param[in] fp Fichier ppm où lire les données
 */
void check_for_comments(FILE* t_fp) {
  char c;
  c = (char)getc(t_fp);
  while (c == '#') {
    while (getc(t_fp) != '\n')
      ;
    c = (char)getc(t_fp);
  }
  ungetc(c, t_fp);
}

/**
 *  Lit la taille des données image et les écrit dans le conteneur d’images
 *  passé en argument.
 *
 *  \param[in] fp Fichier ppm où lire les données
 *  \param[out] img Conteneur d’image où écrire les résultats
 *  \param[in] filename Nom du fichier ouvert
 */
void read_file_size(FILE* t_fp, Image* t_img, const char* t_filename) {
  if (fscanf(t_fp, "%lu %lu", &t_img->x, &t_img->y) != 2) {
    fprintf(stderr, "Invalid image size (error loading '%s')\n", t_filename);
    exit(FILE_FORMAT_ERROR);
  }
}

/**
 *  Vérifie le format RGB de l’image ppm. Si le format n’est pas correct, la
 *  fonction arrête le processus qui renverra la valeur `1`.
 *
 *  \param[in] fp Fichier ppm où lire les données
 *  \param[in] filename Nom du fichier ouvert
 */
void read_rgb(FILE* t_fp, const char* t_filename) {
  char d;
  int rgb_comp_color;
  /* read rgb component */
  if (fscanf(t_fp, "%d", &rgb_comp_color) != 1) {
    fprintf(stderr, "Invalid rgb component (error loading '%s')\n", t_filename);
    exit(FILE_FORMAT_ERROR);
  }
  fscanf(t_fp, "%c ", &d);
  /* check rgb component depth */
  if (rgb_comp_color != RGB_COMPONENT_COLOR) {
    fprintf(stderr, "'%s' does not have 8-bits components\n", t_filename);
    exit(FILE_FORMAT_ERROR);
  }
}

unsigned long read_data(FILE *t_fp, Image *t_img, unsigned char **t_data,
                        const char *t_filename) {
  unsigned long size;
  /* allocation memoire */
  size = t_img->x * t_img->y * 3;
  DEBUG {
    printf("Size image %lu %lu => %lu\n", t_img->x, t_img->y, t_img->x * t_img->y);
  }
  *t_data = (unsigned char *)malloc((size_t)size * sizeof(unsigned char));
  assert(*t_data);
  /* read pixel data from file */
  if (!fread(*t_data, (size_t)1, (size_t)size, t_fp)) {
    fprintf(stderr, "Error loading image '%s'\n", t_filename);
    free(t_data);
    exit(FILE_IO_ERROR);
  }
  return size;
}

/**
 *  Convertit vers un tableau de `unsigned char` les pixels contenus dans un conteneur
 *  d’image. La taille du tableau de `unsigned char` est la taille du tableau de
 *  pixels multipliée par trois du fait des trois emplacements séparés par
 *  couleur.
 *
 *  \param[out] img Image dont les pixels doivent être convertis
 *  \param[in] data Données à convertir en structures \ref Pixel
 *  \param[in] size Taille du tableau de `unsigned char`
 *  \return return type
 */
void dataToImage(Image *t_img, unsigned char *t_data, unsigned long t_size) {
  unsigned long i;
  t_img->pixels = darrayNew(sizeof(Pixel));
  printf("Size of data: %lu\n", t_size);
  for (i = 0; i < t_size; i += 3) {
    darrayPushBack(t_img->pixels,
                   newPixel(t_data[i], t_data[i + 1], t_data[i + 2]));
  }
}

/**
 *  Convertit le vecteur de pixels d’un conteneur d’image en un tableau de
 *  valeurs de type `GLuint` afin de permettre l’écriture d’une image dans un
 *  fichier.
 *
 *  \param[in] img Conteneur d’image contenant les pixels à convertir
 *  \return Tableau de pointeurs de `GLuint`
 */
unsigned char *imageToData(Image* t_img) {
  Pixel* pixel;
  unsigned char *data, size;
  unsigned long i;
  size = (unsigned char)darraySize(t_img->pixels);
  data = (unsigned char *)malloc(3 * sizeof(unsigned char) * size);
  for(i = 0; i < size; i += 3) {
    pixel = darrayGet(t_img->pixels, i / 3);
    data[i] = pixel->r;
    data[i + 1] = pixel->g;
    data[i + 2] = pixel->b;
  }
  return data;
}

/**
 *  Ouvre le fichier image avec son nom de fichier passé par le paramètre
 *  `filename` et charge ses informations et données dans l'objet `img` dans
 *  lequel les données et l'image seront manipulables. Retourne la valeur 1 en
 *  cas de succès.
 *
 *  \param[in] filename Nom du fichier image à ouvrir
 *  \param[out] img Objet \ref Image manipulable
 *  \return Retourne 1 en cas de succès
 */
int imageLoadPPM(const char *t_filename, Image *t_img) {
  FILE *fp;
  unsigned long size;
  unsigned char *data = NULL;
  fp = get_file(t_filename, "rb");       /* open PPM file for reading */
  read_file_format(fp, t_filename);      /* read image format */
  check_for_comments(fp);                /* check for comments */
  read_file_size(fp, t_img, t_filename); /* read image size information */
  read_rgb(fp, t_filename);              /* read rgb component */
  size = read_data(fp, t_img, &data, t_filename); /* read data from file */
  dataToImage(t_img, data, size);
  fclose(fp);
  return 1;
}

/**
() *  Ouvre le fichier image avec son nom de fichier passé par le paramètre
 *  `filename` et y écrit les informations trouvées dans l’objet `img`.
 *
 *  \param[in] filename Nom du fichier image à ouvrir
 *  \param[in] img Objet \ref Image à écrire
 */
void imageSavePPM(char *t_filename, Image* t_img) {
  FILE *fp;
  unsigned char *data;
  fp = get_file(t_filename, "wb"); /* open file for output */
  /* write the header file */
  fprintf(fp, "P6\n");                          /* image format */
  fprintf(fp, "# Created by %s\n", CREATOR);    /* comments */
  fprintf(fp, "%lu %lu\n", t_img->y, t_img->y); /* image size */
  fprintf(fp, "%d\n", RGB_COMPONENT_COLOR);     /* rgb component depth */
  data = imageToData(t_img);                    /* pixel data */
  fwrite(data, (size_t)1, (size_t)(3 * t_img->x * t_img->y), fp);
  free(data);
  fclose(fp);
}
