#pragma once

#include "Universe.hpp"
#include "Chunk.hpp"

namespace world {

template<class Areas>
Universe::ray_result Raycast(const geometry::Ray& ray, const Areas& areas) {
    //MAYBE: ray + offset to get float precision
    Universe::ray_result target;
    size_t dist = UINT32_MAX - 1;
    for(auto& area: areas) {
        if(ray.intersect(area.second->getBounding()) != geometry::IBox::ContainmentType::Disjoint) {
            const auto &offset = area.second->getOffset().as_voxel();
            const auto &chunks = area.second->getChunks();
            std::shared_ptr<world::Chunk> chunk = nullptr;
            chunk_pos chunk_vec(INT_MAX);
            geometry::Ray::iterator it(ray);
            glm::llvec3 point;
            for (size_t i = 0; i < dist && it.next(point); i++) {
                const auto pos = point - offset;
                const chunk_pos cPos = glm::divide(pos);
                if(cPos != chunk_vec) {
                    if (const auto it = chunks.find(cPos); it != chunks.end()) {
                        chunk = it->second;
                        chunk_vec = cPos;
                    } else if(chunks.inRange(cPos)) {
                        target.emplace<Universe::ray_out_of_range>(std::make_pair(area.first, cPos), offset);
                        break;
                    } else {
                        chunk = nullptr;
                    }
                }
                if(chunk != nullptr) {
                    const auto voxel = chunk->getAt(glm::modulo(pos));
                    if(voxel.is_solid()) {
                        target.emplace<Universe::ray_target>(std::make_pair(area.first, pos), voxel, offset);
                        dist = i;
                        break;
                    }
                }
            }
        }
    }
    return target;
}

}