/* Credits for drawing the Mandelbrot set in C++ go to
 * Farouk Ounane (https://github.com/ChinksofLight/mandelbrot_cpp)
 */

#include <iostream>
#include <fstream> // write to file
#include <complex> // complex numbers
#include <cmath> // pow

#include "workerpool.hpp"

constexpr int width = 1200;
constexpr int height = 1200;
constexpr int max_iter = 1000;
constexpr int col_depth = 255;

int main() {
    // initiate a worker pool
    WorkerPool wp(8); // Use 8 threads
    // make an array of values
    int vals[width][height];
    std::cout << "passing tasks to worker pool..." << std::endl;
    for ( int i = 0; i < width; ++i ) {
        for ( int j = 0; j < height; ++j ) {
            /* use a closure to define the WorkerPool::Task object
             * the vals array is captured by reference.
             * NB: we have to capture i and j by value!
             */
            wp.push_back([=,&vals](){
                double x = double(i)/width-1.5;
                double y = double(j)/height-0.5;
                std::complex<double> point(x, y);
                // determine if point is in the Mandelbrot set...
                std::complex<double> z(0, 0);
                int nb_iter = 0;
                while ( std::abs(z) < 2 && nb_iter <= max_iter) {
                    z = z * z + point;
                    nb_iter++;
                }
                if ( nb_iter < max_iter ) {
                    // determine color gradient
                    double frac = double(nb_iter) / max_iter;
                    vals[i][j] = int(col_depth * pow(frac, 0.25));
                } else {
                    vals[i][j] = 0;
                }
            });
        }
    }

    std::cout << "waiting for workers..." << std::endl;
    /* before we sync the worker pool, the values in vals 
     * are invalid. accessing them would lead to data races.
     */
    wp.sync();
    
    /* now all values have been computed and stored in vals.
     * The WorkerPool is still waiting for new tasks. 
     * For example, we could use it to write the data 
     * to a file in the background.
     */
    wp.push_back([&](){
        std::ofstream image_file("mandelbrot.ppm");
        if ( !image_file.is_open() ) {
            std::cerr << "could not open the image file" << std::endl;
            return;
        } // else
        image_file << "P3\n" << width << " " << height << " " 
                   << col_depth << "\n";
        for ( int j = 0; j < height; j++ ) {
            for ( int i = 0; i < width; i++ ) {        
                int val = vals[i][j];
                image_file << 0 << " " << val << " " << val << "\n";
            }
        }
        image_file.close();       
    });

    std::cout << "writing results to file..." << std::endl;
    
    // before we return, we have to wait for the writing task to be completed
    wp.sync();

    return 0;
}