#pragma once
#ifndef INTERSECTION_H__
#define INTERSECTION_H__

#include <render/camera.h>
#include <render/bsdf.h>
#include <render/emitter.h>
#include <core/frame.h>
#include <render/medium.h>
#include <render/shape.h>
#include <core/utils.h>
#include <assert.h>
#include <iostream>
#include <sstream>
enum EVertex
{
    EVInvalid = 0x00000,
    EVSensor = 0x00001,
    EVSurface = 0x00002,
    EVEmitter = 0x00004 | EVSurface,
    EVVolume = 0x00008,
    EVNull = 0x00010
};
struct Intersection
{
    Intersection() {}
    Intersection(const Intersection &other, Float pdf) : Intersection(other)
    {
        this->pdf = pdf;
    }
    Intersection(const Camera &cam) : p(cam.cpos), medium_id(cam.med_id), type(EVSensor), pdf(1.) {}
    Intersection(const DirectSamplingRecord &dRec) : type(EVEmitter), pdf(dRec.pdf)
    {
        assert(dRec.shape_id >= 0);
        assert(dRec.tri_id >= 0);
        shape_id = dRec.shape_id;
        triangle_id = dRec.tri_id;
        barycentric = dRec.barycentric;
    }
    Intersection(const DirectSamplingRecord &dRec, Float pdf) : type(EVEmitter), pdf(pdf)
    {
        assert(dRec.shape_id >= 0);
        assert(dRec.tri_id >= 0);
        shape_id = dRec.shape_id;
        triangle_id = dRec.tri_id;
        barycentric = dRec.barycentric;
    }
    Intersection(const MediumSamplingRecord &mRec, int med_id, Float pdf)
        : p(mRec.p), medium_id(med_id), pdf(pdf), type(EVVolume) {}

    // Pointers
    const Shape *ptr_shape = nullptr;
    const Medium *ptr_med_int = nullptr;
    const Medium *ptr_med_ext = nullptr;
    const BSDF *ptr_bsdf = nullptr;
    const Emitter *ptr_emitter = nullptr;

    Float t;        // Distance traveled along the ray
    Vector p;       // Intersection point in 3D
    Frame geoFrame; // Geometry Frame
    Frame shFrame;  // Shading Frame
    Vector2 uv;     // uv surface coordinate
    Vector wi;      // Incident direction in local shading frame
    Float J;

    Vector2i indices;
    Vector2 barycentric;

    // augmented data
    EVertex type = EVInvalid;
    Float pdf = 1.0;
    // surface interaction
    int shape_id = -1;
    int triangle_id = -1;
    int int_med_id = -1;
    int ext_med_id = -1;
    // medium interaction
    int medium_id = -1;
    // camera
    Array2i pixel_idx;
    // ==== per-vertex contribution ====
    Spectrum value;     // can store bsdf, Le
    Spectrum nee_bsdf;  // emitter sampling bsdf
    Spectrum bsdf_bsdf; // bsdf sampling bsdf
    int l_nee_id = -1;
    int l_bsdf_id = -1;

    void setZero()
    {
        t = 0.;
        p.setZero();
        geoFrame.setZero();
        shFrame.setZero();
        uv.setZero();
        wi.setZero();
        J = 0.;
        value.setZero();
        nee_bsdf.setZero();
        bsdf_bsdf.setZero();
    }
    inline bool isValid() const { return ptr_shape != nullptr; }
    inline bool isEmitter() const { return ptr_emitter != nullptr; }
    inline Vector toWorld(const Vector &v) const { return shFrame.toWorld(v); }
    inline Vector toLocal(const Vector &v) const { return shFrame.toLocal(v); }
    // Does the surface marked as a transition between two media
    inline bool isMediumTransition() const
    {
        return ptr_med_int != nullptr || ptr_med_ext != nullptr;
    }
    inline const Medium *getTargetMedium(const Vector &d) const
    {
        return d.dot(geoFrame.n) > 0 ? ptr_med_ext : ptr_med_int;
    }
    inline int getTargetMediumId(const Vector &d) const
    {
        return d.dot(geoFrame.n) > 0 ? ext_med_id : int_med_id;
    }
    inline const Medium *getTargetMedium(Float cosTheta) const
    {
        return cosTheta > 0 ? ptr_med_ext : ptr_med_int;
    }
    inline int getTargetMediumId(Float cosTheta) const
    {
        return cosTheta > 0 ? ext_med_id : int_med_id;
    }
    inline const BSDF *getBSDF() const { return ptr_bsdf; }
    inline Spectrum Le(const Vector &wo) const
    {
        if (ptr_emitter)
            return ptr_emitter->eval(*this, wo);
        else
            return Spectrum::Zero();
    }
    inline Spectrum evalBSDF(const Vector &wo,
                             EBSDFMode mode = EBSDFMode::ERadiance) const
    {
        return ptr_bsdf->eval(*this, wo, mode);
    }
    inline Spectrum sampleBSDF(const Array &rnd, Vector &wo, Float &pdf,
                               Float &eta,
                               EBSDFMode mode = EBSDFMode::ERadiance) const
    {
        return ptr_bsdf->sample(*this, rnd, wo, pdf, eta, mode);
    }
    inline void sampleBSDFDual(const Array &rnd, Vector &wo, Vector &woDual, Float &pdf, Float &pdfDual,
                               Float &eta, Float &etaDual, Spectrum &ret, Spectrum &retDual,
                               EBSDFMode mode = EBSDFMode::ERadiance) const
    {
        ptr_bsdf->sampleDual(*this, rnd, wo, woDual, pdf, pdfDual, eta, etaDual, ret, retDual, mode);
    }
    inline Float pdfBSDF(const Vector &wo) const
    {
        return ptr_bsdf == nullptr ? 1.0 : ptr_bsdf->pdf(*this, wo);
    }
    inline Float pdfEmitter(const Vector &wo) const
    {
        return ptr_emitter == nullptr ? 0.0f : ptr_emitter->pdf(*this, wo);
    }
};

#endif
