/*
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License version 2 
 *   as published by the Free Software Foundation.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *   Copyright (C) 2006  Thierry Berger-Perrin <tbptbp@gmail.com>
 */

#include "specifics.h"
#include "kdlib_writer.h"
#include "math_vec.h"
#include "math_aabb.h"
#include "math_triangle.h"

namespace kdlib {
    extern const kdlib::triangle_t *triangle_soup_base;
}

/***************************************************************************//**
 * The allocator
/******************************************************************************/
class allocator_t {
public:
    allocator_t(kdtree::node_t *p, kdtree::tri_id_t *q)
        : curr_node(p), items(q), curr_item(q)
    {}

    /* allocate 2 children at once */
    FINLINE
    kdtree::node_t *allocate_children() {
        kdtree::node_t * const p = curr_node;
        curr_node += 2;
        return p;
    }

    /* leaf content */
    kdtree::tri_id_t *allocate_items(const uint_t count) {
        kdtree::tri_id_t * const p = curr_item;
        curr_item += count;
        return p;
    }

    /* get the offset */
    uint32_t get_offset(kdtree::tri_id_t * const p) const {
        return uint32_t(p-items);
    }

    /* for lack of a better place/mechanism. */
    uint32_t get_triangle_id(const kdlib::triangle_t * const p) {
        return uint32_t(p - kdlib::triangle_soup_base);
    }

private:
    kdtree::node_t *curr_node;
    kdtree::tri_id_t * const items;
    kdtree::tri_id_t *curr_item;
};

/***************************************************************************//**
 * Recursively write the kd-tree
/******************************************************************************/
static void
do_write(allocator_t &allocator,
         const kdtree::node_t * const root,
         const kdlib::node_t * const src,
         kdtree::node_t * const dst)
{
    if (src == 0) {
        sys::log("do_write: null src.\n");
    } else if (src->is_leaf) {
        kdtree::tri_id_t * const p = allocator.allocate_items(src->count);
        const uint32_t count = src->count, offset = allocator.get_offset(p);
        uint_t written = 0, pairs = 0;
        kdlib::sideness_t side;
        const kdlib::lbox_t *q = src->items.extract(side);
        while (q) {
            if (side == kdlib::side_left) {
                p[written] = q->triangle_id;
                ++written;
            } else
                ++pairs;
            q = q->dim[0][side].next.extract(side);
        }
        if (written != count) {
            sys::log("do_write: leaf... %d items but only %d written.\n",
                     count, written);
            BREAKPOINT();
        }
        if (written != pairs) {
            sys::log("do_write: leaf... %d items, "
                     "%d written but only %d matching sides.\n",
                     count, written, pairs);
            BREAKPOINT();
        }
        dst->leaf.offset_flag = offset | kdtree::node_t::mask_leaf;
        dst->leaf.count = count;
    } else {
        kdtree::node_t *left = allocator.allocate_children();
        kdtree::node_t *right = left + 1;
        const uint32_t diff = (uint32_t) (left - root);
        dst->inner.dim_offset_flag = src->split_axis | diff << 3;
        dst->inner.split_coord = src->split_coord;
        do_write(allocator, root, src->left, left);
        do_write(allocator, root, src->right, right);
    }
}

/***************************************************************************//**
 * Perform the write of the non-serialized kd-tree into the serialized one
/******************************************************************************/
bool_t kdlib::writer_t::write(kdlib::node_t *src)
{
    allocator_t allocator(dst+2, ids);
    do_write(allocator, dst, src, dst);
    return true;
}

/***************************************************************************//**
 * Init the writer
/******************************************************************************/
bool_t kdlib::writer_t::init(const kdlib::compiler_t &compiler)
{
    num_nodes = compiler.stats.num_nodes+1;
    num_ids = compiler.stats.num_ids;
    const int
        mem_nodes = sizeof(kdtree::node_t) * num_nodes,
        mem_ids = sizeof(kdtree::tri_id_t) * num_ids;
    dst = (kdtree::node_t *) sys::mem::allocate(mem_nodes);
    ids = (kdtree::tri_id_t *) sys::mem::allocate(mem_ids);
    const double mega = 1/(1024.*1024.);
    sys::log("kdlib::writer_t::init: %s %.3fM for nodes, %.3fM for ids.\n",
        ((dst != 0) & (ids != 0)) ? "allocated" : "failed to allocate",
        double(mem_nodes)*mega, double(mem_ids)*mega);
    if (!(dst && ids))
        return false;
    return true;
}
