#pragma once
#include "cuda_helper.hpp"

inline __host__ __device__ float uintToFloat(uint x) {
    union {
        unsigned int a;
        float b;
    } u;

    u.a = 0x3f800000 | (x >> 9);

    return u.b - 1.f;
}

// returns random float between (0,1]
inline __host__ __device__ float uniform_float(uint2& seed) {
    // PCG2D, as described here: https://jcgt.org/published/0009/03/02/
    seed = 1664525u * seed + 1013904223u;
    seed.x += 1664525u * seed.y;
    seed.y += 1664525u * seed.x;
    seed ^= (seed >> 16u);
    seed.x += 1664525u * seed.y;
    seed.y += 1664525u * seed.x;
    seed ^= (seed >> 16u);
    // return float from seed
    return 1.f - uintToFloat(seed.x);
}

// returns random float2 between (0,1]^2
inline __host__ __device__ float2 uniform_float2(uint2& seed) {
    // PCG2D, as described here: https://jcgt.org/published/0009/03/02/
    seed = 1664525u * seed + 1013904223u;
    seed.x += 1664525u * seed.y;
    seed.y += 1664525u * seed.x;
    seed ^= (seed >> 16u);
    seed.x += 1664525u * seed.y;
    seed.y += 1664525u * seed.x;
    seed ^= (seed >> 16u);
    // return float from seed
    return 1.f - make_float2(uintToFloat(seed.x), uintToFloat(seed.y));
}

__host__ __device__ inline CUDA::Intersection intersectAABB(CUDA::Ray ray, float3 minBox, float3 maxBox) {
    const float3 invR = 1.0f / ray.dir;

    const float3 a = (minBox - ray.eye) * invR;
    const float3 b = (maxBox - ray.eye) * invR;

    const float3 t0 = fminf(a, b);
    const float3 t1 = fmaxf(a, b);

    const float tmin = fmaxf(fmaxf(t0), 0.0f);
    const float tmax = fminf(t1);

    return {tmax > tmin, tmin, tmax};
}

__host__ __device__ inline CUDA::Ray generateRayPinhole(float i, float j, float width, float height, float3 ori,
                                                        float3 U, float3 V, float3 W) {
    // The center of each pixel is at fraction (0.5,0.5)
    const float2 subpixel_jitter = make_float2(0.5f, 0.5f);

    const float2 d = 2.0f * make_float2((i + subpixel_jitter.x) / width, (j + subpixel_jitter.y) / height) - 1.0f;
    const float3 dir = normalize(d.x * U + d.y * V + W);
    return {ori, dir};
}

__host__ __device__ inline CUDA::PointLight SampleLights(CUDA::PointLight* lights, float numLights, float rand,
                                                         float& pdf) {
    if (numLights == 0) {
        return {-1, make_float3(-1)};
    }
    float u = rand * numLights;
    int index = (int) fminf(u, numLights - 1.f);
    pdf = 1.f / numLights;
    return lights[index];
}

__host__ __device__ inline CUDA::LightSample SamplePointLight(float3 point, CUDA::PointLight light) {
    float3 dstVec = light.pos - point;
    float dist = dot(dstVec, dstVec);
    float3 Li = light.intensity * make_float3(1) / dist;
    float3 wi = normalize(dstVec);

    return {Li, wi, 1, light.pos};
}
