// Implementación del módulo de imágenes. Ver image.h.
#include "image.h"

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

// Ningún dato válido de un PGM de este curso pasa de aquí.
#define NUMBER_LIMIT 100000

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

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;
}

struct image *image_new(int width, int height, int max_value, int value) {
    if (!valid_size(width, height, max_value) || value < 0 || value > max_value) {
        return NULL;
    }

    struct image *img = malloc(sizeof(*img));
    if (img == NULL) {
        return NULL;
    }

    int count = width * height;
    img->pixels = malloc(count * sizeof(int));
    if (img->pixels == NULL) {
        free(img);
        return NULL;
    }

    img->width = width;
    img->height = height;
    img->max_value = max_value;
    for (int i = 0; i < count; i++) {
        img->pixels[i] = value;
    }
    return img;
}

void image_free(struct image *img) {
    if (img != NULL) {
        free(img->pixels);
        free(img);
    }
}

// Devuelve el siguiente número del archivo, o -1 si lo que sigue no es un número.
// 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)) {
        value = value * 10 + (c - '0');
        if (value > NUMBER_LIMIT) {
            return -1;
        }
        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);
    struct image *img = image_new(width, height, max_value, 0);
    if (img == NULL) {
        return NULL;
    }

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

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');
    }
}
