#include "scene.hpp"
#include "stats.hpp"


Scene::Scene(): lighting(new Lights()), cleanup(true) {
}


Scene::~Scene() {
    delete lighting;
    if (!cleanup) {
        return;
    }
    while (objects.size()) {
        delete objects.back();
        objects.pop_back();
    }
    while (lights.size()) {
        delete lights.back();
        lights.pop_back();
    }
}


bool Scene::push(Object* o) {
    if (objects.empty()) {
        bound_min = o->bound_min;
        bound_max = o->bound_max;
    } else {
        bound_min = vertex_t::minof(bound_min, o->bound_min);
        bound_max = vertex_t::maxof(bound_max, o->bound_max);
    }
    objects.push_back(o);
    light_objects.push_back(o);
    return true;
}


bool Scene::push(Light* l) {
    lights.push_back(l);
    return true;
}


void Scene::finalize() {
    for (std::vector<const Light*>::const_iterator lit=lights.begin(); lit!=lights.end(); lit++) {
        lighting->push(*lit);
    }
    lighting->finalize(objects);
}


bool Scene::intersect(const ray_t& ray, std::vector<const Object*>& obj) {
    static Counter scene_light_hit("scene light hit", true);

    const vertex_t rdst = ray.origin + ray.direction;
    const vertex_t rmin = vertex_t::minof(ray.origin, rdst);
    const vertex_t rmax = vertex_t::maxof(ray.origin, rdst);

    for (std::vector<const Object*>::iterator it = obj.begin(); it != obj.end(); it++) {
        const Object* o = *it;
        if (!o->intersect_box(rmin, rmax)) {
            continue;
        }
        if (o->intersect(ray)) {
            #ifndef NO_OBJ_LRU
                if (it != obj.begin()) {
                    scene_light_hit.inc();
                    const Object* tmp = obj.front();
                    obj.front() = *it;
                    *it = tmp;
                } else {
                    scene_light_hit.inc(1);
                }
            #endif
            return true;
        }
    }

    return false;
}


bool Scene::intersect(const ray_t& ray) const {
    return intersect(ray, light_objects);
}


const Object* Scene::intersect(ray_t& ray, const vertex_t& ray_normal, vertex_t& hitpoint, vertex_t& normal, LightValue& light, Img::rgb_t* col) const {
    static Counter scene_hit("scene first hit", true);
    static Counter scene_miss("scene miss", true);
    static Counter scene_late_miss("scene late miss", true);

    // build full camera box to detect intersection with object boxes XXX: this can be even a full quadrant
    vertex_t rdst = ray.origin + ray.direction;
    vertex_t rmin = vertex_t::minof(ray.origin, rdst);
    vertex_t rmax = vertex_t::maxof(ray.origin, rdst);

    if (!Object::intersect_box(rmin, rmax, bound_min, bound_max)) {
        return NULL; // misses whole scene
    }

    const Object* rv = NULL;
    std::vector<const Object*>::iterator rv_it;
    Object::intersect_ctx_t rv_ctx;
    LightValue rv_light;

    for (std::vector<const Object*>::iterator it = objects.begin(); it != objects.end(); it++) {
        const Object* o = *it;

        if (!o->intersect_box(rmin, rmax)) {
            scene_miss.inc(1);
            scene_late_miss.inc();
            continue;
        }

        Object::intersect_ctx_t ctx;
        if (o->intersect(ctx, ray, ray_normal, hitpoint)) {
            rv = o;
            rv_it = it;
            rv_ctx = ctx;
            ray.direction = hitpoint - ray.origin; // decrease depth to handle occlusion

            rdst = ray.origin + ray.direction;
            rmin = vertex_t::minof(ray.origin, rdst);
            rmax = vertex_t::maxof(ray.origin, rdst);

            scene_miss.inc();
            scene_late_miss.inc();
        } else {
            scene_miss.inc();
            scene_late_miss.inc(1);
        }
    }

    unless (rv) {
        return NULL;
    }

    // get the corresponding (possibly costly) light, normal, and texture information
    rv->intersect(rv_ctx, ray, hitpoint, normal, light, col);

    #ifndef NO_OBJ_LRU
        if (rv_it != objects.begin()) {
            scene_hit.inc();
            const Object* tmp = objects.front();
            objects.front() = rv;
            *rv_it = tmp;
        } else {
            scene_hit.inc(1);
        }
    #endif

    return rv;
}


void Scene::get_light(const Object* o, const vertex_t& hit, const vertex_t& ray, const vertex_t& raynrm, const vertex_t& nrm, LightValue& lv) const {
    lighting->get_light(o, hit, &raynrm, nrm, lv);
}


const Scene* Scene::getSMPcopy() const {
    Scene* s = new Scene();
    s->cleanup = false;
    for (std::vector<const Object*>::const_iterator it=objects.begin(); it!=objects.end(); it++) {
        s->push(const_cast<Object*>(*it));
    }
    for (std::vector<const Light*>::const_iterator it=lights.begin(); it!=lights.end(); it++) {
        s->push(const_cast<Light*>(*it));
    }
    lighting->getSMPcopy(*s->lighting);
    return s;
}