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

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.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 write_pixel(int x, int y, png_byte cr, png_byte cg, png_byte cb) {
  png_byte *row = row_pointers[y];
  png_byte *ptr = &(row[x * 3]);
  ptr[0] = cr;
  ptr[1] = cg;
  ptr[2] = cb;
}

void read_pixel(int x, int y, png_byte *r, png_byte *g, png_byte *b) {
  png_byte *row = row_pointers[y];
  png_byte *ptr = &(row[x * 3]);
  *r = ptr[0];
  *g = ptr[1];
  *b = ptr[2];
}

void bresenham(int i1, int j1, int i2, int j2, png_byte cr, png_byte cg,
               png_byte cb) {
  int m, b, j, P;
  if (i2 > i1 && j2 >= j1 && j2 - j1 <= i2 - i1) {
    printf("Przypadek 1\n");
    m = 2 * (j2 - j1);
    b = 0;
    write_pixel(i1, j1, cr, cg, cb);
    j = j1;
    P = i2 - i1;
    for (int i = i1 + 1; i <= i2; i++) {
      b = b + m;
      if (b > P) {
        j = j + 1;
        b = b - 2 * P;
      }
      write_pixel(i, j, cr, cg, cb);
    }
  } else if (i2 > i1 && j2 <= j1 && j1 - j2 <= i2 - i1) {
    printf("Przypadek 2\n");
    m = 2 * (j1 - j2);
    b = 0;
    write_pixel(i1, j1, cr, cg, cb);
    j = j1;
    P = i2 - i1;
    for (int i = i1 + 1; i <= i2; i++) {
      b = b + m;
      if (b > P) {
        j = j - 1;
        b = b - 2 * P;
      }
      write_pixel(i, j, cr, cg, cb);
    }
  } else if (j2 > j1 && i2 >= i1 && i2 - i1 <= j2 - j1) {
    printf("Przypadek 3\n");
    m = 2 * (i2 - i1);
    b = 0;
    write_pixel(i1, j1, cr, cg, cb);
    j = i1;
    P = j2 - j1;
    for (int i = j1 + 1; i <= j2; i++) {
      b = b + m;
      if (b > P) {
        j = j + 1;
        b = b - 2 * P;
      }
      write_pixel(j, i, cr, cg, cb);
    }
  } else if (j2 < j1 && i2 >= i1 && i2 - i1 <= j1 - j2) {
    printf("Przypadek 4\n");
    m = 2 * (i2 - i1);
    b = 0;
    write_pixel(i1, j1, cr, cg, cb);
    j = i1;
    P = j1 - j2;
    for (int i = j1 - 1; i >= j2; i--) {
      b = b + m;
      if (b > P) {
        j = j + 1;
        b = b - 2 * P;
      }
      write_pixel(j, i, cr, cg, cb);
    }
  } else {
    printf("Nigdy tego nie zobacze.\n");
  }
}

void plot8(int cx, int cy, int i, int j, png_byte color_r, png_byte color_g,
           png_byte color_b) {
  write_pixel(cx + i, cy + j, color_r, color_g, color_b);
  write_pixel(cx - i, cy + j, color_r, color_g, color_b);
  write_pixel(cx + i, cy - j, color_r, color_g, color_b);
  write_pixel(cx - i, cy - j, color_r, color_g, color_b);
  write_pixel(cx + j, cy + i, color_r, color_g, color_b);
  write_pixel(cx - j, cy + i, color_r, color_g, color_b);
  write_pixel(cx + j, cy - i, color_r, color_g, color_b);
  write_pixel(cx - j, cy - i, color_r, color_g, color_b);
}

void circle(int cx, int cy, int radius, png_byte color_r, png_byte color_g,
            png_byte color_b) {
  int i = 0, j = radius, f = 5 - 4 * radius;

  plot8(cx, cy, i, j, color_r, color_g, color_b);

  while (i < j) {
    if (f > 0) {
      f = f + 8 * i - 8 * j + 20;
      j = j - 1;
    } else {
      f = f + 8 * i + 12;
    }
    i = i + 1;
    plot8(cx, cy, i, j, color_r, color_g, color_b);
  }
}

int is_interior(int x, int y, png_byte br, png_byte bg, png_byte bb,
                png_byte cr, png_byte cg, png_byte cb) {
  if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)
    return 0;
  png_byte r, g, b;
  read_pixel(x, y, &r, &g, &b);

  int is_border = (r == br && g == bg && b == bb);
  int is_filled = (r == cr && g == cg && b == cb);

  return !is_border && !is_filled;
}

void flood_fill(int i, int j, png_byte border_r, png_byte border_g,
                png_byte border_b, png_byte fill_r, png_byte fill_g,
                png_byte fill_b) {
  int capacity = WIDTH * HEIGHT;
  int *stack_i = malloc(capacity * sizeof(int));
  int *stack_j = malloc(capacity * sizeof(int));
  int top = 0;

  write_pixel(i, j, fill_r, fill_g, fill_b);
  stack_i[top] = i;
  stack_j[top] = j;
  top++;

  while (top > 0) {
    top--;
    i = stack_i[top];
    j = stack_j[top];

    if (is_interior(i - 1, j, border_r, border_g, border_b, fill_r, fill_g,
                    fill_b)) {
      write_pixel(i - 1, j, fill_r, fill_g, fill_b);
      stack_i[top] = i - 1;
      stack_j[top] = j;
      top++;
    }
    if (is_interior(i, j - 1, border_r, border_g, border_b, fill_r, fill_g,
                    fill_b)) {
      write_pixel(i, j - 1, fill_r, fill_g, fill_b);
      stack_i[top] = i;
      stack_j[top] = j - 1;
      top++;
    }
    if (is_interior(i + 1, j, border_r, border_g, border_b, fill_r, fill_g,
                    fill_b)) {
      write_pixel(i + 1, j, fill_r, fill_g, fill_b);
      stack_i[top] = i + 1;
      stack_j[top] = j;
      top++;
    }
    if (is_interior(i, j + 1, border_r, border_g, border_b, fill_r, fill_g,
                    fill_b)) {
      write_pixel(i, j + 1, fill_r, fill_g, fill_b);
      stack_i[top] = i;
      stack_j[top] = j + 1;
      top++;
    }
  }

  free(stack_i);
  free(stack_j);
}

void flood_fill_recursive(int i, int j, png_byte border_r, png_byte border_g,
                          png_byte border_b, png_byte fill_r, png_byte fill_g,
                          png_byte fill_b) {
  if (i < 0 || i >= WIDTH || j < 0 || j >= HEIGHT)
    return;

  png_byte r, g, b;
  read_pixel(i, j, &r, &g, &b);

  int is_border = (r == border_r && g == border_g && b == border_b);
  int is_filled = (r == fill_r && g == fill_g && b == fill_b);
  if (is_border || is_filled)
    return;

  write_pixel(i, j, fill_r, fill_g, fill_b);

  flood_fill_recursive(i - 1, j, border_r, border_g, border_b, fill_r, fill_g,
                       fill_b);
  flood_fill_recursive(i, j - 1, border_r, border_g, border_b, fill_r, fill_g,
                       fill_b);
  flood_fill_recursive(i + 1, j, border_r, border_g, border_b, fill_r, fill_g,
                       fill_b);
  flood_fill_recursive(i, j + 1, border_r, border_g, border_b, fill_r, fill_g,
                       fill_b);
}

void draw_LR(png_byte color_r, png_byte color_g, png_byte color_b) {
  // Ł
  printf("Begin L\n");
  bresenham(120, 120, 180, 120, color_r, color_g, color_b);
  bresenham(180, 120, 180, 240, color_r, color_g, color_b);
  bresenham(180, 240, 240, 180, color_r, color_g, color_b);
  bresenham(240, 180, 240, 252, color_r, color_g, color_b);
  bresenham(180, 312, 240, 252, color_r, color_g, color_b);
  bresenham(180, 312, 180, 402, color_r, color_g, color_b);
  bresenham(180, 402, 270, 402, color_r, color_g, color_b);
  bresenham(270, 402, 270, 462, color_r, color_g, color_b);
  bresenham(120, 462, 270, 462, color_r, color_g, color_b);
  bresenham(120, 462, 120, 354, color_r, color_g, color_b);
  bresenham(60, 414, 120, 354, color_r, color_g, color_b);
  bresenham(60, 414, 60, 342, color_r, color_g, color_b);
  bresenham(60, 342, 120, 282, color_r, color_g, color_b);
  bresenham(120, 282, 120, 120, color_r, color_g, color_b);

  // R
  printf("Begin R\n");
  bresenham(330, 132, 330, 468, color_r, color_g, color_b);
  bresenham(330, 468, 378, 468, color_r, color_g, color_b);
  bresenham(378, 468, 378, 318, color_r, color_g, color_b);
  bresenham(378, 318, 468, 468, color_r, color_g, color_b);
  bresenham(468, 468, 516, 468, color_r, color_g, color_b);

  bresenham(426, 294, 516, 468, color_r, color_g, color_b);

  // Brzuszek
  printf("Brzuszek R\n");
  bresenham(426, 294, 480, 240, color_r, color_g, color_b);
  bresenham(480, 168, 480, 240, color_r, color_g, color_b);
  bresenham(420, 132, 480, 168, color_r, color_g, color_b);
  bresenham(330, 132, 420, 132, color_r, color_g, color_b);

  // Wnętrze brzuszka
  printf("Oczko R\n");
  bresenham(366, 156, 402, 156, color_r, color_g, color_b);
  bresenham(402, 156, 438, 180, color_r, color_g, color_b);
  bresenham(438, 180, 438, 240, color_r, color_g, color_b);
  bresenham(402, 264, 438, 240, color_r, color_g, color_b);
  bresenham(366, 264, 402, 264, color_r, color_g, color_b);
  bresenham(366, 156, 366, 264, color_r, color_g, color_b);
}

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

  circle(WIDTH / 2, HEIGHT / 2, 280, 255, 255, 0);

  // Rzucało segfaultem
  // flood_fill_recursive(WIDTH / 2, HEIGHT / 2, 255, 255, 0, 255, 255, 0);
  flood_fill(WIDTH / 2, HEIGHT / 2, 255, 255, 0, 255, 255, 0);
  draw_LR(255, 0, 0);

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

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

  return 0;
}
