#include "CpuGenerators.h"
#include "Types.h"

#ifdef WITH_MPFR
#include "MpfrWrapper.h"
#endif // WITH_MPFR

#include <omp.h>
#include <cmath>

#include <memory>

using mnd::CpuGenerator;

namespace mnd
{
    template class CpuGenerator<float, mnd::NONE, false, false>;
    template class CpuGenerator<float, mnd::NONE, false, true>;
    template class CpuGenerator<float, mnd::NONE, true, false>;
    template class CpuGenerator<float, mnd::NONE, true, true>;

    template class CpuGenerator<double, mnd::NONE, false, false>;
    template class CpuGenerator<double, mnd::NONE, false, true>;
    template class CpuGenerator<double, mnd::NONE, true, false>;
    template class CpuGenerator<double, mnd::NONE, true, true>;

    /*
    template class CpuGenerator<Fixed128, mnd::NONE, false, false>;
    template class CpuGenerator<Fixed128, mnd::NONE, false, true>;
    template class CpuGenerator<Fixed128, mnd::NONE, true, false>;
    template class CpuGenerator<Fixed128, mnd::NONE, true, true>;
    */

#ifdef WITH_BOOST
#include <boost/multiprecision/cpp_bin_float.hpp>
    template class CpuGenerator<mnd::Float128, mnd::NONE, false, false>;
    template class CpuGenerator<mnd::Float128, mnd::NONE, false, true>;
    template class CpuGenerator<mnd::Float128, mnd::NONE, true, false>;
    template class CpuGenerator<mnd::Float128, mnd::NONE, true, true>;
#endif
}


template<typename T, bool parallel, bool smooth>
void CpuGenerator<T, mnd::NONE, parallel, smooth>::generate(const mnd::MandelInfo& info, float* data)
{
    const MandelViewport& view = info.view;

    if constexpr (parallel)
        omp_set_num_threads(2 * omp_get_num_procs());
#pragma omp parallel for if (parallel)
    for (long j = 0; j < info.bHeight; j++) {
        T y = T(view.y) + T(j) * T(view.height / info.bHeight);
        long i = 0;
        for (i; i < info.bWidth; i++) {
            T x = T(view.x + T(i) * T(view.width / info.bWidth));

            T a = x;
            T b = y;

            int k = 0;
            for (k = 0; k < info.maxIter; k++) {
                T aa = a * a;
                T bb = b * b;
                T ab = a * b;
                a = aa - bb + x;
                b = ab + ab + y;
                if (aa + bb > T(16)) {
                    break;
                }
            }
            if constexpr (smooth) {
                if (k >= info.maxIter)
                    data[i + j * info.bWidth] = info.maxIter;
                else
                    data[i + j * info.bWidth] = ((float) k) + 1 - ::logf(::logf(float(a * a + b * b)) / 2) / ::logf(2.0f);
            }
            else
                data[i + j * info.bWidth] = k;
        }
    }
}


#ifdef WITH_MPFR
template<unsigned int bits, bool parallel, bool smooth>
void CpuGenerator<mnd::MpfrFloat<bits>, mnd::NONE, parallel, smooth>::generate(const mnd::MandelInfo& info, float* data)
{
    const MandelViewport& view = info.view;
    using T = mnd::MpfrFloat<bits>;

    if constexpr (parallel)
        omp_set_num_threads(2 * omp_get_num_procs());
#pragma omp parallel for if (parallel)
    for (long j = 0; j < info.bHeight; j++) {
        T y = T(view.y) + T(j) * T(view.height / info.bHeight);
        long i = 0;
        for (i; i < info.bWidth; i++) {
            T x = T(view.x + T(i) * T(view.width / info.bWidth));

            T a = x;
            T b = y;

            int k = 0;
            for (k = 0; k < info.maxIter; k++) {
                T aa = a * a;
                T bb = b * b;
                T ab = a * b;
                a = aa - bb + x;
                b = ab + ab + y;
                if (aa + bb > T(16)) {
                    break;
                }
            }
            if constexpr (smooth) {
                if (k >= info.maxIter)
                    data[i + j * info.bWidth] = info.maxIter;
                else
                    data[i + j * info.bWidth] = ((float) k) + 1 - ::log(::log(a * a + b * b) / 2) / ::log(2.0f);
            }
            else
                data[i + j * info.bWidth] = k;
        }
    }
}
#endif // WITH_MPFR


/*
void CpuGeneratorDouble::generate(const mnd::MandelInfo& info, float* data)
{
    const MandelViewport& view = info.view;
    omp_set_num_threads(2 * omp_get_num_procs());
#pragma omp parallel for
    for (long j = 0; j < info.bHeight; j++) {
        double y = double(view.y) + double(j) * double(view.height / info.bHeight);
        long i = 0;
        for (i; i < info.bWidth; i++) {
            double x = view.x + double(i) * view.width / info.bWidth;

            double a = x;
            double b = y;

            int k = 0;
            for (k = 0; k < info.maxIter; k++) {
                double aa = a * a;
                double bb = b * b;
                double ab = a * b;
                a = aa - bb + x;
                b = ab + ab + y;
                if (aa + bb > 16) {
                    break;
                }
            }
            data[i + j * info.bWidth] = k;
        }
    }
}


void CpuGenerator128::generate(const mnd::MandelInfo& info, float* data)
{
    const MandelViewport& view = info.view;
    omp_set_num_threads(2 * omp_get_num_procs());
#pragma omp parallel for
    for (long j = 0; j < info.bHeight; j++) {
        Fixed128 y = Fixed128(view.y) + Fixed128(j) * Fixed128(view.height / info.bHeight);
        long i = 0;
        for (i; i < info.bWidth; i++) {
            Fixed128 x = view.x + Fixed128(i) * Fixed128(view.width / info.bWidth);

            Fixed128 a = x;
            Fixed128 b = y;

            int k = 0;
            for (k = 0; k < info.maxIter; k++) {
                Fixed128 aa = a * a;
                Fixed128 bb = b * b;
                Fixed128 ab = a * b;
                a = aa - bb + x;
                b = ab + ab + y;
                if (aa + bb > Fixed128(16)) {
                    break;
                }
            }

            data[i + j * info.bWidth] = k;
        }
    }
}
*/