/**
 *   \file common.c
 *   \brief Implémentation de la (dé)compression d’images
 */

#include "common.h"

/**
 *  Cette fonction permet d’évaluer si le pixel passé en argument est éligible à
 *  la zone passée également en argument.
 *
 *  \param[in] t_pixel Pointeur vers le pixel dont l’éligibilité est testée
 *  \param[in] t_zone Zone à laquelle le pixel est éligible ou non
 *  \return Valeur booléenne, `true` si le pixel est éligible, `false` sinon
 */
bool sameColor(Pixel* t_pixel, Zone* t_zone) {
  return t_pixel->r == t_zone->r && t_pixel->g == t_zone->g &&
         t_pixel->b == t_zone->b;
}

/**
 *  Ajoute un pixel à la zone passé en argument si le pixel à l’index passé en
 *  argument est éligible à la zone. Si un pixel n’a pas encore été visité, cela
 *  veut dire également qu’il ne fait partie d’aucun segment, il sera donc
 *  ajouté à un nouveau segment auquel seront rajoutés tous les pixels connexes
 *  éligibles à la zone. Ensuite, le segment est ajouté à la zone, et la
 *  fonction actuelle est appelée sur tous les pixels supérieurs et inférieurs
 *  aux pixels du segment.
 *
 *  \param[in] t_img Image contenant les pixels explorés
 *  \param[in] t_idx Index du pixel actuel dans l’image `t_img`
 *  \param[out] t_zone Zone à laquelle sera potentiellement ajouté le pixel
 */
void addPixelToSelectedZone(Image* t_img, int t_idx, Zone* t_zone) {
  Pixel *current_pixel = darrayGet(t_img->pixels, t_idx);
  int xd, xg, y = t_idx / (int)t_img->x;
  if (current_pixel->visited || t_idx >= (int)darraySize(t_img->pixels) ||
      t_idx < 0 || !sameColor(current_pixel, t_zone)) {
    return;
  }
  (*current_pixel).visited = true;
  for(xd = t_idx; xd % (int)t_img->x != 0; ++xd) { /* fetch right limit of segment */
    current_pixel = darrayGet(t_img->pixels, xd);
    if(current_pixel->visited || !sameColor(current_pixel, t_zone)) {
      break;
    }
    (*current_pixel).visited = true;
  }
  for(xg = t_idx; xg - y >= 0; --xg) { /* fetch right limit of segment */
    current_pixel = darrayGet(t_img->pixels, xd);
    if(current_pixel->visited || !sameColor(current_pixel, t_zone)) {
      break;
    }
    (*current_pixel).visited = true;
  }
  /* Add segment to its zone */
  darrayPushBack(t_zone->segments, newSegment(xd, xg));
  for(; xg <= xd; ++xg) { /* process every pixel up and down the segment */
    addPixelToSelectedZone(t_img, t_idx + t_img->x, t_zone);
  }
}

/* Selects the zone related to the pixel, skip tests if pixel has already been
 * visited */
void chooseZoneForPixel(Image* t_img, int t_idx, darray *zones) {
  Zone* current_zone;
  Pixel* pixel;
  size_t i;

  pixel = darrayGet(t_img->pixels, t_idx);
  /* if the pixel has already been visited, no need to continue */
  if (pixel->visited) {
    return;
  }
  /* for each known zone, see if it matches the current pixel's color */
  for (i = 0; i < darraySize(zones); ++i) {
    current_zone = darrayGet(zones, i);
    /* if it does, add selected pixel and its neighbourging pixels of the same
     * color */
    if (sameColor(pixel, current_zone)) {
      addPixelToSelectedZone(t_img, t_idx, current_zone);
      return;
    }
  }
  /* if none of the same color was found, create a new one, add it to the image
   * and add the selected pixel and its neighbours of the same color to the zone
   */
  current_zone = newZone(pixel->r, pixel->g, pixel->b);
  darrayPushBack(zones, current_zone);
  addPixelToSelectedZone(t_img, t_idx, current_zone);
}

/* converts an image to zones */
darray* imgToZones(Image* t_img) {
  darray *zones;
  const size_t nb_pixels = darraySize(t_img->pixels);
  size_t i;
  zones = darrayNew(sizeof(Zone));

  /* for each pixel, try to create a new zone */
  for (i = 0; i < nb_pixels; ++i) {
    chooseZoneForPixel(t_img, i, zones);
  }
  return zones;
}

void compress(const char *input_file) {
  Image *img;
  darray *zones;
  img = newImage();
  imageLoadPPM(input_file, img);
  zones = imgToZones(img);

  /* print segments for debug ************************************************/
  DEBUG {
    printf("Detected %zu zones\n", darraySize(zones));
    for (size_t i = 0; i < darraySize(zones); ++i) {
      Zone *zone = darrayGet(zones, i);
      printf("\n=== Zone %zu (%d %d %d) ===\n", i, zone->r, zone->g, zone->b);
      for (size_t j = 0; j < darraySize(zone->segments); ++j) {
        Segment *segm = darrayGet(zone->segments, j);
        printf("[%zu: %d, %d]\t", segm->xg / img->x, segm->xg, segm->xd);
      }
    }
    printf("\n");
  }

  deleteImage(img);
}
