/*
 * 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>

#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)
{
	FILE *fp = fopen(file_name, "wb");
	if (!fp)
		abort_("[write_png_file] File %s could not be opened for writing", file_name);

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

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

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

	png_write_image(png_ptr, row_pointers);

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

	png_write_end(png_ptr, NULL);

	for (y=0; y<height; y++)
		free(row_pointers[y]);
	free(row_pointers);

        fclose(fp);
}

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

void get_pixel(int px, int py, int *r, int *g, int *b) {
    if (px >= 0 && px < width && py >= 0 && py < height) {
        png_byte* row = row_pointers[py];
        png_byte* ptr = &(row[px*3]);
        *r = ptr[0]; 
        *g = ptr[1]; 
        *b = ptr[2];
    } else {
        *r = -1; *g = -1; *b = -1;
    }
}

void draw_line(int x0, int y0, int x1, int y1, int r, int g, int 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_val, int r, int g, int b) {
    int x = 0, y = r_val;
    int d = 3 - 2 * r_val;
    
    while (y >= x) {
        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);
        x++;
        if (d > 0) {
            y--; d = d + 4 * (x - y) + 10;
        } else {
            d = d + 4 * x + 6;
        }
    }
}
typedef struct { int x, y; } Point;
Point stack[WIDTH * HEIGHT];
int stack_top = -1;

void flood_fill(int x, int y, int fill_r, int fill_g, int fill_b) {
    int target_r, target_g, target_b;
    get_pixel(x, y, &target_r, &target_g, &target_b);
    if (target_r == fill_r && target_g == fill_g && target_b == fill_b) return;
    
    stack[++stack_top] = (Point){x, y};
    
    while (stack_top >= 0) {
        Point p = stack[stack_top--];
        int px = p.x, py = p.y;
        
        int r, g, b;
        get_pixel(px, py, &r, &g, &b);
        
        if (r == target_r && g == target_g && b == target_b) {
            set_pixel(px, py, fill_r, fill_g, fill_b);
            
            if (px + 1 < width)  stack[++stack_top] = (Point){px + 1, py};
            if (px - 1 >= 0)     stack[++stack_top] = (Point){px - 1, py};
            if (py + 1 < height) stack[++stack_top] = (Point){px, py + 1};
            if (py - 1 >= 0)     stack[++stack_top] = (Point){px, py - 1};
        }
    }
}

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] = 255; 
            ptr[2] = 255;
        }
    }

    int K_points[12][2] = {
        {100, 150}, {150, 150}, {150, 270}, {230, 150}, {280, 150}, 
        {190, 300}, {280, 450}, {230, 450}, {150, 330}, {150, 450}, 
        {100, 450}, {100, 150}
    };
    for(int i=0; i<11; i++) {
        draw_line(K_points[i][0], K_points[i][1], K_points[i+1][0], K_points[i+1][1], 0, 0, 0);
    }

    int R_outer[11][2] = {
        {330, 150}, {430, 150}, {480, 200}, {480, 250}, {430, 300}, 
        {480, 450}, {420, 450}, {380, 320}, {380, 450}, {330, 450}, 
        {330, 150}
    };
    for(int i=0; i<10; i++) {
        draw_line(R_outer[i][0], R_outer[i][1], R_outer[i+1][0], R_outer[i+1][1], 0, 0, 0);
    }

    int R_inner[7][2] = {
        {380, 190}, {420, 190}, {440, 210}, {440, 240}, {420, 260}, 
        {380, 260}, {380, 190}
    };
    for(int i=0; i<6; i++) {
        draw_line(R_inner[i][0], R_inner[i][1], R_inner[i+1][0], R_inner[i+1][1], 0, 0, 0);
    }

    draw_circle(300, 300, 250, 0, 0, 0);

    flood_fill(125, 300, 150, 0, 200); 
    
    flood_fill(355, 400, 255, 165, 0); 
    
    flood_fill(300, 100, 150, 255, 150); 
    
    flood_fill(400, 225, 150, 255, 150); 

    flood_fill(80, 300, 150, 255, 150);
}


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

        return 0;
}
