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

#include <ctype.h>
#include <stdio.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 struct image image_invalid(void) {
    struct image img = {0};
    return img;
}

static int valid_size(int width, int height, int max_value) {
    return width >= 1 && width <= IMAGE_MAX_WIDTH
        && height >= 1 && height <= IMAGE_MAX_HEIGHT
        && max_value >= 1 && max_value <= IMAGE_MAX_VALUE;
}

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

    img.width = width;
    img.height = height;
    img.max_value = max_value;
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            img.pixels[y][x] = value;
        }
    }
    return img;
}

struct image image_from_values(int width, int height, int max_value, const int values[]) {
    struct image img = image_new(width, height, max_value, 0);
    if (!image_is_valid(img)) {
        return img;
    }

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int value = values[y * width + x];
            if (value < 0 || value > max_value) {
                return image_invalid();
            }
            img.pixels[y][x] = value;
        }
    }
    return img;
}

int image_is_valid(struct image img) {
    return img.width > 0;
}

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

    for (int y = 0; y < a.height; y++) {
        for (int x = 0; x < a.width; x++) {
            if (a.pixels[y][x] != b.pixels[y][x]) {
                return 0;
            }
        }
    }
    return 1;
}

// 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 image_invalid();
    }

    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 (!image_is_valid(img)) {
        return img;
    }

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int value = read_number(file);
            if (value < 0 || value > max_value) {
                return image_invalid();
            }
            img.pixels[y][x] = value;
        }
    }
    return img;
}

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

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

int image_save(struct image img, const char name[]) {
    if (!image_is_valid(img)) {
        return 0;
    }

    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][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(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][x]);
        }
        printf("\n");
    }
}

void image_show(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][x] * last_shade / img.max_value]);
        }
        putchar('\n');
    }
}
