#include <chrono>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

// Generates one binary PPM image of the Mandelbrot set.
// The workload mirrors Mandelbrot.py and Mandelbrot.kn.

namespace {
constexpr int WIDTH = 1000;
constexpr int HEIGHT = 700;
constexpr int MAX_ITER = 96;

std::string benchmarkFolder() {
#if defined(_WIN32)
	const char separator = '\\';
#else
	const char separator = '/';
#endif
	std::ifstream probe(std::string("samples") + separator + "perf" + separator + "Benchmark.kn");
	if (probe.good()) {
		return std::string("samples") + separator + "perf";
	}
	return ".";
}
} // namespace

int main() {
	const auto begin = std::chrono::steady_clock::now();

	std::vector<std::uint8_t> pixels(static_cast<std::size_t>(WIDTH * HEIGHT * 3), 0);
	std::size_t index = 0;
	long long checksum = 0;
	const double scaleX = 3.3 / static_cast<double>(WIDTH - 1);
	const double scaleY = 2.4 / static_cast<double>(HEIGHT - 1);

	for (int y = 0; y < HEIGHT; ++y) {
		const double ci = -1.2 + static_cast<double>(y) * scaleY;
		for (int x = 0; x < WIDTH; ++x) {
			const double cr = -2.1 + static_cast<double>(x) * scaleX;
			double zr = 0.0;
			double zi = 0.0;
			int iteration = 0;

			while (zr * zr + zi * zi <= 4.0 && iteration < MAX_ITER) {
				const double nextZr = zr * zr - zi * zi + cr;
				zi = 2.0 * zr * zi + ci;
				zr = nextZr;
				++iteration;
			}

			int r = 0;
			int g = 0;
			int b = 0;
			if (iteration != MAX_ITER) {
				const int shade = iteration * 255 / MAX_ITER;
				r = shade;
				g = shade * 7 / 10;
				b = 255 - shade;
			}

			pixels[index++] = static_cast<std::uint8_t>(r);
			pixels[index++] = static_cast<std::uint8_t>(g);
			pixels[index++] = static_cast<std::uint8_t>(b);
			checksum += r + g + b;
		}
	}

	const std::string output = benchmarkFolder() + "/Mandelbrot.cpp.ppm";
	std::ofstream stream(output, std::ios::binary | std::ios::trunc);
	stream << "P6\n" << WIDTH << " " << HEIGHT << "\n255\n";
	stream.write(reinterpret_cast<const char *>(pixels.data()), static_cast<std::streamsize>(pixels.size()));
	stream.close();

	const auto end = std::chrono::steady_clock::now();
	const auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();

	std::cout << "image = " << output << "\n";
	std::cout << "checksum = " << checksum << "\n";
	std::cout << "Duration = " << durationMs << " ms\n";

	return 0;
}
