#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include <stdio.h>
#include <math.h>

#include <png.h>


void show_terrain(int width, int **terrain)
{
    for (int x = 0; x < width; x++)
        printf("-");
    printf("\n");
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < width; y++) {
            if (terrain[x][y] == 0)
                printf(" ");
            else if (terrain[x][y] == 1)
                printf("·");
            else if (terrain[x][y] == 2)
                printf("●");
            else if (terrain[x][y] == 3)
                printf("⬤");
            else
                printf("+");
        }
        printf("\n");
    }
    for (int x = 0; x < width; x++)
        printf("-");
    printf("\n");
}

int save_terrain(int width, int **terrain, char *filename)
{
    png_image image;
    png_byte row_pointers[width][width];

    memset(&image, 0, (sizeof image));
    image.version = PNG_IMAGE_VERSION;
    image.format = PNG_FORMAT_FLAG_COLORMAP|PNG_FORMAT_FLAG_COLOR;
    image.width = width;
    image.height = width;
    image.colormap_entries = 4;
    for (int x = 0; x < width; x++)
        for (int y = 0; y < width; y++)
            row_pointers[x][y] = terrain[x][y];
    if (png_image_write_to_file(&image, filename, 0,
                                row_pointers, 0,
                                "\xFF\xFF\xFF\xe9\xff\x70\x70\xd6\xff\xff\x70\xa6")){
        return 0;
    }
    fprintf(stderr, "pngtopng: error: %s\n", image.message);
    return 1;
}

#ifdef DEBUG
#define BOUND(a, low, high) (a < low ? low : (a > high ? high : a))
#define STORE_HEIGHT(x) by_height[BOUND(x, 0, 19)] += 1
#endif


void apply_gravity(int width, int **terrain)
{
    bool did_something;
    int div;
#ifdef DEBUG
    int loops = 1;
    uint64_t by_height[20] = {0};
#endif

    while (1) {
        did_something = false;
#ifdef DEBUG
        loops += 1;
#endif

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < width; y++) {
                if (terrain[x][y] >= 4) {
#ifdef DEBUG
                    STORE_HEIGHT(terrain[x][y]);
#endif
                    did_something = true;
                    div = terrain[x][y] / 4;
                    terrain[x][y] = terrain[x][y] % 4;
                    terrain[x - 1][y] += div;
                    terrain[x + 1][y] += div;
                    terrain[x][y + 1] += div;
                    terrain[x][y - 1] += div;
                }
            }
        }
        if (!did_something) {
#ifdef DEBUG
            fprintf(stderr, "looped %i times:\n", loops);
            for (int i = 0; i < 9; i++) {
                fprintf(stderr, "- %"PRIu64" times to break a pile of %i grains\n", by_height[i], i);
            }
            fprintf(stderr, "- %"PRIu64" times to break a pile of more than 13 grains\n", by_height[9]);
#endif
            return;
        }
    }
}


int main(int argc, char **argv)
{
    int width;
    int height;

    if (argc < 2) {
        fprintf(stderr, "Please provide height.\n");
        return EXIT_FAILURE;
    }

    if (sscanf(argv[1], "%i", &height) == EOF) {
        fprintf(stderr, "Can't read height as an integer.\n");
        return EXIT_FAILURE;
    }
    width = pow(height, 0.48);

    printf("Spawning a terrain of %ix%i\n", width, width);
    int **terrain = calloc(width, sizeof(int*));
    int *data = calloc(width * width, sizeof(int));
    for (int i = 0; i < width; i++) {
        terrain[i] = data + i * width;
    }

    terrain[width / 2][width / 2] = height;

    apply_gravity(width, terrain);
    if (argc > 2) {
        if (strcmp(argv[2], "/dev/null") != 0) {
            printf("Saving terrain to file...\n");
            if (save_terrain(width, terrain, argv[2]) == 0)
                printf("Terrain saved\n");
            else
                printf("Can't save terrain\n");
        }
    }
    else {
        show_terrain(width, terrain);
    }
    return EXIT_SUCCESS;
}
