#include "naive_ppm.h"
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE (1 << 24) // max size of 16Mib

int next_token(char* data, size_t size, size_t* start, size_t* end)
{
    // skip whitespace and comments
    while (isspace(data[*start]) || data[*start] == '#') {
        if (data[*start] == '#') {
            while (data[*start] != '\n' && data[*start] != '\r') {
                *start += 1;
                if (*start >= size) {
                    fprintf(stderr, "Error: unexpected end of file.\n");
                    exit(1);
                }
            }
        } else {
            *start += 1;
            if (*start >= size) {
                fprintf(stderr, "Error: unexpected end of file.\n");
                exit(1);
            }
        }
    }

    // get chars
    // NOTE: we don't allow comments inside tokens
    *end = *start;
    while (!isspace(data[*end])) {
        *end += 1;
    }

    return *end;
}

PPM* open_PPM(const char* filename)
{
    PPM* img = malloc(sizeof(PPM));
    img->width = 0;
    img->height = 0;

    FILE* f = fopen(filename, "rb"); // "b" because windows tries to open files in text mode!
    if (f == NULL) {
        fprintf(stderr, "Cannot open file '%s', aborting.\n", filename);
        exit(3);
    }
    char* data = malloc(MAX_SIZE);
    size_t size = fread(data, 1, MAX_SIZE, f);
    if (size == MAX_SIZE) {
        fprintf(stderr, "'naive_ppm' only accepts files smaller than %i bytes.\n", MAX_SIZE);
        exit(1);
    }

    // get header
    //
    // 1/ magic number
    size_t i, j;
    i = 0;
    next_token(data, size, &i, &j);
    if (j - i != 2 || data[i] != 'P' || data[i + 1] != '6') {
        fprintf(stderr, "naive_ppm can only read 'raw' ppm file ('P6')\n");
        exit(1);
    }

    // 2/ size
    i = j + 1;
    next_token(data, size, &i, &j);
    data[j] = '\0';
    img->width = (size_t)strtoumax(data + i, NULL, 10);

    i = j + 1;
    next_token(data, size, &i, &j);
    data[j] = '\0';
    img->height = (size_t)strtoumax(data + i, NULL, 10);

    // 3/ max intensity
    i = j + 1;
    next_token(data, size, &i, &j);
    data[j] = '\0';
    int m = (size_t)strtoumax(data + i, NULL, 10);
    if (m != 255) {
        fprintf(stderr,
            "'naive_ppm' can only read 'raw' ppm file with max intensity of 255 (got '%i')\n", m);
        exit(1);
    }

    size_t len = img->width * img->height;
    if (size - j - 1 != 3 * len) {
        fprintf(stderr, "expected %zu bytes for pixels data, got %zubytes instead\n", 3 * len,
            size - j - 1);
        exit(1);
    }

    img->P = malloc(len * sizeof(Pixel));
    for (size_t k = 0; k < len; k++) {
        img->P[k].R = data[j + 1 + 3 * k + 0];
        img->P[k].G = data[j + 1 + 3 * k + 1];
        img->P[k].B = data[j + 1 + 3 * k + 2];
    }

    free(data);
    fclose(f);
    return img;
}

int save_PPM(PPM* img, const char* filename)
{
    FILE* f = fopen(filename, "wb"); // "b" because windows tries to open files in text mode!
    fprintf(f, "P6\n%zu %zu\n255\n", img->width, img->height);
    for (size_t k = 0; k < img->width * img->height; k++) {
        fwrite(&img->P[k].R, 1, 1, f);
        fwrite(&img->P[k].G, 1, 1, f);
        fwrite(&img->P[k].B, 1, 1, f);
    }
    return fclose(f);
}

void free_PPM(PPM* img)
{
    free(img->P);
    free(img);
}

Pixel pixel_color(Pixel p, color_mode mode)
{
    Pixel q;
    if (mode.mode == NONE) {
        q.R = p.R;
        q.G = p.G;
        q.B = p.B;
    } else if (mode.mode == SOLID) {
        q.R = mode.color.R;
        q.G = mode.color.G;
        q.B = mode.color.B;
    } else if (mode.mode == LIGHT) {
        int intensity = (p.R + p.G + p.B) / 6;
        q.R = 128 + (intensity * mode.color.R) / 255;
        q.G = 128 + (intensity * mode.color.G) / 255;
        q.B = 128 + (intensity * mode.color.B) / 255;
    } else if (mode.mode == DARK) {
        int intensity = (p.R + p.G + p.B) / 6;
        q.R = (intensity * mode.color.R) / 255;
        q.G = (intensity * mode.color.G) / 255;
        q.B = (intensity * mode.color.B) / 255;
    } else {
        fprintf(stderr, "That's impossible, you shouldn't see this!\n");
        assert(0);
    }
    return q;
}

// vim: textwidth=100 foldmethod=indent
