/*
 * 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 * 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 write_pixel(int x, int y, png_byte r, png_byte g, png_byte b)
{
	if (x < 0 || x >= width || y < 0 || y >= height)
		return;

	png_byte* row = row_pointers[y];
	png_byte* ptr = &(row[x * 3]);

	ptr[0] = r;
	ptr[1] = g;
	ptr[2] = b;
}

int same_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 get_color(int x, int y, png_byte *r, png_byte *g, png_byte *b)
{
	png_byte* ptr = &(row_pointers[y][x * 3]);

	*r = ptr[0];
	*g = ptr[1];
	*b = ptr[2];
}

void line(int x0, int y0, int x1, int y1, png_byte r, png_byte g, png_byte b)
{
	int dx = abs(x1 - x0);
	int dy = abs(y1 - y0);

	int sx = x0 < x1 ? 1 : -1;
	int sy = y0 < y1 ? 1 : -1;

	int err = dx - dy;

	while (1) {
		write_pixel(x0, y0, r, g, b);

		if (x0 == x1 && y0 == y1)
			break;

		int e2 = 2 * err;

		if (e2 > -dy) {
			err -= dy;
			x0 += sx;
		}

		if (e2 < dx) {
			err += dx;
			y0 += sy;
		}
	}
}

void circle(int x0, int y0, int radius, png_byte r, png_byte g, png_byte b)
{
	int x = 0;
	int y = radius;
	int d = 3 - 2 * radius;

	while (x <= y) {
		write_pixel(x0 + x, y0 + y, r, g, b);
		write_pixel(x0 - x, y0 + y, r, g, b);
		write_pixel(x0 + x, y0 - y, r, g, b);
		write_pixel(x0 - x, y0 - y, r, g, b);

		write_pixel(x0 + y, y0 + x, r, g, b);
		write_pixel(x0 - y, y0 + x, r, g, b);
		write_pixel(x0 + y, y0 - x, r, g, b);
		write_pixel(x0 - y, y0 - x, r, g, b);

		if (d < 0) {
			d += 4 * x + 6;
		} else {
			d += 4 * (x - y) + 10;
			y--;
		}

		x++;
	}
}

void flood_fill(int x, int y,
		png_byte new_r, png_byte new_g, png_byte new_b)
{
	png_byte old_r, old_g, old_b;
	get_color(x, y, &old_r, &old_g, &old_b);

	if (old_r == new_r && old_g == new_g && old_b == new_b)
		return;

	int max = width * height;
	int *stack_x = malloc(sizeof(int) * max);
	int *stack_y = malloc(sizeof(int) * max);
	int top = 0;

	stack_x[top] = x;
	stack_y[top] = y;
	top++;

	while (top > 0) {
		top--;

		int cx = stack_x[top];
		int cy = stack_y[top];

		if (cx < 0 || cx >= width || cy < 0 || cy >= height)
			continue;

		if (!same_color(cx, cy, old_r, old_g, old_b))
			continue;

		write_pixel(cx, cy, new_r, new_g, new_b);

		if (top + 4 < max) {
			stack_x[top] = cx + 1;
			stack_y[top] = cy;
			top++;

			stack_x[top] = cx - 1;
			stack_y[top] = cy;
			top++;

			stack_x[top] = cx;
			stack_y[top] = cy + 1;
			top++;

			stack_x[top] = cx;
			stack_y[top] = cy - 1;
			top++;
		}
	}

	free(stack_x);
	free(stack_y);
}

void draw_O(void)
{
	
	line(130, 180, 230, 180, 0, 0, 0);
	line(230, 180, 270, 220, 0, 0, 0);
	line(270, 220, 270, 380, 0, 0, 0);
	line(270, 380, 230, 420, 0, 0, 0);
	line(230, 420, 130, 420, 0, 0, 0);
	line(130, 420, 90, 380, 0, 0, 0);
	line(90, 380, 90, 220, 0, 0, 0);
	line(90, 220, 130, 180, 0, 0, 0);

	
	line(150, 230, 210, 230, 0, 0, 0);
	line(210, 230, 225, 245, 0, 0, 0);
	line(225, 245, 225, 355, 0, 0, 0);
	line(225, 355, 210, 370, 0, 0, 0);
	line(210, 370, 150, 370, 0, 0, 0);
	line(150, 370, 135, 355, 0, 0, 0);
	line(135, 355, 135, 245, 0, 0, 0);
	line(135, 245, 150, 230, 0, 0, 0);
}

void draw_K(void)
{
	line(330, 180, 380, 180, 0, 0, 0);
	line(380, 180, 380, 260, 0, 0, 0);
	line(380, 260, 455, 180, 0, 0, 0);
	line(455, 180, 500, 180, 0, 0, 0);
	line(500, 180, 405, 300, 0, 0, 0);

	line(405, 300, 500, 420, 0, 0, 0);
	line(500, 420, 455, 420, 0, 0, 0);
	line(455, 420, 380, 340, 0, 0, 0);
	line(380, 340, 380, 420, 0, 0, 0);
	line(380, 420, 330, 420, 0, 0, 0);
	line(330, 420, 330, 180, 0, 0, 0);

	line(380, 260, 405, 300, 0, 0, 0);
	line(405, 300, 380, 340, 0, 0, 0);
}



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

	circle(300, 300, 250, 25, 25, 112);

	draw_O();
	draw_K();

	flood_fill(300, 300, 255, 245, 180);    
	flood_fill(180, 300, 255, 105, 180);   
	flood_fill(110, 300, 190, 220, 255);   

	flood_fill(350, 250, 255, 165, 0);     
	flood_fill(440, 230, 152, 251, 152);    
	flood_fill(440, 370, 221, 160, 221);   
}

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

	return 0;
}