#include "blinn_phong_shading.h"
// Hint:
#include "first_hit.h"
#include <iostream>

const double AMBIENT_INTENSITY = 0.1;

/* Helper functions */
Eigen::Vector3d lambert(
    const Eigen::Vector3d& n,
    const Eigen::Vector3d& kd,
    const Eigen::Vector3d& l,
    const Eigen::Vector3d& I);
Eigen::Vector3d blinn_phong(
    const Eigen::Vector3d& v,
    const Eigen::Vector3d& n,
    const Eigen::Vector3d& ks,
    double p,
    const Eigen::Vector3d& l,
    const Eigen::Vector3d& I);

// Hint: use I_a = 0.1 to get a subtle, ambient light contribution.
Eigen::Vector3d blinn_phong_shading(
    const Ray & ray,
    const int & hit_id,
    const double & t,
    const Eigen::Vector3d & n,
    const std::vector< std::shared_ptr<Object> > & objects,
    const std::vector<std::shared_ptr<Light> > & lights)
{
    Eigen::Vector3d rgb, light_direction;
    double max_t;

    Eigen::Vector3d v = -ray.direction.normalized();
    Eigen::Vector3d hit = ray.origin + t * ray.direction;
    std::shared_ptr<Material> material = objects[hit_id]->material;

    /* Ambient lighting. */
    rgb = material->ka * AMBIENT_INTENSITY;

    for (const auto & light : lights) {
        light->direction(hit, light_direction, max_t);

        /* Diffuse component (Lambertian shading) */
        rgb += lambert(n, material->kd, light_direction, light->I);

        /* Specular component (Blinn-Phong shading) */
        rgb += blinn_phong(v, n, material->ks, material->phong_exponent, light_direction, light->I);

        /* Shadows */
        // TODO
    }

    return rgb;
}

/**
 * Compute the Lambertian shading model.
 *
 * Inputs:
 *  n  surface normal
 *  kd  diffuse reflection coefficient
 *  l  direction to light source
 *  I  intensity of light source
 */
Eigen::Vector3d lambert(
    const Eigen::Vector3d& n,
    const Eigen::Vector3d& kd,
    const Eigen::Vector3d& l,
    const Eigen::Vector3d& I)
{
    return (kd.array() * I.array()).matrix() * std::max(0.0, n.dot(l));
}

/**
 * Compute the Blinn-Phong shading model.
 *
 * Inputs:
 *  v  direction to viewer
 *  n  surface normal
 *  ks  specular reflection coefficient
 *  p  Phong exponent
 *  l  direction to light source
 *  I  intensity of light source
 */
Eigen::Vector3d blinn_phong(
    const Eigen::Vector3d& v,
    const Eigen::Vector3d& n,
    const Eigen::Vector3d& ks,
    const double p,
    const Eigen::Vector3d& l,
    const Eigen::Vector3d& I)
{
    /* Calculate the half vector of v and l. */
    Eigen::Vector3d h = (v + l).normalized();
    return (ks.array() * I.array()).matrix() * std::pow(std::max(0.0, n.dot(h)), p);
}
