#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#include <errno.h>
#include "bitmapimage/BitmapImage.h"
#include "pngimageio/PNGImageIO.h"

static void printUsage() {
	fprintf(stderr, "Usage: circlegen [-w width] [-h height] [-t thickness] [-s samples_per_pixel] [-k softness] [-rgba] [-o out_file]\n");
}

int main(int argc, char ** argv) {
	bool widthDefined = false, heightDefined = false;
	unsigned int width = 32, height = 32, thickness = 0, samplesPerPixel = 1;
	double softenedWidth, softenedHeight;
	float softness = 1.0f;
	bool rgba = false;
	int argIndex;
	const char * outFilePath = "a.png";
	unsigned int sample;
	unsigned char * pixels;
	BitmapImage * image;
	unsigned int pixelIndexX, pixelIndexY;
	double fsqrtSamplesPerPixel;
	int isqrtSamplesPerPixel;
	double subpixelX, subpixelY, subpixelXLocal, subpixelYLocal;
	double distance;
	
	for (argIndex = 1; argIndex < argc; argIndex++) {
		if (!strcmp(argv[argIndex], "-w")) {
			if (argIndex >= argc - 1 || !sscanf(argv[++argIndex], "%u", &width)) {
				fprintf(stderr, "Error: Couldn't parse argument following -w as width\n");
				printUsage();
				return EXIT_FAILURE;
			}
			widthDefined = true;
			
		} else if (!strcmp(argv[argIndex], "-h")) {
			if (argIndex >= argc - 1 || !sscanf(argv[++argIndex], "%u", &height)) {
				fprintf(stderr, "Error: Couldn't parse argument following -h as height\n");
				printUsage();
				return EXIT_FAILURE;
			}
			heightDefined = true;
			
		} else if (!strcmp(argv[argIndex], "-t")) {
			if (argIndex >= argc - 1 || !sscanf(argv[++argIndex], "%u", &thickness)) {
				fprintf(stderr, "Error: Couldn't parse argument following -t as thickness\n");
				printUsage();
				return EXIT_FAILURE;
			}
			
		} else if (!strcmp(argv[argIndex], "-s")) {
			if (argIndex >= argc - 1 || !sscanf(argv[++argIndex], "%u", &samplesPerPixel)) {
				fprintf(stderr, "Error: Couldn't parse argument following -s as samples per pixel\n");
				printUsage();
				return EXIT_FAILURE;
			}
			
		} else if (!strcmp(argv[argIndex], "-k")) {
			if (argIndex >= argc - 1 || !sscanf(argv[++argIndex], "%f", &softness)) {
				fprintf(stderr, "Error: Couldn't parse argument following -k as softness\n");
				printUsage();
				return EXIT_FAILURE;
			}
			
		} else if (!strcmp(argv[argIndex], "-o")) {
			if (argIndex >= argc - 1) {
				fprintf(stderr, "Error: No output file path provided after -o argument\n");
				printUsage();
				return EXIT_FAILURE;
			}
			outFilePath = argv[++argIndex];
			
		} else if (!strcmp(argv[argIndex], "-rgba")) {
			rgba = true;
			
		} else if (!strcmp(argv[argIndex], "-h") || !strcmp(argv[argIndex], "--help")) {
			printUsage();
			return EXIT_SUCCESS;
		}
	}
	
	fsqrtSamplesPerPixel = sqrt(samplesPerPixel);
	isqrtSamplesPerPixel = fsqrtSamplesPerPixel;
	if (samplesPerPixel == 0) {
		fprintf(stderr, "Error: Samples per pixel must be greater than zero!\n");
		return EXIT_FAILURE;
	}
	if (fsqrtSamplesPerPixel != isqrtSamplesPerPixel) {
		fprintf(stderr, "Error: Samples per pixel must be square (%u is not)\n", samplesPerPixel);
		return EXIT_FAILURE;
	}
	if (samplesPerPixel > 256) {
		fprintf(stderr, "Warning: Samples per pixel beyond 256 won't make a difference in output\n");
	}
	
	if (widthDefined && !heightDefined) {
		height = width;
	} else if (heightDefined && !widthDefined) {
		width = height;
	}
	
	softenedWidth = width + (1 - softness);
	softenedHeight = height + (1 - softness);
	pixels = malloc(width * height * (rgba ? 4 : 1));
	for (pixelIndexY = 0; pixelIndexY < height; pixelIndexY++) {
		for (pixelIndexX = 0; pixelIndexX < width; pixelIndexX++) {
			if (samplesPerPixel == 1) {
				subpixelX = (pixelIndexX + 0.5) / width * 2.0 - 1.0;
				subpixelY = (pixelIndexY + 0.5) / height * 2.0 - 1.0;
				distance = subpixelX * subpixelX + subpixelY * subpixelY;
				sample = (distance <= 1.0);
				if (thickness > 0) {
					subpixelX = (pixelIndexX + 0.5 - thickness) / (width - thickness - thickness) * 2.0 - 1.0;
					subpixelY = (pixelIndexY + 0.5 - thickness) / (height - thickness - thickness) * 2.0 - 1.0;
					distance = subpixelX * subpixelX + subpixelY * subpixelY;
					if (distance < 1.0) {
						sample = 0;
					}
				}
			} else {
				bool isSolid;
				
				sample = 0;
				for (int subpixelIndexY = 0; subpixelIndexY < isqrtSamplesPerPixel; subpixelIndexY++) {
					for (int subpixelIndexX = 0; subpixelIndexX < isqrtSamplesPerPixel; subpixelIndexX++) {
						subpixelXLocal = (((subpixelIndexX + 0.5f) - isqrtSamplesPerPixel / 2) * softness + isqrtSamplesPerPixel / 2) / fsqrtSamplesPerPixel;
						subpixelYLocal = (((subpixelIndexY + 0.5f) - isqrtSamplesPerPixel / 2) * softness + isqrtSamplesPerPixel / 2) / fsqrtSamplesPerPixel;
						subpixelX = (pixelIndexX + subpixelXLocal) / width * 2.0 - 1.0;
						subpixelY = (pixelIndexY + subpixelYLocal) / height * 2.0 - 1.0;
						distance = subpixelX * subpixelX + subpixelY * subpixelY;
						isSolid = (distance <= 1.0);
						if (thickness > 0) {
							subpixelX = ((pixelIndexX + subpixelXLocal) - width * 0.5) / ((softenedWidth - thickness - thickness) * 0.5);
							subpixelY = ((pixelIndexY + subpixelYLocal) - height * 0.5) / ((softenedHeight - thickness - thickness) * 0.5);
							distance = subpixelX * subpixelX + subpixelY * subpixelY;
							if (distance < 1.0) {
								isSolid = false;
							}
						}
						sample += isSolid;
					}
				}
			}
			if (rgba) {
				pixels[(pixelIndexY * width + pixelIndexX) * 4 + 0] = 0xFF;
				pixels[(pixelIndexY * width + pixelIndexX) * 4 + 1] = 0xFF;
				pixels[(pixelIndexY * width + pixelIndexX) * 4 + 2] = 0xFF;
				pixels[(pixelIndexY * width + pixelIndexX) * 4 + 3] = sample * 0xFF / samplesPerPixel;
			} else {
				pixels[pixelIndexY * width + pixelIndexX] = sample * 0xFF / samplesPerPixel;
			}
		}
	}
	
	image = BitmapImage_createWithPixelsNoCopy(rgba ? BITMAP_PIXEL_FORMAT_RGBA_8888 : BITMAP_PIXEL_FORMAT_GRAY_8, width, height, pixels, true);
	if (!PNGImageIO_writePNGFile(image, outFilePath, PNG_PIXEL_FORMAT_AUTOMATIC, false)) {
		fprintf(stderr, "Failed to write PNG to \"%s\" (errno = %d)\n", outFilePath, errno);
		return EXIT_FAILURE;
	}
	BitmapImage_dispose(image);
	
	return EXIT_SUCCESS;
}
