import { vec3 } from "gl-matrix";
import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { Ray3 } from "../../../../core/geom/3d/ray/Ray3.js";
import { getBiasedNormalSample } from "./sampling/getBiasedNormalSample.js";

/*
 Ray hit data layout:
 position: v3
 normal: v3
 t: float
 u: float
 v: float
 primitive_index: uint
 instance_id: uint
 */


const irradiance = [0, 0, 0];

const trace_result = [];

const _ray_0 = new Ray3();

const tmp_0 = new Float32Array(6);

/**
 * Normalized probability of terminating the ray
 * @type {number}
 */
const RUSSIAN_ROULETTE_PROBABILITY = 0.5;

const color_mask = new Float32Array(3);

export class PathTracer {


    /**
     *
     * @param {number[]|Float32Array} out
     * @param {number[]|Float32Array|Ray3} ray
     * @param {number} min_bounce
     * @param {number} max_bounce
     * @param {function} random
     * @param {PathTracedScene} scene
     */
    path_trace(
        out,
        ray,
        min_bounce,
        max_bounce,
        random = Math.random,
        scene
    ) {

        _ray_0.copy(ray);

        irradiance[0] = 0;
        irradiance[1] = 0;
        irradiance[2] = 0;

        color_mask[0] = 1;
        color_mask[1] = 1;
        color_mask[2] = 1;

        let i;
        for (i = 0; i < max_bounce; ++i) {

            if (i >= min_bounce) {
                const russian_roulette_roll = random();

                if (russian_roulette_roll < RUSSIAN_ROULETTE_PROBABILITY) {
                    // terminate ray
                    break;
                }
            }

            const hit_distance = scene.trace(trace_result, _ray_0);

            if (hit_distance < 0) {
                // ray didn't hit anything

                break;
            }

            scene.sample_material(tmp_0, trace_result, _ray_0);


            vec3.multiply(color_mask, color_mask, tmp_0);

            // construct new ray, aligned on the normal of the contact
            array_copy(tmp_0, 3, _ray_0, 3, 3); // copy new direction
            array_copy(trace_result, 0, _ray_0, 0, 3); // copy new origin

            // move ray forward by a small amount to avoid self-occlusion
            _ray_0.shiftForward(0.00001);

            // light sampling
            scene.sample_lights(tmp_0, 0, _ray_0);

            // sample on cosine lobe ray
            getBiasedNormalSample(_ray_0, 3, _ray_0, 3, 1, random);

            // // accumulate irradiance
            irradiance[0] += color_mask[0] * tmp_0[0];
            irradiance[1] += color_mask[1] * tmp_0[1];
            irradiance[2] += color_mask[2] * tmp_0[2];

        }


        if (i === 0) {
            // ray didn't hit anything on the very first bounce

            // sample "environment" and terminate path as there is nothing to reflect off of
            scene.sample_background(tmp_0, 0, _ray_0, 3);
            vec3.multiply(irradiance, irradiance, tmp_0);

        }

        out[0] = irradiance[0]
        out[1] = irradiance[1]
        out[2] = irradiance[2]

    }
}