/*
 * Copyright 2002-2008 Guillaume Cottenceau, 2015 Aleksander Denisiuk
 *
 * This software may be freely redistributed under the terms
 * of the X11 license.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <math.h>

#define PNG_DEBUG 3
#include <png.h>

#define OUT_FILE "initials.png"
#define WIDTH 600
#define HEIGHT 600
#define COLOR_TYPE PNG_COLOR_TYPE_RGB
#define BIT_DEPTH 8

void abort_(const char * s, ...)
{
    va_list args;
    va_start(args, s);
    vfprintf(stderr, s, args);
    fprintf(stderr, "\n");
    va_end(args);
    abort();
}

int x, y;
int width, height;
png_byte color_type;
png_byte bit_depth;

png_structp png_ptr;
png_infop info_ptr;
int number_of_passes;
png_bytep * row_pointers;

void create_png_file()
{
    width = WIDTH;
    height = HEIGHT;
    bit_depth = BIT_DEPTH;
    color_type = COLOR_TYPE;

    row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
    for (y=0; y<height; y++)
        row_pointers[y] = (png_byte*) malloc(width*bit_depth*3);
}

void write_png_file(char* file_name)
{
    /* create file */
    FILE *fp = fopen(file_name, "wb");
    if (!fp)
        abort_("[write_png_file] File %s could not be opened for writing", file_name);

    /* initialize stuff */
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    
    if (!png_ptr)
        abort_("[write_png_file] png_create_write_struct failed");

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
        abort_("[write_png_file] png_create_info_struct failed");

    if (setjmp(png_jmpbuf(png_ptr)))
        abort_("[write_png_file] Error during init_io");

    png_init_io(png_ptr, fp);

    /* write header */
    if (setjmp(png_jmpbuf(png_ptr)))
        abort_("[write_png_file] Error during writing header");

    png_set_IHDR(png_ptr, info_ptr, width, height,
             bit_depth, color_type, PNG_INTERLACE_NONE,
             PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_write_info(png_ptr, info_ptr);

    /* write bytes */
    if (setjmp(png_jmpbuf(png_ptr)))
        abort_("[write_png_file] Error during writing bytes");

    png_write_image(png_ptr, row_pointers);

    /* end write */
    if (setjmp(png_jmpbuf(png_ptr)))
        abort_("[write_png_file] Error during end of write");

    png_write_end(png_ptr, NULL);

    /* cleanup heap allocation */
    for (y=0; y<height; y++)
        free(row_pointers[y]);
    free(row_pointers);

    fclose(fp);
}

void set_pixel(int x, int y, png_byte r, png_byte g, png_byte b) {
    if (x >= 0 && x < width && y >= 0 && y < height) {
        png_byte* row = row_pointers[y];
        png_byte* ptr = &(row[x*3]);
        ptr[0] = r;
        ptr[1] = g;
        ptr[2] = b;
    }
}

void draw_line(int x0, int y0, int x1, int y1, png_byte r, png_byte g, png_byte b) {
    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2;

    for (;;) {
        set_pixel(x0, y0, r, g, b);
        if (x0 == x1 && y0 == y1) break;
        e2 = 2 * err;
        if (e2 >= dy) { err += dy; x0 += sx; }
        if (e2 <= dx) { err += dx; y0 += sy; }
    }
}

void draw_circle(int xc, int yc, int r_radius, png_byte r, png_byte g, png_byte b) {
    int x = 0;
    int y = r_radius;
    int d = 1 - r_radius;

    while (x <= y) {
        set_pixel(xc + x, yc + y, r, g, b);
        set_pixel(xc - x, yc + y, r, g, b);
        set_pixel(xc + x, yc - y, r, g, b);
        set_pixel(xc - x, yc - y, r, g, b);
        set_pixel(xc + y, yc + x, r, g, b);
        set_pixel(xc - y, yc + x, r, g, b);
        set_pixel(xc + y, yc - x, r, g, b);
        set_pixel(xc - y, yc - x, r, g, b);
        
        if (d < 0) {
            d = d + 2 * x + 3;
        } else {
            d = d + 2 * (x - y) + 5;
            y--;
        }
        x++;
    }
}

typedef struct { int x, y; } Point;
Point* stack;
int stack_size = 0;
int stack_capacity = WIDTH * HEIGHT;

void push(int x, int y) {
    if (stack_size < stack_capacity) {
        stack[stack_size++] = (Point){x, y};
    }
}

Point pop() {
    return stack[--stack_size];
}

int match_color(int x, int y, png_byte r, png_byte g, png_byte b) {
    if (x < 0 || x >= width || y < 0 || y >= height) return 0;
    png_byte* ptr = &(row_pointers[y][x*3]);
    return (ptr[0] == r && ptr[1] == g && ptr[2] == b);
}

void flood_fill(int x, int y, png_byte fill_r, png_byte fill_g, png_byte fill_b) {
    if (x < 0 || x >= width || y < 0 || y >= height) return;
    
    png_byte target_r = row_pointers[y][x*3];
    png_byte target_g = row_pointers[y][x*3 + 1];
    png_byte target_b = row_pointers[y][x*3 + 2];

    if (target_r == fill_r && target_g == fill_g && target_b == fill_b) return;

    stack = (Point*)malloc(sizeof(Point) * stack_capacity);
    stack_size = 0;
    push(x, y);

    while (stack_size > 0) {
        Point p = pop();
        int px = p.x;
        int py = p.y;

        if (match_color(px, py, target_r, target_g, target_b)) {
            set_pixel(px, py, fill_r, fill_g, fill_b);
            push(px + 1, py);
            push(px - 1, py);
            push(px, py + 1);
            push(px, py - 1);
        }
    }
    free(stack);
}

void process_file(void)
{
    for (y=0; y<height; y++) {
        png_byte* row = row_pointers[y];
        for (x=0; x<width; x++) {
            png_byte* ptr = &(row[x*3]);
            ptr[0] = 255;
            ptr[1] = 0;
            ptr[2] = 255;
        }
    }

    png_byte line_r = 0, line_g = 0, line_b = 0;

    int j_x[] = {150, 250, 250, 220, 220, 150, 150, 180, 180, 190, 190, 150};
    int j_y[] = {200, 200, 250, 250, 380, 380, 300, 300, 350, 350, 250, 250};
    int j_points = 12;
    for (int i = 0; i < j_points; i++) {
        draw_line(j_x[i], j_y[i], j_x[(i+1)%j_points], j_y[(i+1)%j_points], line_r, line_g, line_b);
    }

    int t_x[] = {350, 450, 450, 415, 415, 385, 385, 350};
    int t_y[] = {200, 200, 250, 250, 400, 400, 250, 250};
    int t_points = 8;
    for (int i = 0; i < t_points; i++) {
        draw_line(t_x[i], t_y[i], t_x[(i+1)%t_points], t_y[(i+1)%t_points], line_r, line_g, line_b);
    }

    draw_circle(300, 300, 220, line_r, line_g, line_b);


    flood_fill(300, 150, 0, 255, 0); 
    
    
    flood_fill(205, 300, 0, 255, 255); 
    flood_fill(400, 300, 0, 255, 255); 
}

int main(int argc, char **argv)
{
    create_png_file();
    process_file();
    write_png_file(OUT_FILE);

    return 0;
}