#include <stdio.h>
#include <png.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>

float lerp(float a0, float a1, float w) {
	return a0 + w * (a1 - a0);
}

float weight(float x) {
	return 3 * (x * x) - 2 * (x * x * x);
}

float perlin(float* noiseMap, int width, int height, float x, float y) {
	int nodex = (int)x;
	int nodey = (int)y;
	float sx = x - nodex;
	float sy = y - nodey;
	
	float wx = weight(sx);
	float wy = weight(sy);
	
	nodex %= width - 1;
	nodey %= height - 1;
	
	int idx = (nodey * width + nodex) * 2;
	float dot0 = sx * noiseMap[idx] + sy * noiseMap[idx + 1];
	idx += 2;
	float dot1 = (sx - 1) * noiseMap[idx] + sy * noiseMap[idx + 1];
	idx = ((nodey + 1) * width + nodex) * 2;
	float dot2 = sx * noiseMap[idx] + (sy - 1) * noiseMap[idx + 1];
	idx += 2;
	float dot3 = (sx - 1) * noiseMap[idx] + (sy - 1) * noiseMap[idx + 1];
	
	float ix0 = lerp(dot0, dot1, wx);
	float ix1 = lerp(dot2, dot3, wx);
	
	return lerp(ix0, ix1, wy);
}

float octave_perlin(float* noiseMap, int width, int height, float x, float y, int octaves, float lacunarity, float persistence) {
	float finalRes = 0;
	float max = 0;
	float currFreq = 1;
	float currSc = 1;
	for(int i = 0; i < octaves; i++) {
		finalRes += perlin(noiseMap, width, height, x * currFreq, y * currFreq) * currSc;
		max += currSc;
		currFreq *= lacunarity;
		currSc *= persistence;
	}
	return finalRes / max;
}

int main(int argc, char **argv) {
	int noiseSize = 512;
	int outputSize = 1024;
	float* noisemap = (float*)malloc(noiseSize * noiseSize * 2 * sizeof(float));
	float* output = (float*)malloc(outputSize * outputSize * sizeof(float));
	if(!noisemap || !output) {
		printf("Failed to allocate memory for generation\r\n");
		return 1;
	}
	for(int i = 0; i < noiseSize * noiseSize; i++) {
		int idx = i << 1;
		float f1 = (rand() & 65535) / 65535.0f * 2 - 1;
		float f2 = (rand() & 65535) / 65535.0f * 2 - 1;
		float len = sqrt(f1 * f1 + f2 * f2);
		f1 /= len;
		f2 /= len;
		noisemap[idx] = f1;
		noisemap[idx+1] = f2;
	}
	
	const float circ_radius = 0.08f;
	const float mountain_radius = 0.4f;
	for(int i = 0; i < outputSize; i++) {
		for(int j = 0; j < outputSize; j++) {
			float x = (float)i / (float)outputSize;
			float y = (float)j / (float)outputSize;
			float res = 0;
			
			res = octave_perlin(noisemap, noiseSize, noiseSize, x * 4, y * 4, 8, 2.0, 0.4) * 1.1;
			if(res < 0) res = -res;
			
			float mul;
			float temp1 = x - 0.5;
			float temp2 = y - 0.5;
			float dist = sqrt(temp1 * temp1 + temp2 * temp2);
			if(dist < circ_radius) {
				mul = dist / circ_radius;
				mul = (exp(mul) - 1) / 1.6;
				res *= mul;
			}
			if(dist > mountain_radius) {
				mul = (dist - mountain_radius) / (0.5f - mountain_radius);
				if(mul < 0) mul = 0;
				if(mul > 1) mul = 1;
				mul = 1 - mul;
				res *= mul;
			}
			
			output[j * outputSize + i] = res;
		}
	}
	
	for(int i = 0; i < 2; i++) {
		png_infop info_ptr = NULL;
		png_structp png_ptr = NULL;
		png_byte** row_pointers = NULL;
		int x,y;
		FILE* fp = fopen(i == 0 ? "output.png" : "output_bw.png", "wb");
		if(!fp) {
			printf("Failed to open output file.\r\n");
			return 1;
		}
		png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
		if(!png_ptr) goto screenshot_failed;
		info_ptr = png_create_info_struct(png_ptr);
		if(!info_ptr) goto screenshot_failed;
		if(setjmp(png_jmpbuf(png_ptr))) goto screenshot_failed;
		png_set_IHDR(png_ptr, info_ptr, outputSize, outputSize, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
		row_pointers = png_malloc(png_ptr, outputSize * sizeof(png_byte *));
		for(y = 0; y < outputSize; y++) {
			png_byte *row = png_malloc(png_ptr, sizeof(uint8_t) * outputSize * 3);
			row_pointers[y] = row;
			for(x = 0; x < outputSize; x++) {
				float f = output[y * outputSize + x];
				if(f < 0) f = 0;
				if(f > 1) f = 1;
				*row++ = (int)(f * 255);
				if(i) {
					*row++ = (int)(f * 255);
					*row++ = (int)(f * 255);
				}else {
					int i = (int)(f * 65535);
					*row++ = i >> 8;
					*row++ = i & 255;
				}
			}
		}
		png_init_io(png_ptr, fp);
		png_set_rows(png_ptr, info_ptr, row_pointers);
		png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
		for(y = 0; y < outputSize; y++) png_free(png_ptr, row_pointers[y]);
		png_free(png_ptr, row_pointers);
		png_destroy_write_struct(&png_ptr, &info_ptr);
		fclose(fp);
	}
	
	free(output);
	free(noisemap);
	return 0;
screenshot_failed:
	printf("PNG generate failed.\r\n");
	free(output);
	free(noisemap);
	return 1;
}
