QList<glm::vec2> StratifiedPixelSampler::GetSamples(int x, int y)
{
    float x_ul = static_cast<float>(x);
    float y_ul = static_cast<float>(y);
    float samples = static_cast<float>(samples_sqrt);
    float x_temp;
    float y_temp;

    QList<glm::vec2> result;

    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    std::default_random_engine generator (seed);

    for(int i = 0; i < samples_sqrt; i++)
    {
        for(int j = 0; j < samples_sqrt; j++)
        {
            //fix these later
            x_temp = x_ul + static_cast<float>(i)/samples + unif_distribution(generator)/samples;
            y_temp = y_ul + static_cast<float>(j)/samples + unif_distribution(generator)/samples;
            result.append(glm::vec2(x_temp, y_temp));
        }
    }
    return result;
}
QList<glm::vec2> StratifiedPixelSampler::GetSamples(int x, int y)
{
    int n = this->samples_sqrt;
    float grid_width = 1.f / static_cast<float>(n);
    // top up for window space
    float left = x;
    float top = y;

    QList<glm::vec2> result;


    for (int i = 0; i < n; i++)
    {
        for (int j = 0;j < n; j++)
        {
            // for each cell generate one random point
            result.append(glm::vec2(left + grid_width * (static_cast<float>(j) + unif_distribution(mersenne_generator)),
                                    top + grid_width * (static_cast<float>(i) + unif_distribution(mersenne_generator))
                                    )
                          );
        }
    }

    return result;
}
glm::vec3 DirectLightingIntegrator::EstimateDirectLighting(const Intersection &isx, unsigned int &n_light, unsigned int &n_brdf, const glm::vec3 &woW)
{
    //for Light source sampling
    //QList<glm::vec3> light_sample_pts;
    glm::vec3 brdf_sampling_final(0,0,0);
    glm::vec3 light_sampling_final(0,0,0);
    glm::vec3 color_final(0,0,0);
    Intersection light_sample_isx; //randomly sampled intersection on the light source's surface
    glm::vec3 wiW; // incoming ray in world frame
    Ray light_sample_ray;
    float rand1 = unif_distribution(mersenne_generator);
    float rand2 = unif_distribution(mersenne_generator);
    int light_choice = 0;


    Intersection obstruction_test;

    //for all light sources
    //for (int i = 0; i < scene->lights.count(); i++)
    //{
    //light source sampling
    for(unsigned int j = 0; j < n_light; j++)
    {
        light_choice = rand()%scene->lights.count();
        light_sample_isx = scene->lights[light_choice]->GetRandISX(rand1, rand2, isx.normal); //take 1 sample point(intersection) on the light source for now
        wiW = light_sample_isx.point - isx.point; //ray direction going from world point to light source
        light_sample_isx.t = glm::length(wiW);
        wiW = glm::normalize(wiW);
        light_sample_ray = Ray(isx.point, wiW);//remember, the direction is from point in scene to light source
        obstruction_test = intersection_engine->GetIntersection(light_sample_ray);
        //update random point
        rand1 = unif_distribution(mersenne_generator);
        rand2 = unif_distribution(mersenne_generator);

        if (obstruction_test.object_hit == scene->lights[light_choice])
        {
            light_sampling_final = light_sampling_final + LightPDFEnergy(obstruction_test, isx, light_sample_ray, woW, n_light, n_brdf);
        }
        else
        {
            //the ray contributes zero energy
        }
    }


    light_sampling_final = light_sampling_final/static_cast<float>(n_light); //divide by samples taken
    //light_sampling_final = light_sampling_final + light_sampling_temp; // accumulate energy per high source
    //light_sampling_temp = glm::vec3(0, 0, 0);// zero out color_temp for the next light source
    //}

    //brdf sampling
    for(unsigned int j = 0; j < n_brdf; j++)
    {
        brdf_sampling_final = brdf_sampling_final + BxDFPDFEnergy(isx, woW, n_light, n_brdf);
    }
    brdf_sampling_final = brdf_sampling_final/static_cast<float>(n_brdf);
    color_final = brdf_sampling_final + light_sampling_final;
    return color_final;
}
glm::vec2 StratifiedPixelSampler::getOneSample(int x, int y, int index)
{
    int n = this->samples_sqrt;
    float grid_width = 1.f / static_cast<float>(n);
    // top up for window space
    float left = x;
    float top = y;

    QList<glm::vec2> result;

    int i = index % n;
    int j = index / n;

    return glm::vec2(left + grid_width * (static_cast<float>(j) + unif_distribution(mersenne_generator)),
                                    top + grid_width * (static_cast<float>(i) + unif_distribution(mersenne_generator))
                                    );
}
glm::vec3 Integrator::EstimateDirectLighting(const Intersection &isx, unsigned int &samples_taken, const glm::vec3 &woW)
{
    //for Light source sampling
    //QList<glm::vec3> light_sample_pts;
    glm::vec3 color_temp(0,0,0);
    glm::vec3 color_final(0,0,0);
    Intersection light_sample_isx; //randomly sampled intersection on the light source's surface
    glm::vec3 wiW; // incoming ray in world frame
    Ray light_sample_ray;
    float rand1 = unif_distribution(mersenne_generator);
    float rand2 = unif_distribution(mersenne_generator);

    Intersection obstruction_test;

    //iterate through all the light sources
    for (int i = 0; i < scene->lights.count(); i++)
    {
        for(unsigned int j = 0; j < samples_taken; j++)
        {
            light_sample_isx = scene->lights[i]->GetRandISX(rand1, rand2, isx.normal); //take 1 sample point(intersection) on the light source for now
            wiW = light_sample_isx.point - isx.point; //ray direction going from world point to light source
            light_sample_isx.t = glm::length(wiW);
            wiW = glm::normalize(wiW);
            light_sample_ray = Ray(isx.point, wiW);//remember, the direction is from point in scene to light source
            obstruction_test = intersection_engine->GetIntersection(light_sample_ray);
            //update random point
            rand1 = unif_distribution(mersenne_generator);
            rand2 = unif_distribution(mersenne_generator);

            if (obstruction_test.object_hit == scene->lights[i])
            {
                color_temp = color_temp + CalculateEnergy(light_sample_isx, isx, light_sample_ray, woW);
            }
            else
            {
                //the ray contributes zero energy
            }
        }
        color_temp = color_temp/static_cast<float>(samples_taken); //divide by samples taken
        color_final = color_final + color_temp; // accumulate energy per high source
        color_temp = glm::vec3(0, 0, 0);// zero out color_temp for the next light source
    }

    return color_final;
}
glm::vec3 DirectLightingIntegrator::BxDFPDFEnergy(const Intersection &isx, const glm::vec3 &woW, unsigned int n_light, unsigned int n_brdf)
{
    glm::vec3 ray_color(0, 0, 0);
    glm::vec3 wiW(0, 0, 0);//this will be obtained by sampling BxDf
    Material* M = isx.object_hit->material; //material of point hit
    float brdfPDF;
    float lightPDF;
    float dummy;
    float rand1 = unif_distribution(mersenne_generator);
    float rand2 = unif_distribution(mersenne_generator);
    glm::vec3 M_energy(M->SampleAndEvaluateScatteredEnergy(isx, woW, wiW, brdfPDF, rand1, rand2));
    //use sampled wiW to check if I can hit the light
    Ray shadow_feeler(isx.point, wiW);
    Intersection light_isx = intersection_engine->GetIntersection(shadow_feeler);
    Geometry* L; //this holds the intersected light source


    //terminate early if brdf pdf is zero;
    if (brdfPDF <= 0.0f) return ray_color;
    if (light_isx.object_hit == NULL)//if ray didnt hit anything
        return ray_color;
    if (light_isx.object_hit->material->is_light_source)
    {
        L = light_isx.object_hit;
        lightPDF = L->RayPDF(light_isx, shadow_feeler);
        if (lightPDF <= 0)
        {
            return ray_color;
        }
        glm::vec3 L_energy(L->material->EvaluateScatteredEnergy(light_isx, woW, -shadow_feeler.direction, dummy));
        float W = MIS(brdfPDF, lightPDF);

        ray_color = ComponentMult(L_energy, M_energy);
        ray_color = ComponentMult(ComponentMult(ray_color, M->base_color), isx.texture_color);
        ray_color = ray_color*W/brdfPDF*glm::abs(glm::dot(isx.normal, shadow_feeler.direction));
        return ray_color;
    }
    else
    {
        return ray_color;
    }

}
QList<glm::vec2> RandomPixelSampler::GetSamples(int x, int y)
{
    int n = this->samples_sqrt;
    int total_samples = n * n;
    // top up for window space
    float left = x;
    float top = y;
    //float grid_width = 1.f;

    QList<glm::vec2> result;


    for (int i = 0; i < total_samples; i++)
    {

        // generate one random point
        result.append(glm::vec2(left + unif_distribution(mersenne_generator),
                                top + unif_distribution(mersenne_generator)
                               )
                     );
    }

    return result;
}
glm::vec3 AllLightingIntegrator::LightIndirectEnergy(const Intersection &isx, unsigned int n_split, const glm::vec3 &woW)
{
    int depth = 0;
    Intersection isx_temp = isx;//reflected intersection
    Intersection isx_light; //sampled intersection with "light source"
    glm::vec3 color_accum(0.0f, 0.0f, 0.0f); //accumulated color
    glm::vec3 color_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 wi_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 wo_temp(woW);
    glm::vec3 brdf_energy_accum(1.0f, 1.0f, 1.0f);
    glm::vec3 brdf_energy_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 L_temp(0.0f, 0.0f, 0.0f);
    Material* M_temp = isx.object_hit->material;
    Geometry* obj_temp; //stores the temporary sampled object
    Ray sampler;
    float pdf_temp_brdf(0);
    float pdf_light_temp(0);
    float W(0); //for MIS


    float rand1 = unif_distribution(mersenne_generator);
    float rand2 = unif_distribution(mersenne_generator);
    float epsilon = 0.0001f;
    float throughput = 1.000001f;
    float russian = unif_distribution(mersenne_generator);
    //default samples for direct lighting when estimating the irradiance of some other point in the scene
    unsigned int n_light = 10;
    unsigned int n_brdf = 10;

    int light_source_choice(0);

    while(depth < max_depth && (russian < throughput || depth < 2))
    {
        //sample a random point on a random object in the scene
        light_source_choice = rand()%scene->objects.count();
        obj_temp = scene->objects[light_source_choice];
        //if I hit a real light source, kill the ray
        if (scene->objects[light_source_choice]->material->is_light_source) break;

        isx_light = obj_temp->GetRandISX(rand1, rand2, isx_temp.normal);
        //update random numbers
        rand1 = unif_distribution(mersenne_generator);
        rand2 = unif_distribution(mersenne_generator);
        //make ray towards these points
        wi_temp = glm::normalize(isx_light.point - isx_temp.point);
        sampler = Ray(isx_temp.point, wi_temp);
        //update my light intersection
        isx_light = intersection_engine->GetIntersection(sampler);

        //this ray dies if it hit nothing or is blocked, kill the ray as well
        if(isx_light.object_hit == NULL) break;
        if(isx_light.object_hit != obj_temp) break;

        //to avoid shadow acne
        isx_light.point = isx_light.point + epsilon*isx_light.normal;

        //find out the pdf w/r/t light
        pdf_light_temp = obj_temp->RayPDF(isx_light, sampler);
        //if my pdf is negative, kill the ray as well
        if(pdf_light_temp <= 0) break;

        //update accumulated brdf energy
        brdf_energy_temp = M_temp->EvaluateScatteredEnergy(isx_temp, wo_temp, wi_temp, pdf_temp_brdf);
        brdf_energy_accum = ComponentMult(brdf_energy_accum, brdf_energy_temp);

        //find the direct lighting irradiance of this point towards my original intersection
        wo_temp = -wi_temp; //now the old incoming ray is the outgoing ray for the new intersection
        L_temp = EstimateDirectLighting(isx_light, n_light, n_brdf, wo_temp);
        W = MIS(pdf_light_temp, pdf_temp_brdf);
        //this is light source sampling so use the illumination equation for BRDF sampling to accumulate color
        color_temp = ComponentMult(brdf_energy_accum, L_temp);
        color_temp = ComponentMult(ComponentMult(color_temp, M_temp->base_color), isx_temp.texture_color);
        color_temp = color_temp*W/pdf_light_temp*glm::abs(glm::dot(isx_temp.normal, wi_temp));
        color_accum = color_accum + color_temp/static_cast<float>(n_split);

        throughput = throughput * glm::max(glm::max(color_accum.r, color_accum.g), color_accum.b);

        //update the temporary material
        M_temp = isx_light.object_hit->material;
        isx_temp = isx_light;
        //update random number
        //update depth
        depth++;
        russian = unif_distribution(mersenne_generator);
    }
    return color_accum;
}
glm::vec3 AllLightingIntegrator::BxDFIndirectEnergy(const Intersection &isx, unsigned int n_split, const glm::vec3 &woW)
{
    int depth = 0;
    Intersection isx_temp = isx;//reflected intersection
    Intersection isx_light; //sampled intersection with "light source"
    glm::vec3 color_accum(0.0f, 0.0f, 0.0f); //accumulated color
    glm::vec3 color_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 wi_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 wo_temp(woW);
    glm::vec3 brdf_energy_accum(1.0f, 1.0f, 1.0f);
    glm::vec3 brdf_energy_temp(0.0f, 0.0f, 0.0f);
    glm::vec3 L_temp(0.0f, 0.0f, 0.0f);
    Material* M_temp = isx.object_hit->material;
    Ray sampler;
    float pdf_temp_brdf(0);
    float pdf_light_temp(0);
    float W(0); //for MIS


    float rand1 = unif_distribution(mersenne_generator);
    float rand2 = unif_distribution(mersenne_generator);
    float epsilon = 0.0001;
    float throughput = 1.000001f;
    float russian = unif_distribution(mersenne_generator);
    //default samples for direct lighting when estimating the irradiance of some other point in the scene
    unsigned int n_light = 10;
    unsigned int n_brdf = 10;

    while(depth < max_depth && (russian < throughput || depth < 2))
    {
        //sample random brdf starting at the input isx direction to get reflected ray to begin
        brdf_energy_temp = M_temp->SampleAndEvaluateScatteredEnergy(isx_temp, wo_temp, wi_temp, pdf_temp_brdf, rand1, rand2);
        //update accumulated brdf energy
        brdf_energy_accum = ComponentMult(brdf_energy_accum, brdf_energy_temp);
        //use the sampled incoming ray to find a reflected intersection
        sampler = Ray(isx_temp.point, wi_temp);
        isx_light = intersection_engine->GetIntersection(sampler);

        //this ray dies if I hit a real light source or nothing
        if(isx_light.object_hit == NULL) break;
        else if(isx_light.object_hit->material->is_light_source) break;
        //

        //to avoid shadow acne
        isx_light.point = isx_light.point + epsilon*isx_light.normal;
        //find the direct lighting irradiance of this point towards my original intersection
        wo_temp = -wi_temp; //now the old incoming ray is the outgoing ray for the new intersection
        L_temp = EstimateDirectLighting(isx_light, n_light, n_brdf, wo_temp); //the direct lighting towards the isx
        pdf_light_temp = isx_light.object_hit->RayPDF(isx_light, sampler);
        W = MIS(pdf_temp_brdf, pdf_light_temp);
        //this is BRDF sampling so use the illumination equation for BRDF sampling to accumulate color
        color_temp = ComponentMult(brdf_energy_accum, L_temp);
        color_temp = ComponentMult(ComponentMult(color_temp, M_temp->base_color), isx_temp.texture_color);
        color_temp = color_temp*W/pdf_temp_brdf*glm::abs(glm::dot(isx_temp.normal, wi_temp));
        color_accum = color_accum + color_temp/static_cast<float>(n_split);

        throughput = throughput * glm::max(glm::max(color_accum.r, color_accum.g), color_accum.b);
        //update the temporary material
        M_temp = isx_light.object_hit->material;
        isx_temp = isx_light;
        //update random number
        rand1 = unif_distribution(mersenne_generator);
        rand2 = unif_distribution(mersenne_generator);
        //update depth
        depth++;
        russian = unif_distribution(mersenne_generator);
    }

    return color_accum;
}
glm::vec3 DirectLightingIntegrator::DirectLights(Ray r, glm::vec3& direction, float& bxdf_pdf, Intersection isx, glm::vec3& energy){
    glm::vec3 colour(0.0f);

        Intersection intersection;
        intersection = isx;//intersection_engine->GetIntersection(r);
        glm::vec3 offset_point = intersection.point + 0.1f * intersection.normal;
        if(intersection.object_hit == NULL){
            return colour;
        }
        if(intersection.object_hit->material->is_light_source){
            return intersection.object_hit->material->EvaluateScatteredEnergy(intersection, glm::vec3(0.0f),
                     -r.direction);
        }
//        isx_point = intersection.point;
        glm::vec3 light_colour;
        float light_pdf;
        float w_f, w_g;
        int light_nSamples = 1;
        int brdf_nSamples = 1;
        float brdf_pdf;
        Ray omega_j;
        glm::vec3 light_final_colour(0.0f);
        Geometry* light = scene->lights.at(rand() % scene->lights.size());
        for(int i = 0; i < light_nSamples; i++){
            float x = unif_distribution(mersenne_generator);
            float y = unif_distribution(mersenne_generator);

            Intersection rand_intersection = light->SampleLight(x ,y, offset_point, intersection_engine);
            if(rand_intersection.object_hit != light){
                continue;
            }
            omega_j = Ray(offset_point, glm::normalize(rand_intersection.point - offset_point));
            glm::vec3 wi;
            intersection.object_hit->material->SampleAndEvaluateScatteredEnergy(intersection, -r.direction, wi, brdf_pdf);
            light_pdf = rand_intersection.object_hit->RayPDF(rand_intersection, omega_j);

            if(fequal(light_pdf, 0.0f)){
                continue;
            }

            light_colour =
                    light->material->EvaluateScatteredEnergy(
                        rand_intersection,
                        -r.direction,
                        -omega_j.direction
                        );

            glm::vec3 brdf_colour =
                    intersection.object_hit->material->EvaluateScatteredEnergy(
                        intersection,
                        -r.direction,
                        omega_j.direction
                        );

            w_g = glm::pow(light_nSamples * light_pdf, 2.0f) /
                    (glm::pow(brdf_nSamples * brdf_pdf, 2.0f) + glm::pow(light_nSamples * light_pdf, 2.0f));
            light_final_colour +=
                    w_g *
                    light_colour *
                    brdf_colour *
                    glm::abs(glm::dot((omega_j.direction), (intersection.normal))) / light_pdf;

        }
        light_final_colour = light_final_colour * float(scene->lights.size()) / float(light_nSamples);
        //BIS
        glm::vec3 brdf_final_colour(0.0f);

        glm::vec3 brdf_colourb = intersection.object_hit->material->SampleAndEvaluateScatteredEnergy
                (intersection, -r.direction, omega_j.direction, brdf_pdf);

        direction = omega_j.direction;
        bxdf_pdf = brdf_pdf;
        energy = brdf_colourb;
        omega_j.origin = intersection.point + 0.0001f * omega_j.direction;
        Ray lightRay = Ray(omega_j.origin, omega_j.direction);
        Intersection light_inter = intersection_engine->GetIntersection(lightRay);

        light_nSamples = 1;

        if(!fequal(brdf_pdf, 0.0f)){

            light_pdf = intersection.object_hit->RayPDF(light_inter, omega_j);

            if(light_inter.object_hit && light_inter.object_hit == light){
                if(!fequal(light_pdf, 0.0f)){
                    w_f = glm::pow(brdf_nSamples * brdf_pdf, 2.0f) /
                        (glm::pow(brdf_nSamples * brdf_pdf, 2.0f) + glm::pow(light_nSamples * light_pdf, 2.0f));
                }
                else{
                    w_f = 1.0f;
                }
                light_colour = light_inter.object_hit->material->EvaluateScatteredEnergy(
                    light_inter,
                    -r.direction,
                    -omega_j.direction
                    );
            }
            else{
                w_f = 0.0f;
            }
            float abs_cos = glm::abs(glm::dot((omega_j.direction), (intersection.normal)));
            brdf_final_colour =
                    w_f *
                    light_colour *
                    brdf_colourb *
                    abs_cos / (brdf_pdf);
        }
        return colour = light_final_colour + brdf_final_colour;
}