#include <kazen/bsdf.h>
#include <kazen/mesh.h>
#include <kazen/frame.h>
#include <kazen/warp.h>
#include <kazen/texture.h>
#include <kazen/mircofacet.h>
#include <kazen/proplist.h>
#include <Eigen/Geometry> // cross()
#include <OpenImageIO/texture.h>
#include <OpenImageIO/ustring.h>
#include <filesystem/resolver.h>

NAMESPACE_BEGIN(kazen)

/**
 * \brief Diffuse / Lambertian BRDF model
 */
class Diffuse : public BSDF {
public:
    Diffuse(const PropertyList &propList) {
        m_albedo = propList.getColor("albedo", Color3f(0.5f));
    }

    /// Evaluate the BRDF model
    Color3f eval(const BSDFQueryRecord &bRec) const {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (bRec.measure != ESolidAngle
            || Frame::cosTheta(bRec.wi) <= 0
            || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        /* The BRDF is simply the albedo / pi */
        return m_albedo * INV_PI * Frame::cosTheta(bRec.wo);
    }

    /// Compute the density of \ref sample() wrt. solid angles
    float pdf(const BSDFQueryRecord &bRec) const {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (bRec.measure != ESolidAngle
            || Frame::cosTheta(bRec.wi) <= 0
            || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;

        /* Importance sampling density wrt. solid angles:
           cos(theta) / pi.

           Note that the directions in 'bRec' are in local coordinates,
           so Frame::cosTheta() actually just returns the 'z' component.
        */
        return INV_PI * Frame::cosTheta(bRec.wo);
    }

    /// Draw a a sample from the BRDF model
    Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);

        bRec.measure = ESolidAngle;

        /* Warp a uniformly distributed sample on [0,1]^2
           to a direction on a cosine-weighted hemisphere */
        bRec.wo = Warp::squareToCosineHemisphere(sample);

        /* Relative index of refraction: no change */
        bRec.eta = 1.0f;

        /* eval() / pdf() * cos(theta) = albedo. There
           is no need to call these functions. */
        return m_albedo;
    }

    bool isDiffuse() const {
        return true;
    }

    /// Return a human-readable summary
    std::string toString() const {
        return fmt::format(
            "Diffuse[\n"
            "  albedo = {}\n"
            "]", m_albedo.toString());
    }

    EClassType getClassType() const { return EBSDF; }
private:
    Color3f m_albedo;
};

/**
 * \brief Dielectric / Ideal dielectric BSDF
 */
class Dielectric : public BSDF {
public:
    Dielectric(const PropertyList &propList) {
        /* Interior IOR (default: BK7 borosilicate optical glass) */
        m_intIOR = propList.getFloat("intIOR", 1.5046f);

        /* Exterior IOR (default: air) */
        m_extIOR = propList.getFloat("extIOR", 1.000277f);
    }

    Color3f eval(const BSDFQueryRecord &) const {
        /* Discrete BRDFs always evaluate to zero in kazen */
        return Color3f(0.0f);
    }

    float pdf(const BSDFQueryRecord &) const {
        /* Discrete BRDFs always evaluate to zero in kazen */
        return 1.0f;
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const {
        bRec.measure = EDiscrete;

        auto cosThetaI = Frame::cosTheta(bRec.wi);
        auto fresnelTerm = fresnel(cosThetaI, m_extIOR, m_intIOR);

        if (sample.x() < fresnelTerm) {
            /* Reflect wo = -wi + 2*dot(wi, n)*n. In this case is much simpler:
            https://www.pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission*/
            bRec.wo = Vector3f(-bRec.wi.x(), -bRec.wi.y(), bRec.wi.z());
            bRec.eta = 1.f;
            return Color3f(1.0f);
        } else {
            auto n = Vector3f(0.0f, 0.0f, 1.0f);
            auto factor = m_intIOR / m_extIOR;
            /* inside out */
            if (Frame::cosTheta(bRec.wi) < 0.f) {
                factor = m_extIOR / m_intIOR;
                n.z() = -1.0f;
            }
            bRec.wo = refract(-bRec.wi, n, factor);
            bRec.eta = m_intIOR / m_extIOR;
            return Color3f(1.0f);
        }
    }

    std::string toString() const {
        return fmt::format(
            "Dielectric[\n"
            "  intIOR = {},\n"
            "  extIOR = {}\n"
            "]",
            m_intIOR, m_extIOR);
    }
private:
    float m_intIOR, m_extIOR;
};

/**
 * \brief Mirror / Ideal mirror BRDF
 */
class Mirror : public BSDF {
public:
    Mirror(const PropertyList &) { }

    Color3f eval(const BSDFQueryRecord &) const {
        /* Discrete BRDFs always evaluate to zero in kazen */
        return Color3f(0.0f);
    }

    float pdf(const BSDFQueryRecord &) const {
        /* Discrete BRDFs always evaluate to zero in kazen */
        return 1.0f;
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &) const {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);

        // Reflection in local coordinates
        bRec.wo = Vector3f(
            -bRec.wi.x(),
            -bRec.wi.y(),
             bRec.wi.z()
        );
        bRec.measure = EDiscrete;

        /* Relative index of refraction: no change */
        bRec.eta = 1.0f;

        return Color3f(1.0f);
    }

    std::string toString() const {
        return "Mirror[]";
    }
};

/**
 * \brief Diffuse / Lambertian BRDF model with texture as albedo
 */
class Lambertian : public BSDF {
public:
    Lambertian(const PropertyList &propList) { }

    ~Lambertian() {
        if(!m_albedo) delete m_albedo;
    }

    Color3f eval(const BSDFQueryRecord &bRec) const override {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (bRec.measure != ESolidAngle
            || Frame::cosTheta(bRec.wi) <= 0
            || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        /* The BRDF is simply the albedo / pi */
        return m_albedo->eval(bRec.uv) * INV_PI * Frame::cosTheta(bRec.wo);
    }

    float pdf(const BSDFQueryRecord &bRec) const override {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (bRec.measure != ESolidAngle
            || Frame::cosTheta(bRec.wi) <= 0
            || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;

        /* Importance sampling density wrt. solid angles:
           cos(theta) / pi.

           Note that the directions in 'bRec' are in local coordinates,
           so Frame::cosTheta() actually just returns the 'z' component.
        */
        return INV_PI * Frame::cosTheta(bRec.wo);
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const override {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);

        bRec.measure = ESolidAngle;

        /* Warp a uniformly distributed sample on [0,1]^2
           to a direction on a cosine-weighted hemisphere */
        bRec.wo = Warp::squareToCosineHemisphere(sample);

        /* Relative index of refraction: no change */
        bRec.eta = 1.0f;

        /* eval() / pdf() * cos(theta) = albedo. There
           is no need to call these functions. */
        return m_albedo->eval(bRec.uv);
    }

    void addChild(Object *obj) override {
        switch (obj->getClassType()) {
            case ETexture:
                m_albedo = static_cast<Texture<Color3f>*>(obj);
                break;
            default:
                throw Exception("addChild is not supported other than albedi maps");
        }
    }

    EClassType getClassType() const { return EBSDF; }

    std::string toString() const {
        return fmt::format("Lambertian[]");
    }

private:
    Texture<Color3f>* m_albedo=nullptr;
};

/// normal map (normal switch)
class NormalMap : public BSDF {
public:
    NormalMap(const PropertyList &propList) { }

    ~NormalMap() {
        if(!m_normalMap) delete m_normalMap;
        if(!m_nested) delete m_nested;
    }

    Color3f eval(const BSDFQueryRecord &bRec) const override {
        const Intersection &its = bRec.its;
        Color3f rgb = m_normalMap->eval(its.uv);
        Vector3f n(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1);

		if (Frame::cosTheta(bRec.wi) > 0 && Frame::cosTheta(bRec.wo) > 0 && n.dot(bRec.wi) <= 0)
            return m_nested->eval(bRec);

        Intersection perturbed(its);
        perturbed.shFrame = getFrame(its, n.normalized(), bRec.wi);

        BSDFQueryRecord perturbedQuery(
            perturbed.toLocal(its.toWorld(bRec.wi)),
            perturbed.toLocal(its.toWorld(bRec.wo)), bRec.measure);

		if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0)
			return Color3f(0.0f);

        perturbedQuery.uv = bRec.uv;
        perturbedQuery.measure = bRec.measure;
        perturbedQuery.eta = bRec.eta;
        return m_nested->eval(perturbedQuery);
    }

    float pdf(const BSDFQueryRecord &bRec) const override {
        const Intersection &its = bRec.its;
        Color3f rgb = m_normalMap->eval(its.uv);
        Vector3f n(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1);

		if (Frame::cosTheta(bRec.wi) > 0 && Frame::cosTheta(bRec.wo) > 0 && n.dot(bRec.wi) <= 0)
            return m_nested->pdf(bRec);

        Intersection perturbed(its);
        perturbed.shFrame = getFrame(its, n.normalized(), bRec.wi);

        BSDFQueryRecord perturbedQuery(
            perturbed.toLocal(its.toWorld(bRec.wi)),
            perturbed.toLocal(its.toWorld(bRec.wo)), bRec.measure);

		if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0)
			return 0.0f;

        perturbedQuery.uv = bRec.uv;
        perturbedQuery.measure = bRec.measure;
        perturbedQuery.eta = bRec.eta;
        return m_nested->pdf(perturbedQuery);
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const override {
        const Intersection &its = bRec.its;
        Color3f rgb = m_normalMap->eval(its.uv);
        Vector3f n(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1);

		if (Frame::cosTheta(bRec.wi) > 0 && n.dot(bRec.wi) <= 0) {
			bRec.eta = 1.0f;
			return m_nested->sample(bRec, sample);
		}

		Intersection perturbed(its);
		perturbed.shFrame = getFrame(its, n.normalized(), bRec.wi);

        BSDFQueryRecord perturbedQuery(perturbed.toLocal(its.toWorld(bRec.wi)));
        perturbedQuery.uv = its.uv;
        perturbedQuery.measure = bRec.measure;
        perturbedQuery.eta = bRec.eta;
        Color3f result = m_nested->sample(perturbedQuery, sample);
        if (!result.isZero()) {
            bRec.wo = its.toLocal(perturbed.toWorld(perturbedQuery.wo));
			bRec.eta = perturbedQuery.eta;
			if (Frame::cosTheta(bRec.wo) * Frame::cosTheta(perturbedQuery.wo) <= 0)
				return Color3f(0.0f);
        }
        return result;
    }

    // https://arxiv.org/abs/1705.01263
    Frame getFrame(const Intersection &its, Vector3f n, Vector3f wi) const {

        // 1. Naive implementation
        Frame result;
        result.n = its.shFrame.toWorld(n).normalized();
        result.s = (its.dpdu - result.n * result.n.dot(its.dpdu)).normalized();
        result.t = result.n.cross(result.s).normalized();

        return result;

        // 2. Normalmap switch (Iray way)
        // Frame result;
		// Vector3f r = 2.0f * n.dot(wi) * n - wi;
		// if (Frame::cosTheta(r) <= 0) {
		// 	// pull up normal (see p. 46 https://arxiv.org/abs/1705.01263)
		// 	r = (r - r.dot(wi) * 1.01f * wi).normalized();
		// 	n = (wi + r).normalized();
		// }

		// Frame frame = its.shFrame;
		// result.n = frame.toWorld(n).normalized();
		// result.s = (its.dpdu - result.n * result.n.dot(its.dpdu)).normalized();
		// result.t = result.n.cross(result.s).normalized();

		// return result;
    }

    void addChild(Object *obj) override {
        switch (obj->getClassType()) {
            case ETexture:
                m_normalMap = static_cast<Texture<Color3f>*>(obj);
                break;
            case EBSDF:
                m_nested = static_cast<BSDF*>(obj);
                break;
            default:
                throw Exception("addChild is not supported other than normal maps and nested BSDF");
        }
    }

    EClassType getClassType() const { return EBSDF; }

    std::string toString() const {
        return fmt::format("NormalMap[]");
    }

private:
    Texture<Color3f>* m_normalMap=nullptr;
    BSDF* m_nested = nullptr;
};

// // Eric Heitz's 2017 - Microfacet-based Normal Mapping for Robust Monte Carlo Path Tracing
// class NormalMapMicrofacet : public BSDF {
// public:
//     NormalMapMicrofacet(const PropertyList &propList) {
//         m_albedo = propList.getColor("albedo", Color3f(0.5f));

//     }

//     ~NormalMapMicrofacet() {
//         if(!m_normalMap) delete m_normalMap;
//         if(!m_nested) delete m_nested;
//     }

//     static float pdot(Vector3f a, Vector3f b) {
//         return std::max(0.f, a.dot(b));
//     }

//     static Vector3f wt(Vector3f wp) {
//         return Vector3f(-wp.x(), -wp.y(), 0.f).normalized();
//     }

//     static float G1(Vector3f wp, Vector3f w) {
//         return std::min(1.f, std::max(0.f, Frame::cosTheta(w)) * std::max(0.f, Frame::cosTheta(wp))
//             / (pdot(w, wp) + pdot(w, wt(wp)) * Frame::sinTheta(wp))
//         );
//     }

//     static float lambda_p(Vector3f wp, Vector3f wi) {
//         float i_dot_p = pdot(wp, wi);
//         return i_dot_p / (i_dot_p + pdot(wt(wp), wi) * Frame::sinTheta(wp));
//     }

// 	Color3f eval(const BSDFQueryRecord &bRec) const {
// 		if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
// 			return Color3f(0.0f);

// 		Vector3f wp;
//         Color3f rgb = m_normalMap->eval(bRec.its.uv);
//         wp = Vector3f(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1).normalized();

// 		if (Frame::cosTheta(wp) <= 0 || (std::abs(wp.x()) < 1e-6 && std::abs(wp.y()) < 1e-6))
// 			return m_nested->eval(bRec);

// 		Frame frame_wp(bRec.its.toWorld(wp));
// 		Intersection perturbed_wp(bRec.its);
// 		perturbed_wp.geoFrame = frame_wp;
// 		perturbed_wp.shFrame = frame_wp;
// 		perturbed_wp.wi = perturbed_wp.toLocal(bRec.its.toWorld(bRec.wi));

// 		Vector3f wo_wp = perturbed_wp.toLocal(bRec.its.toWorld(bRec.wo));
// 		Vector3f wt_ = bRec.its.toWorld(wt(wp));
// 		Vector3f wo = bRec.its.toWorld(bRec.wo);
// 		Vector3f wi = bRec.its.toWorld(bRec.wi);
// 		Vector3f wo_reflected = (wo - 2.0f * wo.dot(wt_) * wt_).normalized();
// 		float notShadowedWpMirror = 1.f - G1(wp, bRec.its.toLocal(wo_reflected));
// 		wo_reflected = perturbed_wp.toLocal(wo_reflected);
// 		float shadowing = G1(wp, bRec.wo);

// 		Color3f value(0.f);

// 		float lambda_p_ = lambda_p(wp, bRec.wi);

// 		// i -> p -> o
// 		BSDFQueryRecord evalSingleP(perturbed_wp, perturbed_wp.wi, wo_wp);
// 		value += m_nested->eval(evalSingleP) * (lambda_p_ * shadowing);

// 		// i -> p -> t -> o
// 		if (wo.dot(wt_) > 0) {
// 			BSDFQueryRecord evalDoubleP(perturbed_wp, perturbed_wp.wi, wo_reflected);
// 			value += m_nested->eval(evalDoubleP) * (lambda_p_ * notShadowedWpMirror * shadowing);
// 		}

// 		// i -> t -> p -> o
// 		if (wi.dot(wt_) > 0) {
// 			Vector3f wi_reflected = (wi - 2.0f * wi.dot(wt_) * wt_).normalized();
// 			BSDFQueryRecord evalDoubleT(perturbed_wp, perturbed_wp.toLocal(wi_reflected), wo_wp);
// 			value += m_nested->eval(evalDoubleT) * ((1.f - lambda_p_) * shadowing);
// 		}

// 		return value;
//     }

// 	float pdf(const BSDFQueryRecord &bRec) const {
// 		if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
// 			return 0.0f;

// 		Vector3f wp;
//         Color3f rgb = m_normalMap->eval(bRec.its.uv);
//         wp = Vector3f(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1).normalized();

// 		if (Frame::cosTheta(wp) <= 0 || (std::abs(wp.x()) < 1e-6 && std::abs(wp.y()) < 1e-6)) {
// 			return m_nested->pdf(bRec);
// 		}

// 		float probability_wp = lambda_p(wp, bRec.wi);
// 		Frame frameWp(bRec.its.toWorld(wp));
// 		Intersection perturbed_wp(bRec.its);
// 		perturbed_wp.geoFrame = frameWp;
// 		perturbed_wp.shFrame = frameWp;
// 		Vector3f wi = bRec.its.toWorld(bRec.wi);
// 		Vector3f wo = bRec.its.toWorld(bRec.wo);
// 		Vector3f wt_ = bRec.its.toWorld(wt(wp));
// 		float pdf = 0.f;
// 		if (probability_wp > 0.f) {
// 			BSDFQueryRecord queryWp(perturbed_wp, perturbed_wp.toLocal(wi), perturbed_wp.toLocal(wo));
// 			pdf += probability_wp * m_nested->pdf(queryWp) * G1(wp, bRec.wo);

// 			if (wo.dot(wt_) > 1e-6) {
// 				Vector3f woReflected = (wo - 2.0f * wo.dot(wt_) * wt_).normalized();
// 				BSDFQueryRecord queryWpt(perturbed_wp, perturbed_wp.toLocal(wi), perturbed_wp.toLocal(woReflected));
// 				pdf += probability_wp * m_nested->pdf(queryWpt)
// 					* (1.f - G1(wp, bRec.its.toLocal(woReflected)));
// 			}
// 		}

// 		if (probability_wp < 1.f && wi.dot(wt_) > 1e-6) {
// 			Vector3f wiReflected = (wi - 2.0f * wi.dot(wt_) * wt_).normalized();
// 			BSDFQueryRecord queryWtp(perturbed_wp, perturbed_wp.toLocal(wiReflected), perturbed_wp.toLocal(wo));
// 			pdf += (1.f - probability_wp) * m_nested->pdf(queryWtp);
// 		}

// 		return pdf;
// 	}

// 	Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const {
// 		if (Frame::cosTheta(bRec.wi) <= 0)
// 			return Color3f(0.0f);

// 		bRec.eta = 1.0f;

// 		Vector3f wp;
//         Color3f rgb = m_normalMap->eval(bRec.its.uv);
//         wp = Vector3f(2*rgb.r()-1, 2*rgb.g()-1, 2*rgb.b()-1).normalized();

// 		if (Frame::cosTheta(wp) <= 0 || (std::abs(wp.x()) < 1e-6 && std::abs(wp.y()) < 1e-6)) {
// 			return m_nested->sample(bRec, sample);
// 		}

// 		Frame frame_wp(bRec.its.toWorld(wp));
// 		Intersection perturbed_wp(bRec.its);
// 		perturbed_wp.geoFrame = frame_wp;
// 		perturbed_wp.shFrame = frame_wp;

// 		Vector3f wt_ = bRec.its.toWorld(wt(wp));

// 		Vector3f wr = -bRec.its.toWorld(bRec.wi);
// 		Color3f energy(1.f);
// 		if (sample.x() < lambda_p(wp, bRec.wi)) {
// 			// sample on wp
// 			perturbed_wp.wi = -perturbed_wp.toLocal(wr);
// 			BSDFQueryRecord query_wp(perturbed_wp, Vector3f(1.f));
// 			energy *= m_nested->sample(query_wp, sample);
// 			// did sampling fail?
// 			if (energy.isZero()) return energy;
// 			wr = perturbed_wp.toWorld(query_wp.wo);
// 			float G1_ = G1(wp, bRec.its.toLocal(wr));

// 			// is the sampled direction shadowed?
// 			if (sample.x() > G1_) {
// 				// reflect on wt
// 				wr = (wr + 2.0f * wt_.dot(-wr) * wt_).normalized();
// 				energy *= G1(wp, bRec.its.toLocal(wr));
// 			}
// 		} else {
// 			// do one reflection if we start at wt
// 			wr = (wr + 2.0f * wt_.dot(-wr) * wt_).normalized();
// 			// sample on wp
// 			perturbed_wp.wi = -perturbed_wp.toLocal(wr);
// 			BSDFQueryRecord query_wp(perturbed_wp, NULL);
// 			energy *= m_nested->sample(query_wp, sample);
// 			// did sampling fail?
// 			if (energy.isZero()) return energy;
// 			wr = perturbed_wp.toWorld(query_wp.wo);
// 			energy *= G1(wp, bRec.its.toLocal(wr));
// 		}
// 		bRec.wo = bRec.its.toLocal(wr);
// 		if (Frame::cosTheta(bRec.wo) <= 0.f) return Color3f(0.f);
// 		return energy;
// 	}

//     void addChild(Object *obj) override {
//         switch (obj->getClassType()) {
//             case ETexture:
//                 m_normalMap = static_cast<Texture<Color3f>*>(obj);
//                 break;
//             case EBSDF:
//                 m_nested = static_cast<BSDF*>(obj);
//                 // m_albedo = Color3f(0.f, 1.0f, 0.f); // this line is for debugging
//                 m_albedo = m_nested->getColor();
//                 break;
//             default:
//                 throw Exception("addChild is not supported other than normal maps and nested BSDF");
//         }
//     }

//     EClassType getClassType() const { return EBSDF; }

//     std::string toString() const {
//         return fmt::format("NormalMapMircofacet[]");
//     }

// private:
//     Color3f m_albedo;
//     Texture<Color3f>* m_normalMap = nullptr;
//     BSDF* m_nested = nullptr;

// };

class GGX : public BSDF {
public:
    GGX(const PropertyList &propList) {
        m_roughness = propList.getFloat("roughness", 0.5f);
        m_anisotropy = propList.getFloat("anisotropy", 0.f);
    }

    ~GGX() {
        if(!m_albedo) delete m_albedo;
    }

    Color3f eval(const BSDFQueryRecord &bRec) const {
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);
        Color3f F;
        Color3f albedo = m_albedo->eval(bRec.uv);
        return evaluateGGXSmithBRDF(bRec.wi, bRec.wo, albedo, m_roughness, m_anisotropy, F) * Frame::cosTheta(bRec.wo);
    }

    float pdf(const BSDFQueryRecord &bRec) const {
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;

        auto H = (bRec.wi + bRec.wo).normalized();
        auto alpha = roughnessToAlpha(m_roughness, m_anisotropy);
        auto denom = 4.0f * bRec.wi.dot(H);
        return computeGGXSmithPDF(bRec.wi, H, alpha) / denom;
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);
        Color3f albedo = m_albedo->eval(bRec.uv);
        Color3f color = sampleGGXSmithBRDF(bRec.wi, albedo, m_roughness, m_anisotropy, sample, bRec.wo, bRec.pdf);

        if (Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        return color * Frame::cosTheta(bRec.wo) / bRec.pdf;

    }

    void addChild(Object *obj) override {
        switch (obj->getClassType()) {
            case ETexture:
                m_albedo = static_cast<Texture<Color3f>*>(obj);
                break;
            default:
                throw Exception("addChild is not supported other than albedi maps");
        }
    }

    std::string toString() const {
        return "GGX[]";
    }

private:
    Texture<Color3f>* m_albedo=nullptr;
    float m_roughness;
    float m_anisotropy;
};

class RoughConductor : public BSDF {
public:
    RoughConductor(const PropertyList &propList) {
        /* RMS surface roughness */
        auto roughness = propList.getFloat("alpha", 0.1f);
        // Specify a minimum alpha value, as the GGX NDF does not support a zero alpha.
        float MIN_ALPHA = 0.001f;
        // Square the roughness value and combine with anisotropy to produce X and Y alpha values.
        m_alpha = std::max(MIN_ALPHA, sqr(roughness));

        /* Eta and K (default: gold) */
        std::string material = propList.getString("material","Au");
        if (material == "Au") {
            m_eta = Color3f(0.1431189557f, 0.3749570432f, 1.4424785571f);
            m_k = Color3f(3.9831604247f, 2.3857207478f, 1.6032152899f);
        } else if (material == "Cu") {
            m_eta = Color3f(0.2004376970f, 0.9240334304f, 1.1022119527f);
            m_k = Color3f(3.9129485033f, 2.4528477015f, 2.1421879552f);
        } else if (material == "Cr") {
            m_eta = Color3f(4.3696828663f, 2.9167024892f, 1.6547005413f);
            m_k = Color3f(5.2064337956f, 4.2313645277f, 3.7549467933f);
        }
    }

    /// Evaluate the fresnel for conductor
    Color3f fresnelCond(float cosThetaI, Color3f eta, Color3f k) const {
        Color3f tmp_f = pow(eta,2) + pow(k,2);
        Color3f tmp = tmp_f * pow(cosThetaI,2);
        Color3f Rparl2 = (tmp - (2.f * eta * cosThetaI) + 1) /
                    (tmp + (2.f * eta * cosThetaI) + 1);
        Color3f Rperp2 = (tmp_f - (2.f * eta * cosThetaI) + pow(cosThetaI,2)) /
                    (tmp_f + (2.f * eta * cosThetaI) + pow(cosThetaI,2));
        return (Rparl2 + Rperp2) / 2.0f;
    }

    /// Evaluate the microfacet normal distribution D
    float evalBeckmann(const Normal3f &m) const {
        float temp = Frame::tanTheta(m) / m_alpha,
              ct = Frame::cosTheta(m), ct2 = ct*ct;

        return std::exp(-temp*temp)
            / (M_PI * m_alpha * m_alpha * ct2 * ct2);
    }

    /// Evaluate Smith's shadowing-masking function G1
    float smithBeckmannG1(const Vector3f &v, const Normal3f &m) const {
        float tanTheta = Frame::tanTheta(v);

        /* Perpendicular incidence -- no shadowing/masking */
        if (tanTheta == 0.0f)
            return 1.0f;

        /* Can't see the back side from the front and vice versa */
        if (m.dot(v) * Frame::cosTheta(v) <= 0)
            return 0.0f;

        float a = 1.0f / (m_alpha * tanTheta);
        if (a >= 1.6f)
            return 1.0f;
        float a2 = a * a;

        /* Use a fast and accurate (<0.35% rel. error) rational
           approximation to the shadowing-masking function */
        return (3.535f * a + 2.181f * a2)
             / (1.0f + 2.276f * a + 2.577f * a2);
    }

    /// Evaluate the BRDF for the given pair of directions
    virtual Color3f eval(const BSDFQueryRecord &bRec) const override {
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);
        Vector3f wh = (bRec.wi + bRec.wo).normalized();

        Color3f F = fresnelCond(wh.dot(bRec.wo), m_eta, m_k);
        float D = evalBeckmann(wh);
        float G = smithBeckmannG1(bRec.wi, wh) * smithBeckmannG1(bRec.wo,wh);

        return D * F * G / (4.f * Frame::cosTheta(bRec.wi));
    }

    /// Evaluate the sampling density of \ref sample() wrt. solid angles
    virtual float pdf(const BSDFQueryRecord &bRec) const override {
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;
        Vector3f wh = (bRec.wi + bRec.wo).normalized();
        float D = evalBeckmann(wh);
        float Jh = 1.f / (4.f * wh.dot(bRec.wo));
        return D * Frame::cosTheta(wh) * Jh;
    }

    /// Sample the BRDF
    virtual Color3f sample(BSDFQueryRecord &bRec, const Point2f &_sample) const override {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);

        Point2f sample = _sample;
        Vector3f wh = Warp::squareToBeckmann(sample, m_alpha);
        bRec.wo = reflect(bRec.wi, wh).normalized();
        if (Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        return eval(bRec) / pdf(bRec);
    }

    virtual std::string toString() const override {
        return fmt::format(
            "RoughConductor[\n"
            "  alpha = %f,\n"
            "  intIOR = %f,\n"
            "  extIOR = %f,\n"
            "]",
            m_alpha,
            m_eta,
            m_k
        );
    }
private:
    float m_alpha;
    Color3f m_eta, m_k;
};

class RoughPlastic : public BSDF {
public:
    RoughPlastic(const PropertyList &propList) {
        /* RMS surface roughness */
        auto roughness = propList.getFloat("alpha", 0.1f);
        // Specify a minimum alpha value, as the GGX NDF does not support a zero alpha.
        float MIN_ALPHA = 0.001f;
        // Square the roughness value and combine with anisotropy to produce X and Y alpha values.
        m_alpha = std::max(MIN_ALPHA, sqr(roughness));

        /* Interior IOR (default: BK7 borosilicate optical glass) */
        m_intIOR = propList.getFloat("intIOR", 1.5046f);

        /* Exterior IOR (default: air) */
        m_extIOR = propList.getFloat("extIOR", 1.000277f);

        /* Albedo of the diffuse base material (a.k.a "kd") */
        m_kd = propList.getColor("kd", Color3f(0.5f));

        /* To ensure energy conservation, we must scale the
           specular component by 1-kd.
           While that is not a particularly realistic model of what
           happens in reality, this will greatly simplify the
           implementation. Please see the course staff if you're
           interested in implementing a more realistic version
           of this BRDF. */
        m_ks = 1 - m_kd.maxCoeff();
    }

    /// Evaluate the microfacet normal distribution D
    float evalBeckmann(const Normal3f &m) const {
        float temp = Frame::tanTheta(m) / m_alpha,
              ct = Frame::cosTheta(m), ct2 = ct*ct;

        return std::exp(-temp*temp)
            / (M_PI * m_alpha * m_alpha * ct2 * ct2);
    }

    /// Evaluate Smith's shadowing-masking function G1
    float smithBeckmannG1(const Vector3f &v, const Normal3f &m) const {
        float tanTheta = Frame::tanTheta(v);

        /* Perpendicular inci		return 0.0f;
         * dence -- no shadowing/masking */
        if (tanTheta == 0.0f)
            return 1.0f;

        /* Can't see the back side from the front and vice versa */
        if (m.dot(v) * Frame::cosTheta(v) <= 0)
            return 0.0f;

        float a = 1.0f / (m_alpha * tanTheta);
        if (a >= 1.6f)
            return 1.0f;
        float a2 = a * a;

        /* Use a fast and accurate (<0.35% rel. error) rational
           approximation to the shadowing-masking function */
        return (3.535f * a + 2.181f * a2)
             / (1.0f + 2.276f * a + 2.577f * a2);
    }

    /// Evaluate the BRDF for the given pair of directions
    virtual Color3f eval(const BSDFQueryRecord &bRec) const override {
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        Vector3f wh = (bRec.wi + bRec.wo).normalized();
		float D = evalBeckmann(wh);
		float F = fresnel((wh.dot(bRec.wo)), m_extIOR, m_intIOR);
		float G = (smithBeckmannG1(bRec.wo, wh) * smithBeckmannG1(bRec.wi, wh));

		return m_kd * INV_PI * Frame::cosTheta(bRec.wo) + m_ks * (D * F * G)
			/ (4.f * Frame::cosTheta(bRec.wi));

    }

    /// Evaluate the sampling density of \ref sample() wrt. solid angles
    virtual float pdf(const BSDFQueryRecord &bRec) const override {
	    if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;

        Vector3f wh = (bRec.wi + bRec.wo).normalized();
		float D = evalBeckmann(wh);
		float Jh = 1.f / (4.f * abs(wh.dot(bRec.wo)));

		return m_ks * D * Frame::cosTheta(wh) * Jh + (1- m_ks) * Frame::cosTheta(bRec.wo) * INV_PI;
		// return m_ks * Warp::squareToBeckmannPdf(wh, m_alpha) * Jh + (1 - m_ks) * Warp::squareToCosineHemispherePdf(bRec.wo);
	}

    /// Sample the BRDF
    virtual Color3f sample(BSDFQueryRecord &bRec, const Point2f &_sample) const override {
		if (Frame::cosTheta(bRec.wi) <= 0)
			return Color3f(0.0f);

		if (_sample.x() < m_ks) {
			Point2f sample(_sample.x() / m_ks, _sample.y());
			Vector3f wh = Warp::squareToBeckmann(sample, m_alpha);
			bRec.wo = ((2.f * wh.dot(bRec.wi) * wh) - bRec.wi).normalized();
		} else {
			Point2f sample((_sample.x() - m_ks) / (1 - m_ks), _sample.y());
			bRec.wo = Warp::squareToCosineHemisphere(sample);
		}

		if (Frame::cosTheta(bRec.wo) <= 0)
			return Color3f(0.0f);

		// return eval(bRec) / pdf(bRec) * Frame::cosTheta(bRec.wo);
		return eval(bRec) / pdf(bRec);
	}

    virtual std::string toString() const override {
        return fmt::format(
            "RoughPlastic[\n"
            "  alpha = %f,\n"
            "  intIOR = %f,\n"
            "  extIOR = %f,\n"
            "  kd = %s,\n"
            "  ks = %f\n"
            "]",
            m_alpha,
            m_intIOR,
            m_extIOR,
            m_kd.toString(),
            m_ks
        );
    }
private:
    float m_alpha;
    float m_intIOR, m_extIOR;
    float m_ks;
    Color3f m_kd;
};

/// TODO: Rough Dielectric

/// kazen innercircle standard surface (kiss)
/* References:
* [1] [Physically Based Shading at Disney] https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf
* [2] [Extending the Disney BRDF to a BSDF with Integrated Subsurface Scattering] https://blog.selfshadow.com/publications/s2015-shading-course/burley/s2015_pbs_disney_bsdf_notes.pdf
* [3] [Simon Kallweit's project report] http://simon-kallweit.me/rendercompo2015/report/
* [4] [Microfacet Models for Refraction through Rough Surfaces] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
* [5] [Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs] https://jcgt.org/published/0003/02/03/paper.pdf
* [6] [Sampling the GGX Distribution of Visible Normals] https://jcgt.org/published/0007/04/01/paper.pdf
*/
class KazenStandardSurface : public BSDF {
public:
    KazenStandardSurface(const PropertyList &proplist) {
        m_anisotropy = proplist.getFloat("anisotropy", 0.0f);
        m_specular = proplist.getFloat("specular", 0.5f);
        m_specularTint = proplist.getFloat("specularTint", 0.5f);
        m_clearcoat = proplist.getFloat("clearcoat", 0.0f);
        m_clearcoatRoughness = proplist.getFloat("clearcoatRoughness", 0.5f);
        m_sheen = proplist.getFloat("sheen", 0.0f);
        m_sheenTint = proplist.getFloat("sheenTint", 0.5);
    }

    ~KazenStandardSurface() {
        if(!m_baseColor) delete m_baseColor;
        if(!m_metallic) delete m_metallic;
        if(!m_roughness) delete m_roughness;
    }

    float schlickWeight(float x) const {
        x = math::clamp(1.f - x, 0.f, 1.f);
        auto x2 = x * x;
        return x2 * x2 * x;
    }

    Color3f lerp(const Color3f& c1, const Color3f& c2, float t) const {
        return (1.f - t) * c1 + t * c2;
    }

    float ior(float specular) const {
        return 2.f/(1.f - std::sqrt(0.08*specular)) - 1.f;
    };

    Color3f eval(const BSDFQueryRecord &bRec) const {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return Color3f(0.0f);

        Vector3f V = bRec.wi;
        Vector3f L = bRec.wo;
        Vector3f H = (V + L).normalized();

        // color
        Color3f Cdlin = m_baseColor->eval(bRec.uv);
        auto metallic = m_metallic->eval(bRec.uv).r(); // TODO: single channel?
        auto roughness = m_roughness->eval(bRec.uv).r();
        float Cdlum = Cdlin.getLuminance();
        Color3f Ctint = Cdlum > 0.f ? Cdlin/Cdlum : Color3f(1.f);
		Color3f Ctintmix = 0.08f * m_specular * lerp(Color3f(1.f), Ctint, m_specularTint);
		Color3f Cspec0 = lerp(Ctintmix, Cdlin, metallic);

        // diffuse
        float FL = schlickWeight(L.z());
        float FV = schlickWeight(V.z());
        float FH = schlickWeight(L.dot(H));

        float cosThetaD = V.dot(H);
        float FD90 = 0.5f * 2 * roughness * cosThetaD * cosThetaD;

        float Lambert = (1.f - 0.5f*FL) * (1.f - 0.5f*FV);
        float RR = 2.f * roughness * cosThetaD * cosThetaD;
        float retro_reflection = RR * (FL + FV + FL * FV * (RR - 1.f));

        // sheen
        Color3f Csheen = lerp(Color3f(1.f), Ctint, m_sheenTint);
        Color3f Fsheen = FH * m_sheen * Csheen;

        // specular
        Color3f F;
        Color3f specTerm = evaluateGGXSmithBRDF(V, L, Cspec0, roughness, m_anisotropy, F);

        // clearcoat(ior = 1.5 -> F0 = 0.04)
        float clearcoatRoughness = math::lerp(m_clearcoatRoughness, .01f, .3f);
        Color3f clearcoatTerm = 0.25f * m_clearcoat * evaluateGGXSmithBRDF(V, L, 0.04f, clearcoatRoughness, m_anisotropy, F);

        return ((1.f-metallic)*(Cdlin * INV_PI * (Lambert + retro_reflection) +  Fsheen) +
                (specTerm + clearcoatTerm))* Frame::cosTheta(bRec.wo);

    }

    float pdf(const BSDFQueryRecord &bRec) const override {
        /* This is a smooth BRDF -- return zero if the measure
           is wrong, or when queried for illumination on the backside */
        if (Frame::cosTheta(bRec.wi) <= 0 || Frame::cosTheta(bRec.wo) <= 0)
            return 0.0f;

        // weight: reference - http://simon-kallweit.me/rendercompo2015/report/
        auto metallic = m_metallic->eval(bRec.uv).r();
        float diffuse = (1.f - metallic) * 0.5f;
        // float diffuse =  m_baseColor->eval(bRec.uv).maxCoeff();
        float GTR2 = 1.f / (1.f + m_clearcoat);

        auto H = (bRec.wi + bRec.wo).normalized();
        auto jacobian = 4.0f * bRec.wi.dot(H);

        // specular
        auto roughness = m_roughness->eval(bRec.uv).r();
        auto alpha = roughnessToAlpha(roughness, m_anisotropy);
        auto specPdf = computeGGXSmithPDF(bRec.wi, H, alpha) / jacobian;

        // clearcoat
        auto coatalpha = roughnessToAlpha(math::lerp(m_clearcoatRoughness, .01f, .3f), 0.f);
        float clearcoatPdf = computeGGXSmithPDF(bRec.wi, H, coatalpha) / jacobian;

        return diffuse * INV_PI * Frame::cosTheta(bRec.wo) +
                (1.f-diffuse) * (GTR2*specPdf + (1.f-GTR2)*clearcoatPdf);
    }

    Color3f sample(BSDFQueryRecord &bRec, const Point2f &_sample) const override {
        if (Frame::cosTheta(bRec.wi) <= 0)
            return Color3f(0.0f);

        bRec.measure = ESolidAngle;
        /* Relative index of refraction: no change */
        bRec.eta = 1.0f;

        auto metallic = m_metallic->eval(bRec.uv).r();
        float diffuse = (1.f - metallic) * 0.5f;
        // float diffuse = m_baseColor->eval(bRec.uv).maxCoeff();

		if (_sample.x() < diffuse) {
            auto sample = Point2f(_sample.x()/diffuse, _sample.y());
            bRec.wo = Warp::squareToCosineHemisphere(sample);
		} else {
			auto sample = Point2f((_sample.x()-diffuse) / (1.f-diffuse), _sample.y());
            float GTR2 = 1.f / (1.f + m_clearcoat);

            Vector3f H;
            Point2f sample1;
            bool flip = bRec.wi.z() <= 0.f;
            if (sample.x() < GTR2) {
                sample1 = Point2f(sample.x() / GTR2, sample.y());
                auto roughness = m_roughness->eval(bRec.uv).r();
                Vector2f alpha = roughnessToAlpha(roughness, m_anisotropy);
                H = sampleGGXSmithVNDF(flip ? -bRec.wi : bRec.wi, alpha, sample1);
            } else {
                sample1 = Point2f((sample.x()-GTR2) / (1.f-GTR2), sample.y());
                Vector2f alpha = roughnessToAlpha(math::lerp(m_clearcoatRoughness, 0.01f, .3f), 0.f);
                H = sampleGGXSmithVNDF(flip ? -bRec.wi : bRec.wi, alpha, sample1);
            }
            H = flip ? -H : H;
            // Reflect the view direction across the microfacet normal to get the sample direction.
            bRec.wo = reflect(bRec.wi, H).normalized();
		}

        auto invalid = [&]() {
            return std::isnan(bRec.wo.x()) || std::isnan(bRec.wo.y()) || std::isnan(bRec.wo.z());
        };

        if (Frame::cosTheta(bRec.wo) <= 0 || pdf(bRec) <= Epsilon || invalid())
            return Color3f(0.f);

		// return eval(bRec) / pdf(bRec) * Frame::cosTheta(bRec.wo);
        return eval(bRec) / pdf(bRec);
    }

    void addChild(Object *obj) override {
        switch (obj->getClassType()) {
            case ETexture:
                if( obj->getId() == "baseColor" ) {
                    if (m_baseColor)
                        throw Exception("There is already an baseColor defined!");
                    m_baseColor = static_cast<Texture<Color3f> *>(obj);
                }
                else if ( obj->getId() == "metallic" ) {
                    if (m_metallic)
                        throw Exception("There is already an metallic defined!");
                    m_metallic = static_cast<Texture<Color3f> *>(obj);
                }
                else if ( obj->getId() == "roughness" ) {
                    if (m_roughness)
                        throw Exception("There is already an roughness defined!");
                    m_roughness = static_cast<Texture<Color3f> *>(obj);
                }
                break;
            default:
                throw Exception("addChild is not supported other than baseColor maps");
        }
    }

    std::string toString() const {
        return fmt::format(
            "KazenStandardSurface"
        );
    }

private:
    Texture<Color3f>* m_baseColor=nullptr;
    Texture<Color3f>* m_roughness=nullptr;
    Texture<Color3f>* m_metallic=nullptr;
    float m_anisotropy;
    float m_specular;
    float m_specularTint;
    float m_sheen;
    float m_sheenTint;
    float m_clearcoat;
    float m_clearcoatRoughness;
};

KAZEN_REGISTER_CLASS(Diffuse, "diffuse");
KAZEN_REGISTER_CLASS(Dielectric, "dielectric");
KAZEN_REGISTER_CLASS(Mirror, "mirror");
KAZEN_REGISTER_CLASS(Lambertian, "lambertian");
KAZEN_REGISTER_CLASS(NormalMap, "normalmap");
// KAZEN_REGISTER_CLASS(NormalMapMicrofacet, "normalmap_mircofacet");
KAZEN_REGISTER_CLASS(GGX, "ggx");
KAZEN_REGISTER_CLASS(RoughConductor, "roughconductor");
KAZEN_REGISTER_CLASS(RoughPlastic, "roughplastic");
// KAZEN_REGISTER_CLASS(RoughDieletric, "roughdieletric");
KAZEN_REGISTER_CLASS(KazenStandardSurface, "kazenstandard");
NAMESPACE_END(kazen)


#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>

#include <CGAL/Surface_mesh.h>

#include <CGAL/boost/graph/copy_face_graph.h>
#include <CGAL/boost/graph/Dual.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/centroid.h>
#include <CGAL/Polygon_mesh_processing/triangulate_faces.h>

#include <boost/graph/filtered_graph.hpp>

#include <fstream>

typedef CGAL::Exact_predicates_inexact_constructions_kernel Epic;
typedef CGAL::Exact_predicates_exact_constructions_kernel Epec;

template <typename K>
bool
test_triangulate_faces()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Point>          Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/cube_quad.off"));

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  bool success = CGAL::Polygon_mesh_processing::triangulate_faces(mesh);
  assert(CGAL::is_triangle_mesh(mesh));

  return success;
}

template <typename K>
bool
test_triangulate_faces_with_named_parameters()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Epic::Point_3>      Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/cube_quad.off"));

  typedef Surface_mesh::Property_map<Surface_mesh::Vertex_index, Point>  VertexPointMap;
  VertexPointMap custom_vpm = mesh.add_property_map<Surface_mesh::Vertex_index, Point>("exact_vpm", Point()).first;

  typedef typename CGAL::property_map_selector<Surface_mesh, boost::vertex_point_t>::const_type CVPM;
  CVPM cvpm = CGAL::get_const_property_map(CGAL::vertex_point, mesh);

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  CGAL::Cartesian_converter<Epic, K> to_custom;

  for(boost::graph_traits<Surface_mesh>::vertex_descriptor vd : vertices(mesh))
  {
    put(custom_vpm, vd, to_custom(get(cvpm, vd)));
  }

  bool success = CGAL::Polygon_mesh_processing::triangulate_faces(mesh,
                                                                  CGAL::parameters::vertex_point_map(custom_vpm)
                                                                                .geom_traits(K()));
  assert(CGAL::is_triangle_mesh(mesh));

  return success;
}

template <typename K>
bool
test_triangulate_face_range()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Point>          Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/cube_quad.off"));

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  bool success = CGAL::Polygon_mesh_processing::triangulate_faces(faces(mesh), mesh);
  assert(CGAL::is_triangle_mesh(mesh));

  // For compilation
  CGAL::Polygon_mesh_processing::triangulate_faces(faces(mesh), mesh,
                                                   CGAL::parameters::geom_traits(K()));

  return success;
}

template <typename K>
bool
test_triangulate_face()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Point>          Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/cube_quad.off"));

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  unsigned int nb = 0;
  for(typename boost::graph_traits<Surface_mesh>::face_descriptor fit : faces(mesh))
  {
    if (nb > 4)
      break;
    else if (next(next(halfedge(fit, mesh), mesh), mesh)
             !=   prev(halfedge(fit, mesh), mesh))
    {
      if(CGAL::Polygon_mesh_processing::triangulate_face(fit, mesh))
        ++nb;
      else
        assert(false);
    }
  }

  return true;
}

template <typename K>
bool
test_triangulate_triangle_face()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Point>          Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/reference_tetrahedron.off"));

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  for(typename boost::graph_traits<Surface_mesh>::face_descriptor fit : faces(mesh))
  {
    if(!CGAL::Polygon_mesh_processing::triangulate_face(fit, mesh, CGAL::parameters::geom_traits(K())))
      assert(false);
  }
  return true;
}

// todo: add this in Dual.h
template <class SurfaceMesh, class Point, class Primal_map>
struct Dual_vpm
{
  typedef typename boost::graph_traits<SurfaceMesh>::face_descriptor key_type;
  typedef Point reference;
  typedef Point value_type;
  typedef boost::readable_property_map_tag category;

  typedef typename boost::graph_traits<SurfaceMesh>::vertex_descriptor vertex_descriptor;

  Dual_vpm(const SurfaceMesh& primal,
           const Primal_map& primal_map)
    : primal_(primal)
    , primal_map_(primal_map) {}

  const Primal_map& primal_map() const
  {
    return primal_map_;
  }

  const SurfaceMesh& primal() const
  {
    return primal_;
  }

  friend
  Point get(Dual_vpm& map, key_type& f)
  {
    std::vector<Point> face_points;

    for(vertex_descriptor v :
                  CGAL::vertices_around_face(halfedge(f, map.primal()), map.primal()))
    {
      face_points.push_back( get(map.primal_map(), v) );
    }

    // temp extra copy
    Point centroid = CGAL::centroid(face_points.begin(), face_points.end(),
                                    CGAL::Dimension_tag<0>());

    return centroid;
  }

  const SurfaceMesh& primal_;
  Primal_map primal_map_;
};

template <typename K>
bool
test_dual_with_various_faces()
{
  typedef typename K::Point_3                    Point;
  typedef CGAL::Surface_mesh<Point>          Surface_mesh;

  Surface_mesh mesh;
  std::ifstream input(CGAL::data_file_path("meshes/elephant.off"));

  if (!input || !(input >> mesh) || mesh.is_empty())
  {
    std::cerr << "Not a valid off file." << std::endl;
    return false;
  }

  typedef typename boost::property_map<Surface_mesh, boost::vertex_point_t>::type Pmap;
  Pmap vpmap = get_property_map(boost::vertex_point, mesh);

  CGAL::Dual<Surface_mesh> dual(mesh);
  // copy dual to a sm
  Surface_mesh sm_dual;
  CGAL::copy_face_graph(dual, sm_dual,
                        CGAL::parameters::vertex_point_map(
                          Dual_vpm<Surface_mesh, Point, Pmap>(mesh, vpmap)));

  for(typename boost::graph_traits<Surface_mesh>::face_descriptor fit : faces(sm_dual))
  {
    if(!CGAL::Polygon_mesh_processing::triangulate_face(fit, sm_dual))
      assert(false);
  }
  return true;
}

int main()
{
  assert(test_triangulate_faces<Epic>());
  assert(test_triangulate_faces_with_named_parameters<Epic>());
  assert(test_triangulate_face_range<Epic>());
  assert(test_triangulate_face<Epic>());
  assert(test_triangulate_triangle_face<Epic>());
  assert(test_dual_with_various_faces<Epic>());

  assert(test_triangulate_faces<Epec>());
  assert(test_triangulate_faces_with_named_parameters<Epec>());
  assert(test_triangulate_face_range<Epec>());
  assert(test_triangulate_face<Epec>());
  assert(test_triangulate_triangle_face<Epec>());
  assert(test_dual_with_various_faces<Epec>());

  return EXIT_SUCCESS;
}


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_PACK_CAST_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_PACK_CAST_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = ::boost::dispatch;
  namespace bs = ::boost::simd;

  //------------------------------------------------------------------------------------------------
  //  float->int32
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::single_<A0>,bs::avx_>
                          , bd::target_<bd::scalar_<bd::int32_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvttps_epi32(a0);
    }
  };

  //------------------------------------------------------------------------------------------------
  //  float->double
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::single_<A0>,bs::sse_>
                          , bd::target_<bd::scalar_<bd::double_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvtps_pd(a0);
    }
  };

  //------------------------------------------------------------------------------------------------
  //  int32->float
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::int32_<A0>,bs::avx_>
                          , bd::target_<bd::scalar_<bd::single_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvtepi32_ps(a0);
    }
  };

  //------------------------------------------------------------------------------------------------
  //  int32->double
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::int32_<A0>,bs::sse_>
                          , bd::target_<bd::scalar_<bd::double_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvtepi32_pd(a0);
    }
  };

  //------------------------------------------------------------------------------------------------
  //  double->int32
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>,bs::avx_>
                          , bd::target_<bd::scalar_<bd::int32_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvttpd_epi32(a0);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>,bs::avx_>
                          , bd::target_<bd::scalar_<bd::uint32_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvttpd_epi32(a0);
    }
  };

  //------------------------------------------------------------------------------------------------
  //  double->float
  BOOST_DISPATCH_OVERLOAD ( pack_cast_
                          , (typename A0, typename A1)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>,bs::avx_>
                          , bd::target_<bd::scalar_<bd::single_<A1>>>
                          )
  {
    using result = typename A0::template rebind<typename A1::type>;
    BOOST_FORCEINLINE result operator()(A0 const& a0, A1 const&) const BOOST_NOEXCEPT
    {
      return _mm256_cvtpd_ps(a0);
    }
  };
} } }

#endif


#ifndef R_WRITER_HPP
#define R_WRITER_HPP

#include <set>
#include <math.h>
#include <numeric>
#include "stats/linear.hpp"
#include <boost/format.hpp>

// Defined in main.cpp
extern std::string date();

// Defined in main.cpp
extern std::string __full_command__;

namespace Anaquin
{
    class MappingStats;

    struct RWriter
    {
        static Scripts createLogistic(const FileName    &,
                                      const std::string &,
                                      const std::string &,
                                      const std::string &,
                                      const std::string &,
                                      const std::string &,
                                      bool showLOQ);

        static Scripts createMultiLinear(const FileName    &,
                                         const Path        &,
                                         const std::string &,
                                         const std::string &,
                                         const std::string &,
                                         const std::string &,
                                         const std::string &,
                                         const std::string &,
                                         bool showLOQ,
                                         bool shouldLog,
                                         const std::string &extra = "");

        static Scripts createFold(const FileName    &,
                                  const Path        &,
                                  const std::string &,
                                  const std::string &,
                                  const std::string &,
                                  const std::string &,
                                  const std::string &,
                                  bool shouldLog,
                                  const std::string &extra = "");

        static Scripts createRLinear(const FileName    &,
                                     const Path        &,
                                     const std::string &,
                                     const std::string &,
                                     const std::string &,
                                     const std::string &,
                                     const std::string &,
                                     const std::string &,
                                     bool showLOQ,
                                     const std::string &script = "");

        static Scripts createRConjoint(const FileName    &,
                                       const Scripts     &,
                                       const Path        &,
                                       const std::string &,
                                       const std::string &,
                                       const std::string &,
                                       const std::string &,
                                       const std::string &);

        static Scripts createScript(const FileName &, const Scripts &);
        static Scripts createScript(const FileName &, const Scripts &, const std::string &);
    };
}

#endif


/** Similarity Search
“© Copyright 2017  Hewlett Packard Enterprise Development LP
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.”
*/
/*
 * IndexSearch.cpp
 *
 *  Created on: Dec 18, 2013
 *      Author: kimmij
 */

#include <random>
#include <cmath>
#include <iostream>
#include "IndexSearch.h"
#include "LSHManager.h"
#include "headers.h"
#include <unordered_set>
#include <vector>
#include <unistd.h>
#include <sys/time.h>
#include "QueryHashFunctions.h"
#include <armadillo>
#include <blaze/math/DynamicMatrix.h>

using blaze::DynamicMatrix;

using namespace arma;

IndexSearch::IndexSearch() {
	// TODO Auto-generated constructor stub

}

IndexSearch::~IndexSearch() {
	// TODO Auto-generated destructor stub
}

/**
 * Search method using query hashes as precomputed vectors
 * to speep up perturbation indexing.
 * Paramters: query, R, L rande, results and queryHashes.
 * queryHashes class implemented a new method of getting index using a single set of random vectors.
 *
 * ASSUMPTION: The first R is RIndex =0, if this is not true, then the logic need to change.
 * Created by Tere Gonzalez
 * April 10, 2014
 */
time_t IndexSearch::searchIndex(vector<RealT> & query, int R, pair<int,int> const& range_L, int pert_no, MergeResult& mergeResult,QueryHashFunctions &queryHashes){

	LSH_HashFunction& lshHash 		= LSHManager::getInstance()->getLSH_HashFunction();
	//Getting hash structures for R=0 only
	PRNearNeighborStructT& nnStruct = lshHash.getNnStruct(0);
	DynamicMatrix<RealT>& a = lshHash.getMat(0);
	PUHashStructureT & uHash		= lshHash.getPUHash(0);
	cout <<endl<<" ...Method WITH PRECOMPUTATION***********WITH INNERJOIN";

	vector <RealT> atimesByRW;

	/*Parameters for the hashes*/
	int kLength		= nnStruct->hfTuplesLength;
	int W 			= nnStruct->parameterW;
	RealT rValue 	= lshHash.getR(R);
cout << "search test rValue=" << rValue << endl;
	int lLength		= range_L.second-range_L.first+1;

	//h1 and h2 results values
	Uns32T			h1[lLength];
	Uns32T			h2[lLength];

	time_t htime = 0;
	time_t htime1= 0;
	time_t htime2= 0;
	time_t htime3= 0;
	struct timespec before, after;

	clock_gettime(CLOCK_MONOTONIC,&before);

	//if R==0, then we compute the vectors, otherwise reuse the vectos
	if( R==0 ) {
		// vector a has been added for matrix multiplication for Hash generation
		if (!queryHashes.computeVectors(query,  kLength, lLength,pert_no,nnStruct->lshFunctions, a)){
			cout <<endl<<"error creating vectors";
			return 0; //Error computing vectors, we can not continue..
			}
	}

	clock_gettime(CLOCK_MONOTONIC,&after);
	htime1 =(diff(before, after).tv_nsec)/1000;

	//computing atimesbyRandW

	/*vector<RealT> &atimesX= queryHashes.getATimesX();

	atimesByRW.resize(query.size());
	for (int d =0; d < query.size(); d++) {
		atimesByRW [d] =atimesX/(rValue);
	}*/

	//get the index for the query
	queryHashes.getIndex(W,rValue,h1,h2,nnStruct->lshFunctions,uHash);

	for (int l=0; l< lLength; l++){
		HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,range_L.first+l);
		hashIndex.get(h1[l],h2[l], mergeResult);
	}

	htime2=0;

	//get the index for each perturbation
	for ( int n = 0; n < pert_no; n++)  {
		//get the index for the delta : query perturbation
		clock_gettime(CLOCK_MONOTONIC,&before);
		queryHashes.getIndex(W,rValue,h1,h2,nnStruct->lshFunctions,uHash,n);
		clock_gettime(CLOCK_MONOTONIC,&after);
		htime2+=((diff(before, after).tv_nsec)/1000);

		for (int l=0; l< lLength; l++){
				HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,range_L.first+l);
				hashIndex.get(h1[l],h2[l], mergeResult);
		}
	}

	htime = htime2 +htime1;
	cout<<endl<<"vectors total time1:"<<htime1;
	cout<<endl<<"h1h2 total time:"<<htime2;

#ifdef DESERIALIZE_CANDIDATES
 	bitset<LSHGlobalConstants::TOTAL_NUM_TIMESERIES_POINTS>& mergedResult = mergeResult.getMergedResult();
        static int query_id = -1;
        if(R == 0) query_id++;

        char filename[50];
        sprintf(filename,"./candidates_K21L600P7/candidates_%f.02_%f.02_%d", query[0], query[1], R);
        //sprintf(filename,"./K21L600P7candidates_query990/candidates_%d_%d", query_id, R);
cout << "candfile=" << filename << endl;
cout << "deserialize\n";
        //timespec before, after;
        //clock_gettime(CLOCK_MONOTONIC, &before);
        FILE * f = fopen(filename,"r");
        if(f == NULL)
                return htime;

        size_t result;
        int pcnt = 0;
        int p_row, p_col;

        while(true) {
                result = fread(&p_row, 1, sizeof(int), f);
                if(result <= 0)
                        break;
                fread(&p_col, 1, sizeof(int), f);
                int pos = p_row*LSHGlobalConstants::NUM_TIMESERIES_POINTS+p_col/LSHGlobalConstants::POS_TIMESERIES;
                mergedResult[pos] = true;

                if(pcnt < 10) {
                        cout << p_row << "," << p_col << endl;
                        pcnt++;
                }
        }
        fclose(f);
        //clock_gettime(CLOCK_MONOTONIC, &after);
        //long time_deser = diff(before, after).tv_nsec;
        //cout << "deserialize time=" << time_deser << endl;
#endif

	return htime;

}

/*
 * Search index method.
 * It computes perturbations and then get index of the points
 * This method is the same for all the R iterations
 * Created by Mijung Kim
 */
time_t IndexSearch::searchIndex(vector<RealT> & query, int R, pair<int,int> const& range_L, int pert_no, MergeResult& mergeResult) {
	LSH_HashFunction& lshHash = LSHManager::getInstance()->getLSH_HashFunction();

	timeval begin,end;
	time_t htime = 0;

	cout <<endl<<"Method NO PRECOMPUTATION*********** AND INNER PRODUCT";

	//set<pair<Uns32T,Uns32T>> merged_hindex[range_L.second-range_L.first+1];

	// h1 and h2 for the query point
	gettimeofday(&begin, nullptr);
	vector<pair<Uns32T,Uns32T>> hindex;
	lshHash.getIndex(R, range_L, (RealT*)&(query.at(0)), query.size(), hindex);
	gettimeofday(&end, nullptr);
	htime += (end.tv_sec - begin.tv_sec)*1000000 + (end.tv_usec - begin.tv_usec);

	// retrieve candidate points and pass to mergeResult
	//printf("query[%f][%f] \n", query[0],query[1]);
	//printf("query hashIndex[%d][%d] \n", hindex[0].first,hindex[0].second);
	for(int i=0;i<hindex.size();i++) {
		HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,range_L.first+i);
		//printf("h1[%d], h2[%d] \n", hindex.at(i).first, hindex.at(i).second);
		//merged_hindex[i].insert(make_pair(hindex.at(i).first, hindex.at(i).second));
		hashIndex.get(hindex.at(i).first, hindex.at(i).second, mergeResult);
	}

	//  Create perturbation list
	//  1. Generate d independent normal random variables x[i][1], x[i][2], ..., x[i][d], each with zero mean and unit variance.
	//  2. Compute Xnorm = sqrt(x[i][1]*x[i][1]+x[i][2]*x[i][2]+ ...+x[i][d]*x[i][d])
	//  3. Set delta[i] to be the vector (x[i][1], x[i][2], ..., x[i][d])/Xnorm
	//  4. Compute H(Q+delta[i]), the LSH Hash of Q+delta[i]
	//  5. Add H(Q+delta[i]) to S_H(l)

	vector<pair<Uns32T,Uns32T>> hindex_pert;
	RealT delta[query.size()];
	RealT dsum;
	// For the following three variables, maybe private member for IndexSearch or SearchCoordinator?
	static std::random_device rd;
	static std::mt19937 gen(rd());
	static std::normal_distribution<> dist(0,1);
	for(int j=0;j<pert_no;j++) {
		dsum = 0.0;
		//gen.seed(time(nullptr)*j);

		gen.seed(45354*j+j*j);

		for(int d=0;d<query.size();d++) {
			delta[d] = dist(gen);
			dsum += pow(delta[d],2);
		}
		dsum = sqrt(dsum);
		for(int d=0;d<query.size();d++) {
			delta[d] = query[d] + lshHash.getR(R)*delta[d]/dsum;
		}
		//printf("%d delta[0]=%f\n", j, delta[0]);

		hindex_pert.clear();
		// h1 and h2 for the perturbation point
		gettimeofday(&begin, nullptr);
		lshHash.getIndex(R, range_L,(RealT*)&(delta), query.size(), hindex_pert);
		gettimeofday(&end, nullptr);
		htime += (end.tv_sec - begin.tv_sec)*1000000 + (end.tv_usec - begin.tv_usec);

		// retrieve candidate points and pass to mergeResult
		for(int i=0;i<hindex_pert.size();i++) {
			//printf("Lindex=%d\n", i);
			HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,range_L.first+i);
			hashIndex.get(hindex_pert.at(i).first, hindex_pert.at(i).second, mergeResult);
		//	merged_hindex[i].insert(make_pair(hindex_pert.at(i).first, hindex_pert.at(i).second));

		}
	}
	//for(int i=0;i<hindex.size();i++) {
	//	HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,range_L.first+i);
	//	for (set<pair<Uns32T,Uns32T> >::iterator it=merged_hindex[i].begin(); it!=merged_hindex[i].end(); ++it) {
	//		pair<int, int> val = *it;
	//		hashIndex.get(val.first, val.second, mergeResult);
	//	}
	//}
	return htime;
}

/**
 * This is a test method ONLY
 * Search method using query hashes as precomputed vectors
 * to test original computation for all the R.
 * Paramters: query, R, L rande, results and queryHashes.
 * queryHashes class implemented a new method of getting index using a single set of random vectors.
 *
 * ASSUMPTION: This method should be removed when the code is stabilized
 * Created by Tere Gonzalez
 * April 10, 2014
 */
time_t IndexSearch::searchIndexPrecomputationAllR(vector<RealT> & query, int R, pair<int,int> const& range_L, int pert_no, MergeResult& mergeResult,QueryHashFunctions &queryHashes) {

	LSH_HashFunction& lshHash 		= LSHManager::getInstance()->getLSH_HashFunction();
	PRNearNeighborStructT& nnStruct = lshHash.getNnStruct(R);
	DynamicMatrix<RealT>& a = lshHash.getMat(R);
	PUHashStructureT & uHash		= lshHash.getPUHash(R);

	cout <<endl<<"Running precomputation...starting search index";
	int kLength		= nnStruct->hfTuplesLength;
	int W 			= nnStruct->parameterW;
	RealT rValue 	= lshHash.getR(R);

	cout <<endl<<"@LValue:"<<range_L.second<<" --->"<<range_L.first;
	int lLength		= range_L.second-range_L.first+1;

	Uns32T			h1[lLength];
	Uns32T			h2[lLength];
	struct timespec before, after;
	long time1;
	long time2;
	long time3;

	cout<<endl<<"R:"<<R<<"Rindex"<<rValue;

	clock_gettime(CLOCK_MONOTONIC,&before);
	//if( R==0 ) {
		if (!queryHashes.computeVectors(query,  kLength, lLength,pert_no,nnStruct->lshFunctions, a)){
			return 0; //Error computing vectors, we can not continue..
			}
		//}

	clock_gettime(CLOCK_MONOTONIC,&after);

	cout<<endl<<"..............hashes start";
	time1=diff(before, after).tv_nsec;

	clock_gettime(CLOCK_MONOTONIC,&before);
	queryHashes.getIndex(W,rValue,h1,h2,nnStruct->lshFunctions,uHash);
	clock_gettime(CLOCK_MONOTONIC,&after);
	time2 =diff(before, after).tv_nsec;

	clock_gettime(CLOCK_MONOTONIC,&before);

	for (int l=0; l< lLength; l++){
		HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,l);
		//hashIndex.get(hash, mergeResult);
		hashIndex.get(h1[l],h2[l], mergeResult);
	}

	clock_gettime(CLOCK_MONOTONIC,&after);
	time3 =diff(before, after).tv_nsec;

	clock_gettime(CLOCK_MONOTONIC,&after);

	for ( int n = 0; n < pert_no; n++)  {
		clock_gettime(CLOCK_MONOTONIC,&before);

		queryHashes.getIndex(W,rValue,h1,h2,nnStruct->lshFunctions,uHash,n);

		clock_gettime(CLOCK_MONOTONIC,&after);
		time2 += diff(before, after).tv_nsec;

		clock_gettime(CLOCK_MONOTONIC,&before);

		for (int l=0; l< lLength; l++){
			HashIndex& hashIndex = LSHManager::getInstance()->getHashIndex(R,l);
			hashIndex.get(h1[l],h2[l], mergeResult);
		}
		clock_gettime(CLOCK_MONOTONIC,&after);
		time3 += diff(before, after).tv_nsec;

	}
	clock_gettime(CLOCK_MONOTONIC,&after);
	time2=diff(before, after).tv_nsec;
	cout <<endl<<"time1:"<<time1;
	cout <<endl<<"time2:"<<time2;
	cout <<endl<<"time3:"<<time3;
	cout<<endl<<"..............hashes start"<<endl<<endl;

	time1= (time1+time2+time3)/1000;
	cout  <<endl<<"total time:"<<time1<<endl;
	return time1;

}


// Copyright (C) 2019 Kirill Okhotnikov.
//
// Use, modification, and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
//
// You are welcome to contact the author at:
//  kirill.okhotnikov@gmail.com
//

#ifndef SAFE_INT_TYPES_LIST_HPP
#define SAFE_INT_TYPES_LIST_HPP

#ifndef SAFE_REDUCED_LIST
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_LIST_SIZE 40
#endif

#include <cstdint>
#include <type_traits>
#include <boost/mpl/list.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/copy.hpp>
#include <boost/mpl/front_inserter.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/number.hpp>

#ifdef SAFE_REDUCED_LIST
typedef boost::mpl::list<signed char, unsigned char,
                         unsigned short, signed short,
                         signed int, unsigned int,
                         signed long, unsigned long,
                         signed long long, unsigned long long
                        > __types_integer_raw_t;
#else
typedef boost::mpl::list<signed char, unsigned char,
                         unsigned short, signed short,
                         signed int, unsigned int,
                         signed long, unsigned long,
                         signed long long, unsigned long long,
                         std::int8_t, std::int16_t, std::int32_t, std::int64_t,
                         std::int_fast8_t, std::int_fast16_t, std::int_fast32_t, std::int_fast64_t,
                         std::int_least8_t, std::int_least16_t, std::int_least32_t, std::int_least64_t,
                         std::intmax_t,
                         std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t,
                         std::uint_fast8_t, std::uint_fast16_t, std::uint_fast32_t, std::uint_fast64_t,
                         std::uint_least8_t, std::uint_least16_t, std::uint_least32_t, std::uint_least64_t,
                         std::uintmax_t
                        > __types_integer_raw_t;
#endif

//typedef boost::mpl::list<signed char, unsigned char> __result_types_raw_t;

typedef boost::mpl::list<float, double, long double> types_floating_t;

namespace __details {

template <typename List, typename T>
struct check_element
    : std::is_same<
        typename boost::mpl::find_if<List, std::is_same<boost::mpl::_1, T> >::type,
      typename boost::mpl::end<List>::type>
{};

typedef boost::mpl::fold<
    __types_integer_raw_t, boost::mpl::list<> ,
    boost::mpl::if_<
      boost::mpl::lambda<check_element<boost::mpl::_1, boost::mpl::_2> >,
      boost::mpl::push_front<boost::mpl::_1, boost::mpl::_2>, boost::mpl::_1>

      >::type types_integer_t;

}

typedef __details::types_integer_t types_integer_t;
typedef boost::mpl::copy<types_integer_t, boost::mpl::front_inserter< types_floating_t> >::type types_arithmetic_t;

//typedef boost::mpl::list<int>::type types_integer_t;
//typedef boost::mpl::list<float>::type types_arithmetic_t;

typedef boost::multiprecision::cpp_int precision_type_t;

inline
std::vector<precision_type_t> values_to_check() {
  std::vector<precision_type_t> result;
  precision_type_t cv = 1;
  for(int i = 0; i <= 68; i++) {
    for(const auto j : {-5, -2, -1, 0, 1, 2, 5}) {
      for(const auto k : {1, -1, 3, -3}) {
        result.emplace_back(k*cv + j);
      }
    }
    cv *= 2;
  }
  return result;
}

template <typename U>
bool fits_in(const precision_type_t &pp) {
  return    static_cast<precision_type_t>(std::numeric_limits<U>::max()) >= pp
         && static_cast<precision_type_t>(std::numeric_limits<U>::lowest()) <= pp;
}

template <typename T>
T __abs(const T t) {
  return t >= 0 ? t : -t;
};

template <typename U>
typename std::enable_if<std::is_floating_point<U>::value, bool>::type
precision_saved(const precision_type_t &v1, const precision_type_t &v2) {
  precision_type_t dv = v1 - v2;
  return __abs(dv.convert_to<U>()) + 1 > std::numeric_limits<U>::epsilon()
          * (__abs(v1.convert_to<U>()) + __abs(v2.convert_to<U>()));
}

template <typename U>
typename std::enable_if<!std::is_floating_point<U>::value, bool>::type
precision_saved(const precision_type_t &v1, const precision_type_t &v2) {
  return true;
}

#endif //SAFE_INT_TYPES_LIST_HPP


/*!
@file
Defines `boost::hana::Range`.

@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_RANGE_HPP
#define BOOST_HANA_RANGE_HPP

#include <boost/hana/comparable/equal_mcd.hpp>
#include <boost/hana/core/datatype.hpp>
#include <boost/hana/detail/std/integer_sequence.hpp>
#include <boost/hana/foldable/foldable.hpp>
#include <boost/hana/group/group.hpp>
#include <boost/hana/integral.hpp>
#include <boost/hana/iterable/mcd.hpp>
#include <boost/hana/logical/logical.hpp>
#include <boost/hana/monoid/monoid.hpp>
#include <boost/hana/orderable/orderable.hpp>

namespace boost { namespace hana {
    //! @ingroup group-datatypes
    //! Compile-time half-open interval of `Integral`s.
    //!
    //! ## Instance of
    //! `Iterable`, `Foldable`, `Comparable`
    struct Range { };

    namespace range_detail {
        template <typename From, typename To, typename = operators<Comparable>>
        struct range {
            From from;
            To to;
            using hana_datatype = Range;
        };
    }

    //! Creates a `Range` containing the `Integral`s in `[from, to)`.
    //! @relates Range
    //! @hideinitializer
    //!
    //! `from` and `to` must be `Integral`s such that `from <= to`. Otherwise,
    //! a compilation error is triggered.
    //!
    //! ### Example
    //! @snippet example/range/range.cpp main
    BOOST_HANA_CONSTEXPR_LAMBDA auto range = [](auto from, auto to) {
        // For some reason, Clang 3.5 requires that we create an intermediate
        // variable whose type is dependent so we can use `valid_range` as a
        // constant expression below.
        auto valid_range = less_equal(from, to);
        static_assert(valid_range(),
        "invalid usage of boost::hana::range(from, to) with from > to");
        return range_detail::range<decltype(from), decltype(to)>{from, to};
    };

    //! Shorthand to create a `Range` of `Integral`s.
    //! @relates Range
    //!
    //! This shorthand is provided for convenience only and it is equivalent
    //! to using `range`. Specifically, `range_c<T, from, to>` is such that
    //! @code
    //!     range_c<T, from, to> == range(integral<T, from>, integral<T, to>)
    //! @endcode
    //!
    //!
    //! @tparam T
    //! The underlying integral type of the `Integral`s in the created range.
    //!
    //! @tparam from
    //! The inclusive lower bound of the created range.
    //!
    //! @tparam to
    //! The exclusive upper bound of the created range.
    //!
    //!
    //! ### Example
    //! @snippet example/range/range_c.cpp main
    template <typename T, T from, T to>
    BOOST_HANA_CONSTEXPR_LAMBDA auto range_c = range(
        integral<T, from>, integral<T, to>
    );

    //! @details
    //! Let `r` be a `Range` containing the `Integral`s in the half-open
    //! interval `[from, to)`. The head of `r` is an `Integral` with value
    //! `from`, its tail is the range representing the `[from + 1, to)`
    //! interval and `r` is empty if and only if `from == to`.
    //!
    //! @snippet example/range/iterable.cpp main
    template <>
    struct Iterable::instance<Range> : Iterable::mcd {
        template <typename R>
        static constexpr auto head_impl(R r)
        { return r.from; }

        template <typename R>
        static constexpr auto tail_impl(R r)
        { return range(plus(r.from, int_<1>), r.to); }

        template <typename R>
        static constexpr auto is_empty_impl(R r)
        { return equal(r.from, r.to); }

        template <typename N, typename R>
        static constexpr auto at_impl(N n, R r)
        { return plus(r.from, n); }

        template <typename R>
        static constexpr auto last_impl(R r)
        { return minus(r.to, int_<1>); }

        template <typename N, typename R>
        static constexpr auto drop_impl(N n, R r) {
            auto size = minus(r.to, r.from);
            return range(if_(greater(n, size), r.to, plus(r.from, n)), r.to);
        }
    };

    //! Generic instance for `Iterable`s.
    template <>
    struct Foldable::instance<Range> : detail::FoldableFromIterable {
        template <typename F, typename From, typename T, T ...vs>
        static constexpr auto unpack_helper(F f, From from,
            detail::std::integer_sequence<T, vs...>)
        { return f(integral<T, from() + vs>...); }

        template <typename R, typename F>
        static constexpr auto unpack_impl(R r, F f) {
            auto size = minus(r.to, r.from);
            return unpack_helper(f, r.from,
                detail::std::make_integer_sequence<decltype(r.from()), size()>{});
        }

        template <typename R>
        static constexpr auto length_impl(R r)
        { return minus(r.to, r.from); }
    };

    //! @details
    //! Two ranges are equal if and only if they are both empty or they have
    //! the same `head` and the same length.
    template <>
    struct Comparable::instance<Range, Range> : Comparable::equal_mcd {
        template <typename R1, typename R2>
        static constexpr auto equal_impl(R1 r1, R2 r2) {
            return or_(
                and_(is_empty(r1), is_empty(r2)),
                and_(
                    equal(r1.from, r2.from),
                    equal(r1.to, r2.to)
                )
            );
        }
    };
}} // end namespace boost::hana

#endif // !BOOST_HANA_RANGE_HPP