#include "object.hpp"
#include <assert.h>
#include <cmath>


/**
 * @file
 * @brief Ray-intersections for most simple one-sided axis-aligned planes.
 */


Object::Object(box_t box, const Texture* tex) : bounding(box), texture(tex) {}


SidedAxisPlane::SidedAxisPlane(vertex_t pos, vertex_t dir, bool positive, const Texture* tex)
    : Object(box_t{pos, pos + dir}, tex), pos(pos), dir(dir), positive(positive) {
    assert(bounding.lo.x == bounding.hi.x || bounding.lo.y == bounding.hi.y || bounding.lo.z == bounding.hi.z);

    if (dir.x == 0.0) {
        os = {0, 1, 2};
    } else if (dir.y == 0.0) {
        os = {1, 0, 2};
    } else if (dir.z == 0.0) {
        os = {2, 0, 1};
    } else {
        assert(false);
    }

    nrm.v[os.n] = positive ? 1.0 : -1.0;
}


bool SidedAxisPlane::intersect(const ray_t& ray, vertex_t& hit) const {
    assert(ray.dir.v[os.n] != 0.0F); // pre-checked: parallel and div by zero

    const fcoord_t t = vertex_t::scale_factor(pos.v[os.n], ray.pos.v[os.n], ray.dir.v[os.n]);
    if (t < 0.0F || t > 1.0F) {
        return false;
    }

    const vertex_t h{ray.dir * t + ray.pos};
    if (h.v[os.a] < bounding.lo.v[os.a] || h.v[os.a] > bounding.hi.v[os.a] || h.v[os.b] < bounding.lo.v[os.b] ||
        h.v[os.b] > bounding.hi.v[os.b]) {
        return false;
    }

    hit = h;
#if 1
    hit.v[os.n] = pos.v[os.n];
#endif
    return true;
}


bool SidedAxisPlane::intersect(const ray_t& ray) const {
    static vertex_t hit;
    return can_intersect(ray) && intersect(ray, hit);
}

bool SidedAxisPlane::intersect(const ray_t& ray, vertex_t& hit, vertex_t& hit_nrm) const {
    if (can_intersect(ray) && intersect(ray, hit)) {
        hit_nrm = nrm;
        return true;
    }
    return false;
}

bool SidedAxisPlane::intersect(const ray_t& ray, vertex_t& hit, vertex_t& hit_nrm, const Texture*& tex,
                               uv_t& uv) const {
    if (can_intersect(ray) && intersect(ray, hit)) {
        hit_nrm = nrm;
        tex = texture;
        uv.u = (hit.v[os.a] - pos.v[os.a]) / dir.v[os.a];
        uv.v = (hit.v[os.b] - pos.v[os.b]) / dir.v[os.b];
        return true;
    }
    return false;
}


/** @brief camera position on visible side - in normal direction? */
bool SidedAxisPlane::can_intersect(const vertex_t& camera) const {
    return positive ? camera.v[os.n] > pos.v[os.n] : camera.v[os.n] < pos.v[os.n];
}


/** @brief normal pointing to opposite direction? */
INLINE bool SidedAxisPlane::can_intersect(const ray_t& ray) const {
    return positive ? ray.dir.v[os.n] < 0.0 : ray.dir.v[os.n] > 0.0;
}


/** @brief side-independent hit in direction possible? */
INLINE bool SidedAxisPlane::can_intersect_any(const ray_t& ray) const {
    return likely(ray.dir.v[os.n] != 0.0F);
}


bool SidedAxisPlane::intersect_full(const vertex_t& pos, const box_t& dst) const {
    for (const auto corner_side :
         {box_t::corner_t::CORNER_A, box_t::corner_t::CORNER_B, box_t::corner_t::CORNER_C, box_t::corner_t::CORNER_D,
          box_t::corner_t::CORNER_E, box_t::corner_t::CORNER_F, box_t::corner_t::CORNER_G, box_t::corner_t::CORNER_H}) {
        const vertex_t corner = dst.get_corner(corner_side);
        const ray_t ray{pos, corner - pos, corner};
        if (likely(!can_intersect_any(ray) || !intersect(ray))) {
            return false;
        }
    }
    return true;
}