// Implementación del módulo de imágenes. Ver image.h.
// Sesión 1: escriban image_allocate, image_new e image_free, y terminen read_image.
#include "image.h"

#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

// En cada línea del archivo: el formato pide no pasar de 70 caracteres.
#define VALUES_PER_LINE 16

// La división evita calcular width * height, que podría no caber en un int.
static int valid_size(int width, int height, int max_value) {
    return width >= 1 && height >= 1 && width <= INT_MAX / height
        && max_value >= 1 && max_value <= IMAGE_MAX_VALUE;
}

// Devuelve una imagen nueva con sus píxeles sin inicializar, o NULL si los datos no son
// válidos o si no hay memoria. Son dos bloques: el struct y los píxeles. Si el segundo
// malloc falla, no olviden liberar el primero.
// Si el segundo malloc falla, no olviden liberar el primero.
static struct image *image_allocate(int width, int height, int max_value) {
    if (!valid_size(width, height, max_value)) {
        return NULL;
    }

    // Escriban aquí su código.
    return NULL;
}

struct image *image_new(int width, int height, int max_value, int value) {
    (void) width;      // borren estas cuatro líneas cuando usen los parámetros
    (void) height;
    (void) max_value;
    (void) value;
    // Escriban aquí su código. Usen image_allocate.
    return NULL;
}

// Ya está hecha, y sirve de modelo: si un valor está mal, libera lo que reservó.
struct image *image_from_values(int width, int height, int max_value, const int *values) {
    struct image *img = image_allocate(width, height, max_value);
    if (img == NULL) {
        return NULL;
    }

    int total = width * height;
    for (int i = 0; i < total; i++) {
        if (values[i] < 0 || values[i] > max_value) {
            image_free(img);
            return NULL;
        }
        img->pixels[i] = values[i];
    }
    return img;
}

void image_free(struct image *img) {
    (void) img;  // borren esta línea cuando usen img
    // Escriban aquí su código.
}

int image_equal(const struct image *a, const struct image *b) {
    if (a->width != b->width || a->height != b->height || a->max_value != b->max_value) {
        return 0;
    }

    int total = a->width * a->height;
    for (int i = 0; i < total; i++) {
        if (a->pixels[i] != b->pixels[i]) {
            return 0;
        }
    }
    return 1;
}

// Devuelve el siguiente número del archivo, o -1 si lo que sigue no es un número o no
// cabe en un int. Se salta los espacios y los comentarios, que van de # hasta el fin de
// la línea.
static int read_number(FILE *file) {
    int c = fgetc(file);
    while (c == '#' || isspace(c)) {
        if (c == '#') {
            while (c != '\n' && c != EOF) {
                c = fgetc(file);
            }
        } else {
            c = fgetc(file);
        }
    }

    if (!isdigit(c)) {
        return -1;
    }

    int value = 0;
    while (isdigit(c)) {
        int digit = c - '0';
        if (value > (INT_MAX - digit) / 10) {
            return -1;
        }
        value = value * 10 + digit;
        c = fgetc(file);
    }
    // Lo que no era parte del número le toca a la siguiente lectura.
    if (c != EOF) {
        ungetc(c, file);
    }
    return value;
}

// Lee la imagen de un archivo ya abierto. Lo que venga después del último píxel se ignora.
static struct image *read_image(FILE *file) {
    if (fgetc(file) != 'P' || fgetc(file) != '2') {
        return NULL;
    }

    int width = read_number(file);
    int height = read_number(file);
    int max_value = read_number(file);

    (void) width;      // borren estas tres líneas cuando usen las variables
    (void) height;
    (void) max_value;
    // Escriban aquí su código. Reserven con image_allocate y lean píxel por píxel, con
    // read_number. Un píxel fuera de rango, o que falta, hace fallar toda la lectura.
    return NULL;
}

struct image *image_load(const char *name) {
    FILE *file = fopen(name, "r");
    if (file == NULL) {
        return NULL;
    }

    struct image *img = read_image(file);
    fclose(file);
    return img;
}

int image_save(const struct image *img, const char *name) {
    FILE *file = fopen(name, "w");
    if (file == NULL) {
        return 0;
    }

    int written = fprintf(file, "P2\n%d %d\n%d\n", img->width, img->height, img->max_value);
    for (int y = 0; y < img->height && written >= 0; y++) {
        for (int x = 0; x < img->width && written >= 0; x++) {
            int last_in_line = x == img->width - 1 || x % VALUES_PER_LINE == VALUES_PER_LINE - 1;
            written = fprintf(file, "%d%c", img->pixels[y * img->width + x],
                              last_in_line ? '\n' : ' ');
        }
    }

    // Al cerrar se escribe lo que quedaba pendiente, y eso también puede fallar.
    if (fclose(file) != 0 || written < 0) {
        return 0;
    }
    return 1;
}

void image_print(const struct image *img) {
    printf("%d x %d, max %d\n", img->width, img->height, img->max_value);
    for (int y = 0; y < img->height; y++) {
        for (int x = 0; x < img->width; x++) {
            printf("%4d", img->pixels[y * img->width + x]);
        }
        printf("\n");
    }
}

void image_show(const struct image *img) {
    const char shades[] = " .:-=+*#%@";
    int last_shade = (int) sizeof(shades) - 2;

    for (int y = 0; y < img->height; y++) {
        for (int x = 0; x < img->width; x++) {
            putchar(shades[img->pixels[y * img->width + x] * last_shade / img->max_value]);
        }
        putchar('\n');
    }
}
