/*
    Copyright 2014 Adobe
    Distributed under 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)
*/
/****************************************************************************************************/

// identity
#include <binspector/common.hpp>

// stdc++
#include <algorithm>

// boost
#include <boost/lexical_cast.hpp>

// asl
#include <adobe/implementation/expression_parser.hpp>
#include <adobe/implementation/token.hpp>
#include <adobe/virtual_machine.hpp>
#include <adobe/unicode.hpp>

// application
#include <binspector/endian.hpp>

/****************************************************************************************************/

namespace {

/****************************************************************************************************/

template <typename T>
inline adobe::any_regular_t convert_raw(const rawbytes_t& raw) {
    T value(*reinterpret_cast<const T*>(&raw[0]));

    return adobe::any_regular_t(static_cast<double>(value));
}

inline adobe::any_regular_t convert_raw(const rawbytes_t& raw,
                                        std::size_t       bit_count,
                                        atom_base_type_t  base_type) {
    if (base_type == atom_unknown_k) {
        throw std::runtime_error("convert_raw: unknown atom base type");
    } else if (base_type == atom_float_k) {
        BOOST_STATIC_ASSERT((sizeof(float) == 4));
        BOOST_STATIC_ASSERT((sizeof(double) == 8));

        if (bit_count == 32)
            return convert_raw<float>(raw);
        else if (bit_count == 64)
            return convert_raw<double>(raw);
        else
            throw std::runtime_error(
                "convert_raw: float atom of specified bit count not supported.");
    } else if (bit_count <= 8) {
        if (base_type == atom_signed_k)
            return convert_raw<boost::int8_t>(raw);
        else if (base_type == atom_unsigned_k)
            return convert_raw<boost::uint8_t>(raw);
    } else if (bit_count <= 16) {
        if (base_type == atom_signed_k)
            return convert_raw<boost::int16_t>(raw);
        else if (base_type == atom_unsigned_k)
            return convert_raw<boost::uint16_t>(raw);
    } else if (bit_count <= 32) {
        if (base_type == atom_signed_k)
            return convert_raw<boost::int32_t>(raw);
        else if (base_type == atom_unsigned_k)
            return convert_raw<boost::uint32_t>(raw);
    } else if (bit_count <= 64) {
        if (base_type == atom_signed_k)
            return convert_raw<boost::int64_t>(raw);
        else if (base_type == atom_unsigned_k)
            return convert_raw<boost::uint64_t>(raw);
    }

    throw std::runtime_error("convert_raw: invalid bit count");
}

/****************************************************************************************************/
#if 0
#pragma mark -
#endif
/****************************************************************************************************/

class evaluation_error_t : public std::runtime_error {
public:
    explicit evaluation_error_t(const std::string& error) : std::runtime_error(error) {}

    virtual ~evaluation_error_t() throw() {}
};

/****************************************************************************************************/

struct contextual_evaluation_engine_t {
    contextual_evaluation_engine_t(const adobe::array_t& expression,
                                   inspection_branch_t   main_branch,
                                   inspection_branch_t   current_node,
                                   bitreader_t&          input);

    adobe::any_regular_t evaluate(bool finalize = true);

private:
    // evaluation vm custom callbacks
    adobe::any_regular_t named_index_lookup(const adobe::any_regular_t& value,
                                            adobe::name_t               name,
                                            bool                        throwing);
    adobe::any_regular_t numeric_index_lookup(const adobe::any_regular_t& value, std::size_t index);
    adobe::any_regular_t stack_variable_lookup(adobe::name_t name);
    adobe::any_regular_t array_function_lookup(adobe::name_t         name,
                                               const adobe::array_t& parameter_set);

    // helpers
    inspection_branch_t regular_to_branch(const adobe::any_regular_t& name_or_branch);

    const adobe::array_t& expression_m;
    inspection_branch_t   main_branch_m;
    inspection_branch_t   current_node_m;
    bitreader_t&          input_m;
    bool                  finalize_m;
};

/****************************************************************************************************/

contextual_evaluation_engine_t::contextual_evaluation_engine_t(const adobe::array_t& expression,
                                                               inspection_branch_t   main_branch,
                                                               inspection_branch_t   current_node,
                                                               bitreader_t&          input)
    : expression_m(expression), main_branch_m(main_branch), current_node_m(current_node),
      input_m(input), finalize_m(false) {}

/****************************************************************************************************/
#if 0
template <typename T, typename U>
void bad_cast_details(const adobe::bad_cast& error, const std::string& details)
{
    static const adobe::bad_cast test(typeid(T), typeid(U));
    static const std::string     test_string(test.what());

    if (std::string(error.what()) == test_string)
        throw std::runtime_error(details);
}
#endif
/****************************************************************************************************/

adobe::any_regular_t contextual_evaluation_engine_t::evaluate(bool finalize) try {
    adobe::virtual_machine_t vm;

    finalize_m = finalize;

    vm.set_variable_lookup(std::bind(&contextual_evaluation_engine_t::stack_variable_lookup,
                                     std::ref(*this),
                                     std::placeholders::_1));
    vm.set_named_index_lookup(std::bind(&contextual_evaluation_engine_t::named_index_lookup,
                                        std::ref(*this),
                                        std::placeholders::_1,
                                        std::placeholders::_2,
                                        true));
    vm.set_numeric_index_lookup(std::bind(&contextual_evaluation_engine_t::numeric_index_lookup,
                                          std::ref(*this),
                                          std::placeholders::_1,
                                          std::placeholders::_2));
    vm.set_array_function_lookup(std::bind(&contextual_evaluation_engine_t::array_function_lookup,
                                           std::ref(*this),
                                           std::placeholders::_1,
                                           std::placeholders::_2));

    vm.evaluate(expression_m);

    adobe::any_regular_t result(vm.back());

    vm.pop_back();

    return result;
}
#if 0
catch (const adobe::bad_cast& error)
{
    // If in the course of eval we get a bad cast from double to name we can
    // deduce the user might have forgotten an @ symbol when appropriate, e.g.,
    //     str(my_field) instead of str(@my_field)
    // Another possible error is when a user tries to fetch the subfield of
    // an address, which is not allowed:
    //     @field.subfield
    // Another possible error is when an address is passed when a value is
    // expected, a la
    //     byte(@field), which expects an offset instead of an address

    std::string path(build_path(main_branch_m, current_node_m));

    bad_cast_details<double, adobe::name_t>(error, "Address failure -- possible '@' missing in " + path);
    bad_cast_details<adobe::name_t, inspection_branch_t>(error, "Address failure -- possible '@' extra in " + path);
    bad_cast_details<inspection_branch_t, double>(error, "Address failure -- possible '@' extra in " + path);

    throw error;
}
#endif
catch (const std::out_of_range& error) {
    // we'll get this from the bitreader when we hit the end of the file.
    // pass it up -- it's handled elsewhere.

    throw;
} catch (const evaluation_error_t& error) {
    // we'll get this from the contexutal evaluation engine when something
    // goes wrong. It's already formatted, to the std::exception decoration
    // below isn't necessary. Pass it up.

    throw;
} catch (const std::exception& error) {
    std::string details(error.what());

    // REVISIT (fbrereto) : String concatenation in this routine

    if (dynamic_cast<const std::bad_cast*>(&error) != NULL)
        details += " -- do you need to use ptoi or itop?";

    if (current_node_m.equal_node(inspection_branch_t()))
        details += "\nin field: <<unknown>>";
    else
        details += "\nin field: " + build_path(main_branch_m, current_node_m);

    throw std::runtime_error(details);
}

/****************************************************************************************************/

adobe::any_regular_t contextual_evaluation_engine_t::named_index_lookup(
    const adobe::any_regular_t& value, adobe::name_t name, bool throwing) {
    inspection_branch_t branch(value.cast<inspection_branch_t>());

    if (!branch.equal_node(inspection_branch_t())) {
        inspection_forest_t::child_iterator iter(adobe::child_begin(branch));
        inspection_forest_t::child_iterator last(adobe::child_end(branch));

        for (; iter != last; ++iter)
            if (iter->name_m == name)
                return finalize_lookup<adobe::any_regular_t>(
                    main_branch_m, inspection_branch_t(iter.base()), input_m, finalize_m);
    }

    if (throwing)
        throw std::runtime_error(adobe::make_string("Subfield '", name.c_str(), "' not found."));

    return adobe::any_regular_t();
}

/****************************************************************************************************/

adobe::any_regular_t contextual_evaluation_engine_t::numeric_index_lookup(
    const adobe::any_regular_t& value, std::size_t index) {
    inspection_branch_t branch(value.cast<inspection_branch_t>());

    if (!adobe::has_children(branch))
        throw std::range_error(adobe::make_string("Array '", branch->name_m.c_str(), "' is empty"));

    inspection_forest_t::child_iterator iter(adobe::child_begin(branch));
    inspection_forest_t::child_iterator last(adobe::child_end(branch));
    std::size_t                         i(0);

    while (true) {
        if (i == index)
            return finalize_lookup<adobe::any_regular_t>(
                main_branch_m, inspection_branch_t(iter.base()), input_m, finalize_m);

        if (++iter == last) {
            std::stringstream error;
            error << "Array index " << index << " out of range [ 0 .. " << i << " ] for array '"
                  << branch->name_m << "'";
            throw std::range_error(error.str());
        }

        ++i;
    }

    return adobe::any_regular_t();
}

/****************************************************************************************************/

adobe::any_regular_t contextual_evaluation_engine_t::stack_variable_lookup(adobe::name_t name) {
    inspection_branch_t current_node(current_node_m);
    bool                bad_node(current_node.equal_node(inspection_branch_t()));

    if (name == value_main)
        return adobe::any_regular_t(main_branch_m);
    else if (name == value_this)
        return adobe::any_regular_t(current_node);

    while (!bad_node) {
        adobe::any_regular_t subfield(
            named_index_lookup(adobe::any_regular_t(current_node), name, false));

        // named_index_lookup handles finalization
        if (subfield != adobe::any_regular_t())
            return subfield;

        current_node.edge() = adobe::forest_leading_edge;

        if (current_node == main_branch_m)
            break;

        current_node = adobe::find_parent(current_node);

        bad_node = current_node.equal_node(inspection_branch_t());
    }

    std::string error;

    // REVISIT (fbrereto) : String concatenation in this routine.
    if (bad_node)
        error +=
            adobe::make_string("Subfield '", name.c_str(), "' not found at or above unknown node");
    else
        error += adobe::make_string("Subfield '", name.c_str(), "' not found at or above ") +
                 build_path(main_branch_m, current_node_m);

    throw std::runtime_error(error);
}

/****************************************************************************************************/

inspection_branch_t contextual_evaluation_engine_t::regular_to_branch(
    const adobe::any_regular_t& name_or_branch) {
    const std::type_info& type(name_or_branch.type_info());

    if (type == typeid(inspection_branch_t)) {
        return name_or_branch.cast<inspection_branch_t>();
    } else if (type == typeid(adobe::name_t)) {
        // Otherwise, converts a user-specified name-as-path to an inspection branch

        std::stringstream      input(name_or_branch.cast<adobe::name_t>().c_str());
        adobe::line_position_t position(__FILE__, __LINE__);
        adobe::array_t         expression;

        adobe::expression_parser(input, position).require_expression(expression);

        return contextual_evaluation_of<inspection_branch_t>(
            expression, main_branch_m, current_node_m, input_m);
    }

    throw std::runtime_error(
        adobe::make_string("Expected ref(@field) or @field, but was passed ", type.name()));
}

/****************************************************************************************************/

namespace {

/****************************************************************************************************/

template <class I>
void endian_swap(I first, I last) {
    constexpr auto swapper{[](auto& x){
        constexpr auto size = sizeof(x);
        constexpr auto end = size >> 1;

        for (std::size_t i(0); i < end; ++i)
            std::swap(reinterpret_cast<char*>(&x)[i],
                      reinterpret_cast<char*>(&x)[size - i - 1]);
    }};

    while (first != last) {
        swapper(*first++);
    }
}

/****************************************************************************************************/

} // namespace

/****************************************************************************************************/

adobe::any_regular_t contextual_evaluation_engine_t::array_function_lookup(
    adobe::name_t name, const adobe::array_t& parameter_set) {
    CONSTANT_VALUE(byte);
    CONSTANT_VALUE(card);
    CONSTANT_VALUE(endof);
    CONSTANT_VALUE(indexof);
    CONSTANT_VALUE(path);
    CONSTANT_VALUE(peek);
    CONSTANT_VALUE(print);
    CONSTANT_VALUE(sizeof);
    CONSTANT_VALUE(startof);
    CONSTANT_VALUE(str);
    CONSTANT_VALUE(strcat);
    CONSTANT_VALUE(summaryof);
    CONSTANT_VALUE(fcc);
    CONSTANT_VALUE(ptoi);
    CONSTANT_VALUE(padd);
    CONSTANT_VALUE(psub);
    CONSTANT_VALUE(itop);
    CONSTANT_VALUE(itoah); // integer to hex string (c++'s itoa, hex format)
    CONSTANT_VALUE(gtell);
    CONSTANT_VALUE(utf16utf8);

    if (name == value_sizeof) {
        if (parameter_set.empty())
            throw std::runtime_error("sizeof(): @field_name expected");

        inspection_position_t start_offset;
        inspection_position_t end_offset(invalid_position_k);

        if (parameter_set.size() == 1) {
            inspection_branch_t leaf(regular_to_branch(parameter_set[0]));

            start_offset = starting_offset_for(leaf);
            end_offset   = ending_offset_for(leaf);
        } else if (parameter_set.size() == 2) {
            inspection_branch_t leaf1(regular_to_branch(parameter_set[0]));
            inspection_branch_t leaf2(regular_to_branch(parameter_set[1]));

            start_offset = starting_offset_for(leaf1);
            end_offset   = ending_offset_for(leaf2);
        } else {
            throw std::runtime_error("sizeof(): too many parameters");
        }

        inspection_position_t size(end_offset - start_offset + inspection_byte_k);

        return adobe::any_regular_t(static_cast<double>(size.bytes()));
    } else if (name == value_startof) {
        if (parameter_set.empty())
            throw std::runtime_error("startof(): @field_name expected");

        inspection_branch_t   leaf(regular_to_branch(parameter_set[0]));
        inspection_position_t start_offset(starting_offset_for(leaf));

        return adobe::any_regular_t(start_offset);
    } else if (name == value_endof) {
        if (parameter_set.empty())
            throw std::runtime_error("endof(): @field_name expected");

        inspection_branch_t   leaf(regular_to_branch(parameter_set[0]));
        inspection_position_t end_offset(ending_offset_for(leaf));

        return adobe::any_regular_t(end_offset);
    } else if (name == value_byte) {
        if (parameter_set.empty())
            throw std::runtime_error("byte(): offset expected");

        const adobe::any_regular_t& argument(parameter_set[0]);
        inspection_position_t       offset;

        if (argument.type_info() == typeid(double))
            offset = bytepos(argument.cast<double>());
        else // argument.type_info() == inspection_position_t
            offset = argument.cast<inspection_position_t>();

        input_m.seek(offset);

        rawbytes_t buffer(input_m.read(1));

        return adobe::any_regular_t(static_cast<double>(buffer[0]));
    } else if (name == value_peek) {
        std::size_t byte_count(1);
        std::size_t param_count(parameter_set.size());

        if (param_count > 0)
            byte_count = static_cast<std::size_t>(parameter_set[0].cast<double>());

        rawbytes_t buffer;

        {
            restore_point_t restore(input_m);

            buffer = input_m.read(byte_count);
        }

        if (param_count < 3)
            return byte_count > 1 ? adobe::any_regular_t(std::string(buffer.begin(), buffer.end())) :
                                    adobe::any_regular_t(static_cast<double>(buffer[0]));

        CONSTANT_VALUE(signed);
        CONSTANT_VALUE(unsigned);
        CONSTANT_VALUE(float);
        CONSTANT_VALUE(big);
        // CONSTANT_VALUE(little);

        adobe::name_t    type_name = parameter_set[1].cast<adobe::name_t>();
        adobe::name_t    endian_name = parameter_set[2].cast<adobe::name_t>();
        atom_base_type_t type = type_name == value_signed ?   atom_signed_k :
                                type_name == value_unsigned ? atom_unsigned_k :
                                type_name == value_float ?    atom_float_k :
                                                              atom_unknown_k;
        bool             endian = endian_name == value_big;

        return ::evaluate(buffer, buffer.size() * 8, type, endian);
    } else if (name == value_card) {
        if (parameter_set.empty())
            throw std::runtime_error("card(): @field_name expected");

        inspection_branch_t array(regular_to_branch(parameter_set[0]));

        if (!array->get_flag(is_array_root_k))
            throw std::runtime_error("card(): field is not an array");

        return adobe::any_regular_t(
            static_cast<double>(node_property(array, ARRAY_ROOT_PROPERTY_SIZE)));
    } else if (name == value_print) {
        // should be a printf-like behavior, to be able to output numbers etc.

        if (parameter_set.empty())
            throw std::runtime_error("print(): argument required");

        std::string result;

        for (adobe::array_t::const_iterator iter(parameter_set.begin()), last(parameter_set.end());
             iter != last;
             ++iter) {
            const std::type_info& type(iter->type_info());

            if (type == typeid(double))
                result += boost::lexical_cast<std::string>(iter->cast<double>());
            else if (type == typeid(std::string))
                result += iter->cast<std::string>();
            else
                result += type.name();
        }

        return adobe::any_regular_t(result);
    } else if (name == value_strcat) {
        if (parameter_set.empty())
            throw std::runtime_error("strcat(): argument required");

        std::string result;

        for (adobe::array_t::const_iterator iter(parameter_set.begin()), last(parameter_set.end());
             iter != last;
             ++iter)
            result += iter->cast<std::string>();

        return adobe::any_regular_t(result);
    } else if (name == value_summaryof) {
        if (parameter_set.size() != 1)
            throw std::runtime_error("summaryof(): takes one argument");

        return adobe::any_regular_t(regular_to_branch(parameter_set[0])->summary_m);
    } else if (name == value_str) {
        if (parameter_set.empty())
            throw std::runtime_error("str(): @field_name expected");

        inspection_branch_t   leaf(regular_to_branch(parameter_set[0]));
        inspection_position_t start_offset(starting_offset_for(leaf));
        inspection_position_t end_offset(ending_offset_for(leaf));
        inspection_position_t size(end_offset - start_offset + inspection_byte_k);

        if (node_property(leaf, NODE_PROPERTY_IS_CONST))
            throw std::runtime_error("str(): cannot take the string of a const");

        input_m.seek(start_offset);

        rawbytes_t str(input_m.read(size.bytes()));

        // This is a workaround I'm still not sure about; in the cases when we
        // obtain an array with the terminator: construct the terminator is
        // included. In the case of a null-terminated c-string we consider it
        // part of the array but do not want it included in the string_t. As
        // such we have a general exception case here where if the final
        // character of the string is 0 it is excluded.

        std::size_t strsize(str.size());

        if (strsize && str[strsize - 1] == 0)
            str.resize(strsize - 1);

        // If we have an atom that isn't an array root and it's little endian,
        // we need to reverse the contents.
        if (node_property(leaf, NODE_PROPERTY_IS_ATOM) &&
            !node_property(leaf, ATOM_PROPERTY_IS_BIG_ENDIAN) && !leaf->get_flag(is_array_root_k)) {
            adobe::reverse(str);
        }

        return adobe::any_regular_t(std::string(str.begin(), str.end()));
    } else if (name == value_path) {
        adobe::any_regular_t path("this"_name);

        if (!parameter_set.empty())
            path = parameter_set[0];

        inspection_branch_t leaf(regular_to_branch(path));

        return adobe::any_regular_t(std::string(build_path(main_branch_m, leaf)));
    } else if (name == value_indexof) {
        adobe::any_regular_t path("this"_name);

        if (!parameter_set.empty())
            path = parameter_set[0];

        inspection_branch_t leaf(regular_to_branch(path));

        if (!leaf->get_flag(is_array_element_k)) {
            adobe::name_t     name(node_property(leaf, NODE_PROPERTY_NAME));
            std::stringstream error;
            error << "indexof(): field '" << name << "' is not an array element";
            throw std::runtime_error(error.str());
        }

        return adobe::any_regular_t(
            static_cast<double>(node_value(leaf, ARRAY_ELEMENT_VALUE_INDEX)));
    } else if (name == value_fcc) {
        // converts an N character-code (commonly a four character code) to its
        // integer equivalent.

        if (parameter_set.empty())
            throw std::runtime_error("fcc(): character code expected");

        std::string fcc(parameter_set[0].cast<std::string>());
        std::size_t length(fcc.size());

        if (length > 4)
            throw std::runtime_error("fcc(): character code too long.");

        boost::uint32_t value(0);

        for (std::size_t i(0); i < length; ++i)
            value = (value << 8) | fcc[i];

        return adobe::any_regular_t(value);
    } else if (name == value_ptoi) {
        // converts the bytes portion of a pos_t to a double

        if (parameter_set.empty())
            throw std::runtime_error("ptoi(): position required");

        const adobe::any_regular_t& argument(parameter_set[0]);

        if (argument.type_info() != typeid(inspection_position_t))
            throw std::runtime_error("ptoi(): bad parameter type (expects a position)");

        // NOTE (fbrereto) : 64->32 truncation!
        boost::uint32_t value(argument.cast<inspection_position_t>().bytes());

        return adobe::any_regular_t(value);
    } else if (name == value_itoah) {
        // converts the value to a string in hex

        if (parameter_set.empty())
            throw std::runtime_error("itoah(): integer required");

        const adobe::any_regular_t& argument(parameter_set[0]);

        if (argument.type_info() != typeid(double))
            throw std::runtime_error("itoah(): bad parameter type (expects an integer)");

        std::stringstream ss;
        ss << std::hex << static_cast<std::size_t>(argument.cast<double>());
        auto s{ss.str()};

        std::transform(s.begin(), s.end(), s.begin(), [](auto c){ return std::toupper(c); });

        return adobe::any_regular_t("0x" + s);
    }  else if (name == value_itop) {
        // converts the bytes portion of a pos_t to a double

        if (parameter_set.empty())
            throw std::runtime_error("itop(): position required");

        const adobe::any_regular_t& argument(parameter_set[0]);

        if (argument.type_info() != typeid(double))
            throw std::runtime_error("itop(): bad parameter type (expects an integer)");

        inspection_position_t value(bytepos(argument.cast<double>()));

        return adobe::any_regular_t(value);
    } else if (name == value_padd) {
        // adds N values (position and/or double) and returns a position

        if (parameter_set.empty())
            throw std::runtime_error("padd(): argument required");

        inspection_position_t result;

        for (adobe::array_t::const_iterator iter(parameter_set.begin()), last(parameter_set.end());
             iter != last;
             ++iter) {
            if (iter->type_info() == typeid(double))
                result += bytepos(iter->cast<double>());
            else if (iter->type_info() == typeid(inspection_position_t))
                result += iter->cast<inspection_position_t>();
            else
                throw std::runtime_error("padd() : type of argument must be position or double");
        }

        return adobe::any_regular_t(result);
    } else if (name == value_psub) {
        // subs 2 values (position and/or double) and returns a position

        if (parameter_set.size() != 2)
            throw std::runtime_error("psub(): exactly 2 arguments required");

        inspection_position_t       result;
        const adobe::any_regular_t& op1(parameter_set[0]);
        const adobe::any_regular_t& op2(parameter_set[1]);

        if (op1.type_info() == typeid(double))
            result += bytepos(op1.cast<double>());
        else if (op1.type_info() == typeid(inspection_position_t))
            result += op1.cast<inspection_position_t>();
        else
            throw std::runtime_error("psub() : first argument must be position or double");

        if (op2.type_info() == typeid(double))
            result -= bytepos(op2.cast<double>());
        else if (op2.type_info() == typeid(inspection_position_t))
            result -= op2.cast<inspection_position_t>();
        else
            throw std::runtime_error("psub() : second argument must be position or double");

        return adobe::any_regular_t(result);
    } else if (name == value_gtell) {
        // returns the current read head position
        return adobe::any_regular_t(input_m.pos());
    } else if (name == value_utf16utf8) {
        if (parameter_set.empty())
            throw std::runtime_error("utf16utf8(): @field_name expected");

        inspection_branch_t leaf(regular_to_branch(parameter_set[0]));

        if (node_property(leaf, NODE_PROPERTY_IS_CONST))
            throw std::runtime_error("utf16utf8(): cannot take the string of a const");

        inspection_position_t start_offset(starting_offset_for(leaf));
        inspection_position_t end_offset(ending_offset_for(leaf));
        inspection_position_t size(end_offset - start_offset + inspection_byte_k);
        std::size_t byte_count{size.bytes()};
        std::size_t utf16_code_count{byte_count / 2};

        input_m.seek(start_offset);

        rawbytes_t str(input_m.read(size.bytes()));
        std::uint16_t* utf16_first{reinterpret_cast<std::uint16_t*>(&str[0])};
        std::uint16_t* utf16_last{utf16_first + utf16_code_count};

        // REVISIT: (fbrereto) The ability to get pointers to non-byte-sized atoms
        // that are properly swapped should be made generally available.
        // e.g. std::pair<T*, T*> transform_raw(rawbytes_t& raw);
        // then called like:
        // auto utf16_range = transform_raw<std::uint16_t>(raw);
#if BINSPECTOR_ENDIAN_LITTLE
        if (node_property(leaf, ATOM_PROPERTY_IS_BIG_ENDIAN))
            endian_swap(utf16_first, utf16_last);
#else
        if (!node_property(leaf, ATOM_PROPERTY_IS_BIG_ENDIAN))
            endian_swap(utf16_first, utf16_last);
#endif

        std::string utf8;
        adobe::copy_utf<std::uint8_t>(utf16_first, utf16_last, std::back_inserter(utf8));

        // chomp a null terminator if there is one.
        if (!utf8.empty() && utf8.back() == 0)
            utf8.pop_back();

        return adobe::any_regular_t(std::move(utf8));
    }

    throw std::runtime_error(adobe::make_string("Function '", name.c_str(), "' not found"));
}

/****************************************************************************************************/

} // namespace

/****************************************************************************************************/
#if 0
#pragma mark -
#endif
/****************************************************************************************************/

adobe::any_regular_t evaluate(const rawbytes_t& raw,
                              boost::uint64_t   bit_count,
                              atom_base_type_t  base_type,
                              bool              is_big_endian) {
    rawbytes_t byte_set(raw);

    host_to_endian(byte_set, is_big_endian);

    return convert_raw(byte_set, bit_count, base_type);
}

/****************************************************************************************************/

adobe::any_regular_t fetch_and_evaluate(bitreader_t&                 input,
                                        const inspection_position_t& location,
                                        boost::uint64_t              bit_count,
                                        atom_base_type_t             base_type,
                                        bool                         is_big_endian) {
    return evaluate(input.read_bits(location, bit_count), bit_count, base_type, is_big_endian);
}

/****************************************************************************************************/

template <>
adobe::any_regular_t finalize_lookup(inspection_branch_t root,
                                     inspection_branch_t branch,
                                     bitreader_t&        input,
                                     bool                finalize) {
    // converts our branch into its value when its an atom or a const.
    // can be bypassed by passing false to evaluate(), in which case
    // the evaluated result is always an inspection branch.

    adobe::any_regular_t result(branch);

    ++branch->use_count_m;

    // really slow, but good for debugging
    // std::cerr << "branch " << build_path(main_branch_m, branch) << " use count now " << branch->use_count_m <<'\n';

    if (!finalize) {
        // do nothing
    } else if (branch->get_flag(is_array_root_k) ||
               node_property(branch, NODE_PROPERTY_IS_STRUCT)) {
        // do nothing
    } else if (node_property(branch, NODE_PROPERTY_IS_ATOM)) {
        atom_base_type_t      base_type(node_property(branch, ATOM_PROPERTY_BASE_TYPE));
        bool                  is_big_endian(node_property(branch, ATOM_PROPERTY_IS_BIG_ENDIAN));
        boost::uint64_t       bit_count(node_property(branch, ATOM_PROPERTY_BIT_COUNT));
        inspection_position_t position(node_value(branch, ATOM_VALUE_LOCATION));

        // REVISIT (fbrereto) : Is there any reason why we wouldn't want to cache
        //                      the value of an atom like we do a const or slot?

        result = fetch_and_evaluate(input, position, bit_count, base_type, is_big_endian);
    } else if (node_property(branch, NODE_PROPERTY_IS_CONST) ||
               node_property(branch, NODE_PROPERTY_IS_SLOT)) {
        // We handle slots and consts the same way; at the time a signal is fired for the slot
        // a new expression is pushed into the slot's expression and the cache is invalidated.

        if (node_value(branch, CONST_VALUE_IS_EVALUATED) == false) {
            try {
                const adobe::array_t& const_expression(node_value(branch, CONST_VALUE_EXPRESSION));

                // Note (fbrereto): Evaluate at the point of the const declaration,
                //                  not at the current branch.
                branch->evaluated_value_m = contextual_evaluation_of<adobe::any_regular_t>(
                    const_expression, root, adobe::find_parent(branch), input);
                branch->evaluated_m = true;
            } catch (const std::exception& error) {
                // This pads out the current error with the location of the
                // expression being evaluated, hopefully giving the user a
                // cascade of lines directing them from the location of the
                // evaluation error to the line in the template that caused it.

                std::string details(error.what());

                // REVISIT (fbrereto) : String concatenation in this block

                details += adobe::make_string(", while evaluating constant '",
                                              node_property(branch, NODE_PROPERTY_NAME).c_str(),
                                              "'");

                if (branch.equal_node(inspection_branch_t()))
                    details += "\nin field: <<unknown>>";
                else
                    details += "\nin field: " + build_path(root, branch);

                throw evaluation_error_t(details);
            }
        }

        result = node_value(branch, CONST_VALUE_EVALUATED_VALUE);
    }

    return result;
}

/****************************************************************************************************/

template <>
adobe::any_regular_t contextual_evaluation_of(const adobe::array_t& expression,
                                              inspection_branch_t   main_branch,
                                              inspection_branch_t   current_node,
                                              bitreader_t&          input) {
    return contextual_evaluation_engine_t(expression, main_branch, current_node, input).evaluate();
}

template <>
inspection_branch_t contextual_evaluation_of(const adobe::array_t& expression,
                                             inspection_branch_t   main_branch,
                                             inspection_branch_t   current_node,
                                             bitreader_t&          input) {
    return contextual_evaluation_engine_t(expression, main_branch, current_node, input)
        .evaluate(false)
        .cast<inspection_branch_t>();
}

/****************************************************************************************************/

inspection_position_t starting_offset_for(inspection_branch_t branch) {
    bool is_struct(node_property(branch, type_struct_k));
    bool is_array_root(branch->get_flag(is_array_root_k));

    if (is_struct || is_array_root)
        return branch->start_offset_m;

    return node_value(branch, ATOM_VALUE_LOCATION);
}

/****************************************************************************************************/

inspection_position_t ending_offset_for(inspection_branch_t branch) {
    bool is_struct(node_property(branch, type_struct_k));
    bool is_array_root(branch->get_flag(is_array_root_k));

    if (is_struct || is_array_root)
        return branch->end_offset_m;

    inspection_position_t position(node_value(branch, ATOM_VALUE_LOCATION));
    boost::uint64_t       bit_count(node_property(branch, ATOM_PROPERTY_BIT_COUNT));

    return position + bitpos(bit_count) - inspection_byte_k;
}

/****************************************************************************************************/

std::string build_path(const_inspection_branch_t main, const_inspection_branch_t current) {
#if !BOOST_WINDOWS
    using std::isalpha;
#endif

    std::vector<std::string> result_set;

    result_set.reserve(10);

    while (true) {
        std::string path_segment;
        bool        is_array_element(current->get_flag(is_array_element_k));

        if (is_array_element) {
            boost::uint64_t index(node_value(current, ARRAY_ELEMENT_VALUE_INDEX));

            // REVISIT (fbrereto) : String concatenation here.
            path_segment += "[" + boost::lexical_cast<std::string>(index) + "]";
        } else {
            adobe::name_t name(node_property(current, NODE_PROPERTY_NAME));

            if (name)
                path_segment = name.c_str();
        }

        if (!path_segment.empty())
            result_set.push_back(path_segment);

        current.edge() = adobe::forest_leading_edge;

        if (current == main)
            break;

        current = adobe::find_parent(current);
    }

    std::string result;
    bool        first(true);

    for (std::vector<std::string>::reverse_iterator iter(result_set.rbegin()),
         last(result_set.rend());
         iter != last;
         ++iter) {
        if (!first && isalpha((*iter)[0]))
            result += ".";

        result += *iter;

        first = false;
    }

    return result;
}

/****************************************************************************************************/

attack_vector_set_t build_attack_vector_set(const inspection_forest_t& forest) {
    const_inspection_branch_t                    root(forest.begin());
    inspection_forest_t::const_preorder_iterator iter(root);
    inspection_forest_t::const_preorder_iterator last(forest.end());
    std::size_t                                  node_count(0);
    attack_vector_set_t                          result;

    for (; iter != last; ++iter, ++node_count) {
        const_inspection_branch_t iter_base(iter.base());
        const auto&               node(*iter_base);
        std::size_t               use_count(node.use_count_m);
        bool                      used_atom(use_count != 0 && node.bit_count_m != 0);
        bool shuffle_array(node.get_flag(is_array_root_k) && node.cardinal_m != 0 &&
                           node.shuffle_m);

        if (!used_atom && !shuffle_array)
            continue;

        if (node.type_m == atom_unknown_k)
            continue;

        // takes time to build this - only do so when needed.
        std::string path(build_path(root, iter_base));

        if (used_atom) {
            result.push_back(
                attack_vector_t(attack_vector_t::type_atom_usage_k, path, iter_base, use_count));
        } else if (shuffle_array) {
            result.push_back(
                attack_vector_t(attack_vector_t::type_array_shuffle_k, path, iter_base));
        }
    }

    adobe::sort(result);

    std::cerr << "Scanned " << node_count << " nodes\n";

    return result;
}

/****************************************************************************************************/


#include <stdio.h>
#include <cfloat>
#include <list>
#include <vector>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <numeric>
#include <boost/algorithm/clamp.hpp>
#include <random>
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "unsupported/Eigen/CXX11/src/Tensor/TensorDeviceCuda.h"
//#include "unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/common_shape_fns.h"
#include "tensorflow/core/util/work_sharder.h"
#include "bboxes.h"
#include "wtoolkit.h"
#include "wtoolkit_cuda.h"
#include <future>

using namespace tensorflow;
using namespace std;

typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
/*
 * max_box_nr: groundtruth box的最大可能数量, -1表示按需分配
 * gaussian_iou: 一般为0.7
 * gbboxes: groundtruth bbox, [B,N,4] 相对坐标
 * glabels: 标签[B,N], 背景为0
 * glength: 有效的groundtruth bbox数量
 * output_size: 输出图的大小[2]=(OH,OW)
 *
 * output:
 * output_heatmaps_tl: top left heatmaps [B,OH,OW,C]
 * output_heatmaps_br: bottom right heatmaps [B,OH,OW,C]
 * output_heatmaps_c: center heatmaps [B,OH,OW,C]
 * output_offset: positive point offset [B,max_box_nr,6] (ytl,xtl,ybr,xbr,yc,xc)
 * output_tags: positive point index [B,max_box_nr,3] (itl,ibr,ic)
 */
REGISTER_OP("CenterBoxesEncode")
    .Attr("T: {float,double,int32,int64}")
	.Attr("max_box_nr:int")
	.Attr("num_classes:int")
	.Attr("gaussian_iou:float")
    .Input("gbboxes: T")
    .Input("glabels: int32")
    .Input("glength: int32")
    .Input("output_size: int32")
	.Output("output_heatmaps_tl:T")
	.Output("output_heatmaps_br:T")
	.Output("output_heatmaps_c:T")
	.Output("output_offset:T")
	.Output("output_tags:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
            const auto input_shape0 = c->input(0);
            const auto batch_size = c->Dim(input_shape0,0);
            int num_classes;
            int max_box_nr;
            c->GetAttr("num_classes",&num_classes);
            c->GetAttr("max_box_nr",&max_box_nr);
            auto shape0 = c->MakeShape({batch_size,-1,-1,num_classes});
            auto shape1 = c->MakeShape({batch_size,max_box_nr,6});
            auto shape2 = c->MakeShape({batch_size,max_box_nr,3});

            for(auto i=0; i<3; ++i)
			    c->set_output(i, shape0);
			c->set_output(3, shape1);
			c->set_output(4, shape2);
			return Status::OK();
			});

template <typename Device,typename T>
class CenterBoxesEncodeOp: public OpKernel {
	public:
		explicit CenterBoxesEncodeOp(OpKernelConstruction* context) : OpKernel(context) {
			OP_REQUIRES_OK(context, context->GetAttr("max_box_nr", &max_box_nr_));
			OP_REQUIRES_OK(context, context->GetAttr("num_classes", &num_classes_));
			OP_REQUIRES_OK(context, context->GetAttr("gaussian_iou", &gaussian_iou_));
		}

		void Compute(OpKernelContext* context) override
        {
            TIME_THISV1("CenterBoxesEncode");
            const Tensor &_gbboxes    = context->input(0);
            const Tensor &_glabels    = context->input(1);
            const Tensor &_gsize      = context->input(2);
            auto          gbboxes     = _gbboxes.template tensor<T,3>();
            auto          glabels     = _glabels.template tensor<int,2>();
            auto          gsize       = _gsize.template tensor<int,1>();
            auto          output_size = context->input(3).template flat<int>().data();
            const auto    batch_size  = _gbboxes.dim_size(0);

            OP_REQUIRES(context, _gbboxes.dims() == 3, errors::InvalidArgument("box data must be 3-dimension"));
            OP_REQUIRES(context, _glabels.dims() == 2, errors::InvalidArgument("labels data must be 2-dimension"));
            OP_REQUIRES(context, _gsize.dims() == 1, errors::InvalidArgument("gsize data must be 1-dimension"));

            const auto    max_box_nr = max_box_nr_>0?max_box_nr_:*max_element(_gsize.template flat<int>().data(),_gsize.template flat<int>().data()+batch_size);
            int           dims_4d[4]            = {int(batch_size),output_size[0],output_size[1],num_classes_};
            int           dims_3d0[3]           = {int(batch_size),max_box_nr,6};
            int           dims_3d1[3]           = {int(batch_size),max_box_nr,3};
            TensorShape  outshape0;
            TensorShape  outshape1;
            TensorShape  outshape2;
            Tensor      *output_heatmaps_tl = NULL;
            Tensor      *output_heatmaps_br = NULL;
            Tensor      *output_heatmaps_c  = NULL;
            Tensor      *output_tags        = NULL;
            Tensor      *output_offset      = NULL;

            TensorShapeUtils::MakeShape(dims_4d, 4, &outshape0);
            TensorShapeUtils::MakeShape(dims_3d0, 3, &outshape1);
            TensorShapeUtils::MakeShape(dims_3d1, 3, &outshape2);

            OP_REQUIRES_OK(context, context->allocate_output(0, outshape0, &output_heatmaps_tl));
            OP_REQUIRES_OK(context, context->allocate_output(1, outshape0, &output_heatmaps_br));
            OP_REQUIRES_OK(context, context->allocate_output(2, outshape0, &output_heatmaps_c));
            OP_REQUIRES_OK(context, context->allocate_output(3, outshape1, &output_offset));
            OP_REQUIRES_OK(context, context->allocate_output(4, outshape2, &output_tags));

            auto heatmaps_tl = output_heatmaps_tl->template tensor<T,4>();
            auto heatmaps_br = output_heatmaps_br->template tensor<T,4>();
            auto heatmaps_c  = output_heatmaps_c->template tensor<T,4>();
            auto offsets     = output_offset->template tensor<T,3>();
            auto tags        = output_tags->template tensor<int,3>();
            tags.setZero();
            offsets.setZero();
            heatmaps_tl.setZero();
            heatmaps_br.setZero();
            heatmaps_c.setZero();
            for(auto i=0; i<batch_size; ++i) {
                for(auto j=0; j<gsize(i); ++j) {
                    const auto fytl = gbboxes(i,j,0)*(output_size[0]-1);
                    const auto fxtl = gbboxes(i,j,1)*(output_size[1]-1);
                    const auto fybr = gbboxes(i,j,2)*(output_size[0]-1);
                    const auto fxbr = gbboxes(i,j,3)*(output_size[1]-1);
                    const auto fyc = (fytl+fybr)/2;
                    const auto fxc = (fxtl+fxbr)/2;
                    const auto ytl = int(fytl+0.5);
                    const auto xtl = int(fxtl+0.5);
                    const auto ybr = int(fybr+0.5);
                    const auto xbr = int(fxbr+0.5);
                    const auto yc = int(fyc+0.5);
                    const auto xc = int(fxc+0.5);
                    const auto r0 = get_gaussian_radius(fybr-fytl,fxbr-fxtl,gaussian_iou_);
                    const auto label = glabels(i,j);
                    draw_gaussian(heatmaps_tl,xtl,ytl,r0,i,label);
                    draw_gaussian(heatmaps_br,xbr,ybr,r0,i,label);
                    draw_gaussian(heatmaps_c,fxc,fyc,r0,i,label,5);
                    offsets(i,j,0) = fytl-ytl;
                    offsets(i,j,1) = fxtl-xtl;
                    offsets(i,j,2) = fybr-ybr;
                    offsets(i,j,3) = fxbr-xbr;
                    offsets(i,j,4) = fyc-yc;
                    offsets(i,j,5) = fxc-xc;
                    tags(i,j,0) = ytl*output_size[1]+xtl;
                    tags(i,j,1) = ybr*output_size[1]+xbr;
                    tags(i,j,2) = yc*output_size[1]+xc;
                }
            }
        }
        template<typename DT>
        static void draw_gaussian(DT& data,int cx,int cy,float radius,int batch_index,int class_index,float delta=6,float k=1.0)
        {
            const auto width   = data.dimension(2);
            const auto height  = data.dimension(1);
            const auto xtl     = max(0,int(cx-radius));
            const auto ytl     = max(0,int(cy-radius));
            const auto xbr     = min<int>(width,int(cx+radius+1));
            const auto ybr     = min<int>(height,int(cy+radius+1));
            const auto sigma   = (2*radius+1)/delta;
            const auto c_index = class_index-1;

            for(auto x=xtl; x<xbr; ++x) {
                for(auto y=ytl; y<ybr; ++y) {
                    auto dx = x-cx;
                    auto dy = y-cy;
                    auto v = exp(-(dx*dx+dy*dy)/(2*sigma*sigma))*k;
                    data(batch_index,y,x,c_index) = max(data(batch_index,y,x,c_index),v);
                }
            }
        }
	private:
        int max_box_nr_;
        int num_classes_;
        float gaussian_iou_ = 0.7f;
};
REGISTER_KERNEL_BUILDER(Name("CenterBoxesEncode").Device(DEVICE_CPU).TypeConstraint<float>("T"), CenterBoxesEncodeOp<CPUDevice, float>);

/*
 *
 */
REGISTER_OP("CenterBoxesDecode")
    .Attr("T: {float,double,int32,int64}")
    .Attr("k:int")
    .Input("heatmaps_tl: T")
    .Input("heatmaps_br: T")
    .Input("heatmaps_c: T")
    .Input("offset_tl: T")
    .Input("offset_br: T")
    .Input("offset_c: T")
	.Output("output_bboxes:T")
	.Output("output_labels:int32")
	.Output("output_probs:T")
	.Output("output_index:int32")
	.Output("output_lens:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
            const auto input_shape0 = c->input(0);
            const auto batch_size = c->Dim(input_shape0,0);
            auto shape0 = c->MakeShape({batch_size,-1,4});
            auto shape1 = c->MakeShape({batch_size,-1});
            auto shape2 = c->MakeShape({batch_size});

			c->set_output(0, shape0);
			c->set_output(1, shape1);
			c->set_output(2, shape1);
			c->set_output(3, shape1);
			c->set_output(4, shape2);
			return Status::OK();
			});

template <typename Device,typename T>
class CenterBoxesDecodeOp: public OpKernel {
    private:
        struct InterData{
            InterData(int i,float s):index(i),score(s){}
            int index;
            float score;
            float y;
            float x;
            bool operator<(const InterData& rhv)const{
                return score<rhv.score;
            }
        };
        struct Box
        {
            float ymin;
            float xmin;
            float ymax;
            float xmax;
            float prob;
            int index;
        };
    public:
        explicit CenterBoxesDecodeOp(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("k", &k_));
        }
        void Compute(OpKernelContext* context) override
        {
            TIME_THISV1("CenterBoxesDecode");
            const Tensor &_heatmaps_tl = context->input(0);
            const Tensor &_heatmaps_br = context->input(1);
            const Tensor &_heatmaps_c  = context->input(2);
            const Tensor &_offset_tl   = context->input(3);
            const Tensor &_offset_br   = context->input(4);
            const Tensor &_offset_c    = context->input(5);
            auto          heatmaps_tl  = _heatmaps_tl.template tensor<T,4>();
            auto          heatmaps_br  = _heatmaps_br.template tensor<T,4>();
            auto          heatmaps_c   = _heatmaps_c.template tensor<T,4>();
            auto          offset_tl    = _offset_tl.template tensor<T,4>();
            auto          offset_br    = _offset_br.template tensor<T,4>();
            auto          offset_c     = _offset_c.template tensor<T,4>();

            const auto    batch_size  = _heatmaps_tl.dim_size(0);
            const auto num_classes = _heatmaps_tl.dim_size(3);
            const auto dw = 1.0/(heatmaps_tl.dimension(2)-1);
            const auto dh = 1.0/(heatmaps_tl.dimension(1)-1);
            const auto H = heatmaps_tl.dimension(1);
            const auto W = heatmaps_tl.dimension(2);

            OP_REQUIRES(context, _heatmaps_tl.dims() == 4, errors::InvalidArgument("heatmap data must be 4-dimension"));
            OP_REQUIRES(context, _heatmaps_br.dims() == 4, errors::InvalidArgument("heatmap data must be 4-dimension"));
            OP_REQUIRES(context, _heatmaps_c.dims() == 4, errors::InvalidArgument("heatmap data must be 4-dimension"));
            OP_REQUIRES(context, _offset_tl.dims() == 4, errors::InvalidArgument("offset data must be 4-dimension"));
            OP_REQUIRES(context, _offset_br.dims() == 4, errors::InvalidArgument("offset data must be 4-dimension"));
            OP_REQUIRES(context, _offset_c.dims() == 4, errors::InvalidArgument("offset data must be 4-dimension"));

            list<vector<Box>> res_boxes;
            list<vector<int>> res_labels;
            for(auto i=0; i<batch_size; ++i) {
                vector<Box> boxes;
                vector<int> labels;
                for(auto j=0; j<num_classes; ++j) {
                    vector<InterData> tl;
                    vector<InterData> br;
                    vector<InterData> c;
                    tl.reserve(k_);
                    br.reserve(k_);
                    c.reserve(k_);
                    tl = get_top_k(heatmaps_tl,i,j,k_);
                    br = get_top_k(heatmaps_br,i,j,k_);
                    c  = get_top_k(heatmaps_c,i,j,k_);
                    fill_pos(offset_tl,tl,i,j);
                    fill_pos(offset_br,br,i,j);
                    fill_pos(offset_c,c,i,j);
                    auto tboxes = get_boxes(tl,br,c);
                    vector<int> tlabels(tboxes.size(),j);
                    boxes.insert(boxes.end(),tboxes.begin(),tboxes.end());
                    labels.insert(labels.end(),tlabels.begin(),tlabels.end());
                }
                res_boxes.push_back(boxes);
                res_labels.push_back(labels);
            }
            auto box_nr = max_element(res_boxes.begin(),res_boxes.end(),[](const auto& lhv,const auto& rhv){ return lhv.size()<rhv.size();})->size();
            int           dims_3d[3]            = {int(batch_size),box_nr,4};
            int           dims_2d[2]           = {int(batch_size),box_nr};
            int           dims_1d[1]           = {int(batch_size)};
            TensorShape  outshape0;
            TensorShape  outshape1;
            TensorShape  outshape2;
            Tensor      *output_boxes = NULL;
            Tensor      *output_labels = NULL;
            Tensor      *output_probs = NULL;
            Tensor      *output_indexs = NULL;
            Tensor      *output_lens = NULL;

            TensorShapeUtils::MakeShape(dims_3d, 3, &outshape0);
            TensorShapeUtils::MakeShape(dims_2d, 2, &outshape1);
            TensorShapeUtils::MakeShape(dims_1d, 1, &outshape2);

            OP_REQUIRES_OK(context, context->allocate_output(0, outshape0, &output_boxes));
            OP_REQUIRES_OK(context, context->allocate_output(1, outshape1, &output_labels));
            OP_REQUIRES_OK(context, context->allocate_output(2, outshape1, &output_probs));
            OP_REQUIRES_OK(context, context->allocate_output(3, outshape1, &output_indexs));
            OP_REQUIRES_OK(context, context->allocate_output(4, outshape2, &output_lens));

            auto o_boxes = output_boxes->template tensor<T,3>();
            auto o_labels = output_labels->template tensor<int,2>();
            auto o_probs = output_probs->template tensor<T,2>();
            auto o_indexs = output_indexs->template tensor<int,2>();
            auto o_lens = output_lens->template tensor<int,1>();

            o_boxes.setZero();
            o_labels.setZero();
            o_lens.setZero();

            for(auto i=0; i<batch_size; ++i) {
                auto b_it = next(res_boxes.begin(),i);
                auto l_it = next(res_labels.begin(),i);
                for(auto j=0; j<b_it->size(); ++j) {
                    auto& box = (*b_it)[j];
                    o_boxes(i,j,0) = box.ymin;
                    o_boxes(i,j,1) = box.xmin;
                    o_boxes(i,j,2) = box.ymax;
                    o_boxes(i,j,3) = box.xmax;
                    o_labels(i,j) = (*l_it)[j];
                    o_probs(i,j) = box.prob;
                    o_indexs(i,j) = box.index;
                }
                o_lens(i) = b_it->size();
            }
        }
        template<typename DT>
            vector<InterData> get_top_k(const DT& heatmaps,int batch_index,int class_index,int k) {
                const auto H = heatmaps.dimension(1);
                const auto W = heatmaps.dimension(2);
                vector<InterData> res;
                res.reserve(H*W);
                for(auto y=0; y<H; ++y) {
                    for(auto x=0; x<W; ++x) {
                        res.emplace_back(y*W+x,heatmaps(batch_index,y,x,class_index));
                    }
                }
                auto it = partition(res.begin(),res.end(),[this](auto v){ return v.score>threshold_;});
                res.erase(it,res.end());
                auto mid = res.begin()+std::min<int>(k,res.size());
                partial_sort(res.begin(),mid,res.end(),[](const auto& lhv,const auto& rhv){ return lhv.score>rhv.score;});
                res.erase(mid,res.end());
                return res;
            }
        template<typename DT>
            void fill_pos(const DT& offsets,vector<InterData>& data,int batch_index,int class_index) {
                const auto H = offsets.dimension(1);
                const auto W = offsets.dimension(2);
                const auto dw = 1.0/(H-1);
                const auto dh = 1.0/(W-1);
                int y;
                int x;

                for(auto&d :data) {
                    std::tie(y,x) = index_to_yx(d.index,H,W);
                    auto yoffset = offsets(batch_index,y,x,0)*dh;
                    auto xoffset = offsets(batch_index,y,x,1)*dw;
                    d.y = float(y)/(H-1)+yoffset;
                    d.x = float(x)/(W-1)+xoffset;
                }
            }
        vector<Box> get_boxes(const vector<InterData>& tl,const vector<InterData>& br,const vector<InterData>& c) {
            const auto nr_i = tl.size();
            const auto nr_j = br.size();
            const auto nr_k = c.size();
            Eigen::Tensor<float,2,Eigen::RowMajor> scores(nr_i,nr_j);
            Eigen::Tensor<int,2,Eigen::RowMajor> c_index(nr_i,nr_j);

            scores.setConstant(-1.0);
            c_index.setZero();

            for(auto i=0; i<nr_i; ++i) {
                const auto& tl_d = tl[i];
                for(auto j=0; j<nr_j; ++j) {
                    const auto& br_d = br[j];
                    float box[4] = {tl_d.y,tl_d.x,br_d.y,br_d.x};

                    if((box[0]>=box[2]) || (box[1]>=box[3]))
                        continue;

                    for(auto k=0; k<nr_k; ++k) {
                        const auto& c_d = c[k];
                        if(is_in_box_center(box,c_d.y,c_d.x,1)) {
                            auto score = 1.0 - (box[2]-box[0])*(box[3]-box[1]);
                            if(score>scores(i,j)) {
                                scores(i,j) = score;
                                c_index(i,j) = k;
                            }
                        }
                    }
                }
            }
            vector<Box> boxs;
            const auto loop_nr = min({nr_i,nr_j,nr_k});
            for(auto x=0; x<loop_nr; ++x) {
                auto max_i = 0;
                auto max_j = 0;
                auto max_score = -1.0;
                for(auto i=0; i<nr_i; ++i) {
                    for(auto j=0; j<nr_j; ++j) {
                        if(scores(i,j)>max_score) {
                            max_score = scores(i,j);
                            max_i = i;
                            max_j = j;
                        }
                    }
                }
                if(max_score<0) break;
                Box box;
                box.ymin = tl[max_i].y;
                box.xmin = tl[max_i].x;
                box.ymax = br[max_j].y;
                box.xmax = br[max_j].x;
                const auto k = c_index(max_i,max_j);
                box.index = c[k].index;
                box.prob = (tl[max_i].score+br[max_j].score+c[k].score)/3.0f;
                boxs.push_back(box);
                for(auto j=0; j<nr_j; ++j)
                    scores(max_i,j) = -1.0;
                for(auto i=0; i<nr_i; ++i)
                    scores(i,max_j) = -1.0;
            }
            return boxs;
        }
       static inline bool is_in_box_center(const float* box,float y,float x,int grid_size=3) {
           const auto h = box[2]-box[0];
           const auto w = box[3]-box[1];
           const auto h_grid_size = float(int(grid_size/2))/grid_size;
           if((y<box[0]+h*h_grid_size)||
                   (y>box[2]-h*h_grid_size)||
                   (x<box[1]+w*h_grid_size) ||
                   (x>box[3]-w*h_grid_size))
               return false;
           return true;
       }
        static inline pair<int,int> index_to_yx(int index,int H,int W) {
            return make_pair(index/W,index%W);
        }
	private:
        int k_ = 0;
        float threshold_ = 1e-3;
};
REGISTER_KERNEL_BUILDER(Name("CenterBoxesDecode").Device(DEVICE_CPU).TypeConstraint<float>("T"), CenterBoxesDecodeOp<CPUDevice, float>);

REGISTER_OP("MakeNegPairIndex")
    .Input("mask: bool")
	.Output("index0:int32")
	.SetShapeFn([](shape_inference::InferenceContext* c) {
            const auto input_shape1 = c->input(0);
            const auto batch_size = c->Dim(input_shape1,0);
            const auto data_nr = c->Value(c->Dim(input_shape1,1));
            const auto out_nr = data_nr>0?data_nr*(data_nr-1)/2:-1;
            auto shape0 = c->MakeShape({batch_size,out_nr,2});

			c->set_output(0, shape0);
			return Status::OK();
			});

template <typename Device>
class MakeNegPairIndexOp: public OpKernel {
	public:
		explicit MakeNegPairIndexOp(OpKernelConstruction* context) : OpKernel(context) {
		}

		void Compute(OpKernelContext* context) override
        {
            TIME_THISV1("CenterBoxesEncode");
            const Tensor &_mask = context->input(0);

            OP_REQUIRES(context, _mask.dims() == 2, errors::InvalidArgument("mask data must be 2-dimension"));

            auto         mask       = _mask.template tensor<bool,2>();
            const auto   batch_size = _mask.dim_size(0);
            const auto   data_nr    = _mask.dim_size(1);
            int          dims_3d[]  = {int(batch_size),data_nr *(data_nr-1)/2,2};
            TensorShape  outshape0;
            Tensor      *index      = nullptr;

            TensorShapeUtils::MakeShape(dims_3d, 3, &outshape0);

            OP_REQUIRES_OK(context, context->allocate_output(0, outshape0, &index));

            auto index_tensor = index->template tensor<int,3>();

            index_tensor.setConstant(-1);

            for(auto i=0; i<batch_size; ++i) {
                int j = 0;
                for(j=0; j<data_nr; ++j)
                    if(!mask(i,j))
                        break;
                auto idx = 0;
                for(auto l=0; l<j; ++l) {
                    for(auto m=l+1; m<j; ++m) {
                        index_tensor(i,idx,0) = l;
                        index_tensor(i,idx,1) = m;
                        ++idx;
                    }
                }
            }
        }
};
REGISTER_KERNEL_BUILDER(Name("MakeNegPairIndex").Device(DEVICE_CPU), MakeNegPairIndexOp<CPUDevice>);

/*
 * nrs = [3,5]
 * bboxes: [B,N,4]. (ymin,xmin,ymax,xmax)
 * center_points: [B,M] (y,x)
 * bboxes_length: [B]
 * size_threshold: ()
 * output:
 * mask:[B,N,4]正确的True, 错误的False
 */
REGISTER_OP("CenterBoxesFilter")
    .Attr("T: {float,double}")
    .Attr("nrs: list(int)")
    .Input("bboxes: T")
    .Input("bboxes_clses: int32")
    .Input("center_points: T")
    .Input("center_clses: int32")
    .Input("bboxes_length: int32")
    .Input("size_threshold: T")
    .Output("mask: int32")
    .SetShapeFn([](shape_inference::InferenceContext* c) {
            const auto input_shape0 = c->input(0);
            const auto batch_size = c->Dim(input_shape0,0);
            const auto box_nr = c->Dim(input_shape0,1);
            auto shape0 = c->MakeShape({batch_size,box_nr});
            c->set_output(0, shape0);
            return Status::OK();
            });

template <typename Device,typename T>
class CenterBoxesFilterOp: public OpKernel {
    public:
        explicit CenterBoxesFilterOp(OpKernelConstruction* context) : OpKernel(context) {
            OP_REQUIRES_OK(context, context->GetAttr("nrs", &nrs_));
        }

        void Compute(OpKernelContext* context) override
        {
            TIME_THISV1("CenterBoxesFilter");
            const Tensor &_bboxes             = context->input(0);
            const Tensor &_bboxes_clses       = context->input(1);
            const Tensor &_center_points      = context->input(2);
            const Tensor &_center_clses       = context->input(3);
            const Tensor &_bboxes_length      = context->input(4);
            const Tensor &_size_threshold     = context->input(5);
            auto          bboxes              = _bboxes.template tensor<T,3>();
            auto          bboxes_clses        = _bboxes_clses.template tensor<int,2>();
            auto          center_points       = _center_points.template tensor<T,3>();
            auto          center_clses        = _center_clses.template tensor<int,2>();
            auto          bboxes_length       = _bboxes_length.template tensor<int,1>();
            auto          size_threshold      = _size_threshold.template flat<T>().data()[0];
            const auto    real_size_threshold = size_threshold *size_threshold;
            const auto    batch_size          = _bboxes.dim_size(0);
            const auto    data_nr             = _bboxes.dim_size(1);
            int           dims_2d[]           = {int(batch_size),data_nr};
            TensorShape   outshape0;
            Tensor       *mask                = nullptr;

            TensorShapeUtils::MakeShape(dims_2d, 2, &outshape0);

            OP_REQUIRES_OK(context, context->allocate_output(0, outshape0, &mask));

            auto mask_tensor = mask->template tensor<int,2>();

            mask_tensor.setConstant(false);

            for(auto i=0; i<batch_size; ++i) {
                const auto nr = bboxes_length(i);
                const auto t_bboxes = bboxes.chip(i,0);
                for(auto j=0; j<nr; ++j) {
                    Eigen::Tensor<T,1,Eigen::RowMajor> bbox = t_bboxes.chip(j,0);
                    const auto bbox_cls = bboxes_clses(i,j);
                    mask_tensor(i,j) = is_in_center(bbox,bbox_cls,center_points,center_clses,real_size_threshold,i);
                }
            }
       }
       template<typename BT0,typename BT1,typename BT2>
       int is_in_center(const BT0& box, const int bbox_cls,const BT1& center_points, const BT2& center_clses,const T size_threshold,int batch_index)
       {
           const auto box_size = (box(2)-box(0))*(box(3)-box(1));
           int n = 0;
           if(box_size>size_threshold) {
               n = nrs_[1];
           } else {
               n = nrs_[0];
           }
           const auto ctlx = ((n+1)*box(1)+(n-1)*box(3))/(n<<1);
           const auto ctly = ((n+1)*box(0)+(n-1)*box(2))/(n<<1);
           const auto cbrx = ((n-1)*box(1)+(n+1)*box(3))/(n<<1);
           const auto cbry = ((n-1)*box(0)+(n+1)*box(2))/(n<<1);
           const auto data_nr = center_points.dimension(1);

           for(auto i=0; i<data_nr; ++i) {
               const auto px = center_points(batch_index,i,1);
               const auto py = center_points(batch_index,i,0);
               if((bbox_cls==center_clses(batch_index,i)) &&
                   (px>ctlx) && (px<cbrx) && (py>ctly) && (py<cbry))
                   return i;
           }
           return -1;
       }
    private:
        vector<int> nrs_;
};
REGISTER_KERNEL_BUILDER(Name("CenterBoxesFilter").Device(DEVICE_CPU).TypeConstraint<float>("T"), CenterBoxesFilterOp<CPUDevice, float>);
REGISTER_KERNEL_BUILDER(Name("CenterBoxesFilter").Device(DEVICE_CPU).TypeConstraint<double>("T"), CenterBoxesFilterOp<CPUDevice, double>);


#ifndef JHMI_VOLUME_IMAGE_HPP_NRC20150429
#define JHMI_VOLUME_IMAGE_HPP_NRC20150429

#include "cube.hpp"
#include "range/v3/view.hpp"
#include "range/v3/core.hpp"
#include "range/v3/view_facade.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <fmt/ostream.h>
#include <fstream>

namespace jhmi {
  namespace jhmi_detail {
    template <typename T, typename P, typename V> class by_location_view;
    inline std::string to_dtype(float) { return "float32"; }
    inline std::string to_dtype(double) { return "float64"; }
    inline std::string to_dtype(std::uint8_t) { return "uint8"; }
    inline std::string to_dtype(std::uint32_t) { return "uint32"; }
    inline std::string to_dtype(std::int32_t) { return "int32"; }
    template <typename U, typename T>
    inline std::string to_dtype(boost::units::quantity<U,T>) { return to_dtype(T{}); }
  }

  template <typename T>
  class volume_image {
    using reference = T&;
    using const_reference = T const&;

  public:
    struct physical_pixel_ref {
      physical_pixel_ref(m3 const& pp, int3 const&, reference v) : loc{pp}, value{v} {}
      m3 const loc;
      reference value;
    };
    struct image_pixel_ref {
      image_pixel_ref(m3 const&, int3 const& ip, reference v) : loc{ip}, value{v} {}
      int3 const loc;
      reference value;
    };
    struct pixel_ref {
      pixel_ref(m3 const& pp, int3 const& ip, reference v) : physical_loc{pp}, image_loc{ip}, value{v} {}
      m3 const physical_loc;
      int3 const image_loc;
      reference value;
    };
    struct physical_pixel {
      physical_pixel(m3 const& pp, int3 const&, const_reference v) : loc{pp}, value{v} {}
      m3 const loc;
      const_reference value;
    };
    struct image_pixel {
      image_pixel(m3 const&, int3 const& ip, const_reference v) : loc{ip}, value{v} {}
      int3 const loc;
      const_reference value;
    };
    struct pixel {
      pixel(m3 const& pp, int3 const& ip, const_reference v) : physical_loc{pp}, image_loc{ip}, value{v} {}
      m3 const physical_loc;
      int3 const image_loc;
      const_reference value;
    };

    volume_image() = default;
    volume_image(int3 const& size, cube<m3> const& physical_extents)
      : physical_extents_(physical_extents),
        w_(size.x), h_(size.y), d_(size.z),
        volume_(new T[w_*h_*d_]()),//Note, we default initialize each element of volume_
        half_pixel_sizes_{element_divide(jhmi::dimensions(physical_extents), 2. * size)}
    {}

    explicit volume_image(boost::filesystem::path const& filename) {
      std::ifstream file(filename.string(), std::ios::binary);
      if (!file.is_open()) {
        throw std::runtime_error(fmt::format(
          "Invalid file \"{}\" provided to volume_image", filename.string()));
      }
      boost::iostreams::filtering_istream in;
      in.push(boost::iostreams::zlib_decompressor{});
      in.push(file);
      std::string line;
      getline(in, line);
      std::vector<std::string> e;
      boost::split(e, line, boost::is_any_of(" "), boost::token_compress_on);
      physical_extents_ = cube<m3>{dbl3{stod(e[0]), stod(e[1]), stod(e[2])}*mm,
                                   dbl3{stod(e[3]), stod(e[4]), stod(e[5])}*mm};
      getline(in, line);
      w_ = stoi(line);
      getline(in, line);
      h_ = stoi(line);
      getline(in, line);
      d_ = stoi(line);
      volume_.reset(new T[w_ * h_ * d_]);
      getline(in, line);
      if (line != jhmi_detail::to_dtype(T{})) {
        throw std::invalid_argument(fmt::format(
          "Invalid attempt to construct {} from {}", jhmi_detail::to_dtype(T()), line));
      }
      in.read(reinterpret_cast<char*>(volume_.get()), w_ * h_ * d_ * sizeof(T));
      half_pixel_sizes_ = element_divide(
        jhmi::dimensions(physical_extents_), 2. * int3{w_,h_,d_});
    }

    dbl3 to_index(m3 const& pt) const {
      return dbl3(element_divide(
        element_multiply(pt - physical_extents_.ul() - half_pixel_sizes_, dbl3(w_,h_,d_)),
        jhmi::dimensions(physical_extents_)));
    }

    reference operator()(m3 const& pt) {
      return (*this)(int3(to_index(pt)));
    }
    const_reference operator()(m3 const& pt) const {
      return (*this)(int3(to_index(pt)));
    }

    reference operator()(int x, int y, int z) {
      assert(x + y * w_ + z * w_ * h_ < w_ * h_ * d_);
      return volume_[x + y * w_ + z * w_ * h_];
    }
    reference operator()(int3 const& pt) { return (*this)(pt.x, pt.y, pt.z); }
    const_reference operator()(int x, int y, int z) const {
      assert(x + y * w_ + z * w_ * h_ < w_ * h_ * d_);
      return volume_[x + y * w_ + z * w_ * h_];
    }
    const_reference operator()(int3 const& pt) const { return (*this)(pt.x, pt.y, pt.z); }

    //TODO: Is this returning a pointer to const?  I don't think so...
    auto begin() const { return volume_.get(); }
    auto begin() { return volume_.get(); }

    auto end() const { return volume_.get() + w_ * h_ * d_; }
    auto end() { return volume_.get() + w_ * h_ * d_; }

    int width() const { return w_; }
    int height() const { return h_; }
    int depth() const { return d_; }

    int3 dimensions() const { return {w_, h_, d_}; }

    void write(boost::filesystem::path const& filename) const {
      std::ofstream file{filename.string(), std::ios::binary};
      boost::iostreams::filtering_ostream out;
      out.push(boost::iostreams::zlib_compressor{});
      out.push(file);
      out << fmt::format("{} {} {} {} {} {}\n{}\n{}\n{}\n{}\n",
        physical_extents_.ul().x.value()*1000,
        physical_extents_.ul().y.value()*1000,
        physical_extents_.ul().z.value()*1000,
        physical_extents_.lr().x.value()*1000,
        physical_extents_.lr().y.value()*1000,
        physical_extents_.lr().z.value()*1000,
        width(), height(), depth(), jhmi_detail::to_dtype(T()));
      write_binary(out);
    }

  private:
    template <typename Stream>
    void write_binary(Stream& out) const {
      out.write(reinterpret_cast<char const*>(volume_.get()),
                w_ * h_ * d_ * sizeof(T));
    }

    template <typename U, typename P, typename V> friend class jhmi_detail::by_location_view;
    friend cube<m3> extents(volume_image<T> const& v) {
      return v.physical_extents_;
    }
    cube<m3> physical_extents_;
    int w_, h_, d_;
    std::unique_ptr<T[]> volume_;
    m3 half_pixel_sizes_;
  };

  namespace jhmi_detail {
  template <typename T, typename P, typename V>
  class by_location_view : public ranges::view_facade<by_location_view<T,P,V>> {
  public:
    friend struct ranges::range_access;
    V* img_;
    m3 step_;
    m3 center_;
    struct cursor {
      V* img_;
      m3 mul_;
      m3 step_;
      int3 iul_;
      int3 ilr_;
      int3 ipt_;

      cursor() = default;

      bool equal(cursor const& other) const { return ipt_ == other.ipt_; }
      auto read() const {
        return P{mul_ + element_multiply(dbl3{ipt_}, step_), ipt_, (*img_)(ipt_)};
      }
      void next() {
        if (++ipt_.x >= ilr_.x) {
          ipt_.x = iul_.x;
          if (++ipt_.y >= ilr_.y) {
            ipt_.y = iul_.y;
            ++ipt_.z;
          }
        }
      }
    };

    cursor make_cursor(bool do_ul) const {
      auto pe = img_->physical_extents_;
      auto d = img_->dimensions();
      int3 iul{0,0,0};
      int3 iend{0,0,d.z};
      auto ipt = do_ul ? iul : iend;

      auto hstep = step_ / 2.;
      auto mul = element_multiply(ceil(element_divide(pe.ul() - center_, hstep)), hstep) + center_;
      return {img_, mul + hstep, step_, iul, d, ipt};
    }

  public:
    cursor begin_cursor() const {
      return make_cursor(true);
    }
    cursor end_cursor() const {
      return make_cursor(false);
    }

    explicit by_location_view(V& img) : img_(&img),
      step_(element_divide(dimensions(extents(img)), dbl3{img.dimensions()})),
      center_{extents(img).ul() + step_ / 2.} {}
    by_location_view(V& img, m3 const& step, m3 const& center)
      : img_(&img), step_(step), center_{center} {}
  };
  }//jhmi_detail

  template <typename T>
  struct by_const_physical_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::physical_pixel, volume_image<T> const> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::physical_pixel, volume_image<T> const>::by_location_view;
  };
  template <typename T>
  struct by_physical_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::physical_pixel_ref, volume_image<T>> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::physical_pixel_ref, volume_image<T>>::by_location_view;
  };
  template <typename T>
  struct by_const_image_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::image_pixel, volume_image<T> const> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::image_pixel, volume_image<T> const>::by_location_view;
  };
  template <typename T>
  struct by_image_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::image_pixel_ref, volume_image<T>> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::image_pixel_ref, volume_image<T>>::by_location_view;
  };
  template <typename T>
  struct by_const_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::pixel, volume_image<T> const> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::pixel, volume_image<T> const>::by_location_view;
  };
  template <typename T>
  struct by_location_view : jhmi_detail::by_location_view<T, typename volume_image<T>::pixel_ref, volume_image<T>> {
    using jhmi_detail::by_location_view<T, typename volume_image<T>::pixel_ref, volume_image<T>>::by_location_view;
  };

  namespace view {
    struct by_physical_location_fn {
      template <typename T> auto operator()(volume_image<T> const& img) const {
        return by_const_physical_location_view<T>{img};
      }
      template <typename T> auto operator()(volume_image<T>& img) const {
        return by_physical_location_view<T>{img};
      }
    };
    template <typename T> auto operator|(T& img, by_physical_location_fn fn) {
      return fn(img);
    }
    struct by_image_location_fn {
      template <typename T> auto operator()(volume_image<T> const& img) const {
        return by_const_image_location_view<T>{img};
      }
      template <typename T> auto operator()(volume_image<T>& img) const {
        return by_image_location_view<T>{img};
      }
    };
    template <typename T> auto operator|(T& img, by_image_location_fn fn) {
      return fn(img);
    }
    struct by_location_fn {
      template <typename T> auto operator()(volume_image<T> const& img) const {
        return by_const_location_view<T>{img};
      }
      template <typename T> auto operator()(volume_image<T>& img) const {
        return by_location_view<T>{img};
      }
    };
    template <typename T> auto operator|(T& img, by_location_fn fn) {
      return fn(img);
    }
    constexpr by_image_location_fn by_image_location{};
    constexpr by_physical_location_fn by_physical_location{};
    constexpr by_location_fn by_location{};

    struct equal_step {
      m step_;
      m3 center_;
      explicit equal_step(m step, m3 const& center = m3{}) : step_{step}, center_{center} {}
      template <typename T> auto operator()(volume_image<T> const& img) const {
        return by_const_location_view<T>(img, dbl3{1,1,1}*step_, center_);
      }
      template <typename T> auto operator()(volume_image<T>& img) const {
        return by_location_view<T>(img, dbl3{1,1,1}*step_, center_);
      }
    };
    template <typename T> auto operator|(T& img, equal_step fn) {
      return fn(img);
    }

    struct x_slice {
      int x1, x2, y, z;
      x_slice(int x1, int x2, int y, int z) : x1(x1), x2(x2), y(y), z(z) {}

      template <typename T> auto operator()(volume_image<T> const& img) const {
        return ranges::make_iterator_range(&img(x1,y,z), &img(x2,y,z));
      }
      template <typename T> auto operator()(volume_image<T>& img) const {
        return ranges::make_iterator_range(&img(x1,y,z), &img(x2,y,z));
      }
    };
    template <typename T> auto operator|(T& img, x_slice fn) {
      return fn(img);
    }
  }

  template <typename T>
  auto gaussian_filter(volume_image<T>& in, double sigma, int num_dev = 3) {
    volume_image<double> out{in.dimensions(), extents(in)};
    volume_image<double> tmp{in.dimensions(), extents(in)};
    volume_image<double> tmp1{in.dimensions(), extents(in)};
    int range = static_cast<int>(std::ceil(num_dev * sigma));
    auto filter = ranges::view::closed_indices(-range, range)
      | ranges::view::transform([&](int x) {
           return /*1. / (sigma * 2.506628274) */ std::exp(-x*x/(2*sigma*sigma)); })
      | ranges::to_vector;

    auto ds = inflate(cube<int3>{int3{}, in.dimensions()}, -range*int3{1,1,1});
    traverse(ds, 1, [&](int3 const& pt) {
      for (int i = 0; i < filter.size(); ++i) {
        auto new_pt = pt + int3{i-range,0,0};
        tmp1(pt) += in(new_pt) * filter[i];
      }
    });
    traverse(ds, 1, [&](int3 const& pt) {
      for (int i = 0; i < filter.size(); ++i) {
        auto new_pt = pt + int3{0,i-range,0};
        tmp(pt) += tmp1(new_pt) * filter[i];
      }
    });
    traverse(ds, 1, [&](int3 const& pt) {
      for (int i = 0; i < filter.size(); ++i) {
        auto new_pt = pt + int3{0,0,i-range};
        out(pt) += tmp(new_pt) * filter[i];
      }
    });
    return out;
  }
  template <typename T, typename U> auto convolve(
      volume_image<T> const& lhs, volume_image<U> const& rhs) {
    auto half_size = int3(ceil(rhs.dimensions() / 2.));
    cube<int3> ds{int3{}, lhs.dimensions() - rhs.dimensions()};
    using Out = decltype(T{} * U{});
    volume_image<Out> out_img{lhs.dimensions(), extents(lhs)};
    //Reverse rhs once, simplify calculations later.
    auto kernel = rhs;
    traverse(cube<int3>{int3{}, rhs.dimensions()}, 1, [&](int3 const& pt) {
      kernel(rhs.dimensions() - int3{1,1,1} - pt) = rhs(pt);
    });
    traverse(ds, 1, [&](int3 const& ul) {
      Out out;
      traverse(cube<int3>{int3{}, kernel.dimensions()}, 1, [&](int3 const& pt) {
        out += lhs(ul + pt) * kernel(pt);
      });
      out_img(ul+half_size) = out;
    });
    return out_img;
  }
  namespace jhmi_detail {
    struct prev_type{}; static const constexpr prev_type prev{};
    struct next_type{}; static const constexpr next_type next{};
    struct Top {
      int operator()() const { return 0; }
      int operator()(prev_type) const { return 0; }
      int operator()(next_type) const { return 1; }
    };
    struct Mid {
      int v;
      explicit Mid(int v) : v(v) {}
      int operator()() const { return v; }
      int operator()(prev_type) const { return v-1; }
      int operator()(next_type) const { return v+1; }
    };
    struct Bottom {
      int v;
      explicit Bottom(int v) : v(v) {}
      int operator()() const { return v; }
      int operator()(prev_type) const { return v-1; }
      int operator()(next_type) const { return v; }
    };
    template <typename T, typename U1, typename U2, typename F>
    void traverse_x(F f, U1 y, U2 z, volume_image<T> const& in, volume_image<T>& out) {
      out(0, y(), z()) = f(in(0, y(), z()), in(1, y(), z()),
                           in(0, y(prev), z()), in(0, y(next), z()),
                           in(0, y(), z(prev)), in(0, y(), z(next)));
      auto l = in.width()-1;
      for (int x = 1; x < l; ++x)
        out(x, y(), z()) = f(in(x, y(), z()), in(x-1, y(), z()), in(x+1, y(), z()),
                             in(x, y(prev), z()), in(x, y(next), z()),
                             in(x, y(), z(prev)), in(x, y(), z(next)));
      out(l, y(), z()) = f(in(l, y(), z()), in(l-1, y(), z()),
                           in(l, y(prev), z()), in(l, y(next), z()),
                           in(l, y(), z(prev)), in(l, y(), z(next)));
    }
    template <typename T, typename U1, typename F>
    void traverse_y(F f, U1 z, volume_image<T> const& in, volume_image<T>& out) {
      traverse_x(f, Top(), z, in, out);
      for (int y = 1; y < in.height()-1; ++y)
        traverse_x(f, Mid(y), z, in, out);
      traverse_x(f, Bottom(in.height()-1), z, in, out);
    }
    template <typename T, typename F>
    void traverse_z(F f, volume_image<T> const& in, volume_image<T>& out) {
      traverse_y(f, Top(), in, out);
      for (int z = 1; z < in.depth()-1; ++z)
        traverse_y(f, Mid(z), in, out);
      traverse_y(f, Bottom(in.depth()-1), in, out);
    }
  }//jhmi_detail
  template <typename T> auto erode(volume_image<T> const& img) {
    auto out = volume_image<T>{img.dimensions(), extents(img)};
    jhmi_detail::traverse_z([](auto ...vals) { return std::min({vals...}); }, img, out);
    return out;
  }
  template <typename T> auto dilate(volume_image<T> const& img) {
    auto out = volume_image<T>{img.dimensions(), extents(img)};
    jhmi_detail::traverse_z([](auto ...vals) { return std::max({vals...}); }, img, out);
    return out;
  }
}

#endif


#include <limits>
#include <fstream>
#include <vector>
#include <Eigen/Core>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/io/pcd_io.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/features/fpfh.h>
#include <pcl/registration/ia_ransac.h>
#include "DuploModel.cpp"

// Align a collection of object templates to a sample point cloud
int
main (int argc, char **argv)
{
  if (argc < 3)
  {
    printf ("No target PCD file given!\n");
    return (-1);
  }

  // Load the object templates specified in the object_templates.txt file
  std::vector<FeatureCloud> object_templates;
  std::ifstream input_stream (argv[1]);
  object_templates.resize (0);
  std::string pcd_filename;
  while (input_stream.good ())
  {
    std::getline (input_stream, pcd_filename);
    if (pcd_filename.empty () || pcd_filename.at (0) == '#') // Skip blank lines or comments
      continue;

    FeatureCloud template_cloud;
    template_cloud.loadInputCloud (pcd_filename);
    object_templates.push_back (template_cloud);
  }
  input_stream.close ();

  // Load the target cloud PCD file
  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);
  pcl::io::loadPCDFile (argv[2], *cloud);

  // Preprocess the cloud by...
  // ...removing distant points
  const float depth_limit = 1.0;
  pcl::PassThrough<pcl::PointXYZRGB> pass;
  pass.setInputCloud (cloud);
  pass.setFilterFieldName ("z");
  pass.setFilterLimits (0, depth_limit);
  pass.filter (*cloud);

  // ... and downsampling the point cloud
  const float voxel_grid_size = 0.005f;
  pcl::VoxelGrid<pcl::PointXYZRGB> vox_grid;
  vox_grid.setInputCloud (cloud);
  vox_grid.setLeafSize (voxel_grid_size, voxel_grid_size, voxel_grid_size);
  vox_grid.filter (*cloud);

  // Assign to the target FeatureCloud
  FeatureCloud target_cloud;
  target_cloud.setInputCloud (cloud);

  // Set the TemplateAlignment inputs
  TemplateAlignment template_align;
  for (size_t i = 0; i < object_templates.size (); ++i)
  {
    template_align.addTemplateCloud (object_templates[i]);
  }
  template_align.setTargetCloud (target_cloud);

  // Find the best template alignment
  TemplateAlignment::Result best_alignment;
  int best_index = template_align.findBestAlignment (best_alignment);
  const FeatureCloud &best_template = object_templates[best_index];

  // Print the alignment fitness score (values less than 0.00002 are good)
  printf ("Best fitness score: %f\n", best_alignment.fitness_score);

  // Print the rotation matrix and translation vector
  Eigen::Matrix3f rotation = best_alignment.final_transformation.block<3,3>(0, 0);
  Eigen::Vector3f translation = best_alignment.final_transformation.block<3,1>(0, 3);

  printf ("\n");
  printf ("    | %6.3f %6.3f %6.3f | \n", rotation (0,0), rotation (0,1), rotation (0,2));
  printf ("R = | %6.3f %6.3f %6.3f | \n", rotation (1,0), rotation (1,1), rotation (1,2));
  printf ("    | %6.3f %6.3f %6.3f | \n", rotation (2,0), rotation (2,1), rotation (2,2));
  printf ("\n");
  printf ("t = < %0.3f, %0.3f, %0.3f >\n", translation (0), translation (1), translation (2));

  // Save the aligned template for visualization
  pcl::PointCloud<pcl::PointXYZRGB> transformed_cloud;
  pcl::transformPointCloud (*best_template.getPointCloud (), transformed_cloud, best_alignment.final_transformation);
  pcl::io::savePCDFileBinary ("output.pcd", transformed_cloud);

  return (0);
}


#include <cctbx/boost_python/flex_fwd.h>

#include <boost/python/module.hpp>
#include <boost/python/scope.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/enum.hpp>

#include <rstbx/diffraction/fastbragg/fastbragg.h>

using namespace boost::python;

namespace rstbx {
namespace diffraction {
namespace fastbragg {

namespace boost_python { namespace {

  boost::python::tuple
  foo()
  {
    return boost::python::make_tuple(1,2,3,4);
  }

  static double
  get_distance_mm(camera const& c) { return c.distance*1000.; }

  static void
  set_distance_mm(camera& c, double const& value) {
      c.distance = value/1000.;}

  void
  init_module() {
    using namespace boost::python;

    typedef return_value_policy<return_by_value> rbv;
    typedef default_call_policies dcp;

    def("foo", &foo);

    class_<detector>("detector",no_init)
      .enable_pickling()
      .def(init<int const&, int const&, double const&>(
        (arg_("slowpixels"), arg_("fastpixels"), arg_("pixel_size"))))

      .def("set_region_of_interest",&detector::set_region_of_interest,
        (arg_("slow_min"), arg_("slow_max"),
         arg_("fast_min"), arg_("fast_max")))

      .def("set_oversampling",&detector::set_oversampling,
        (arg_("oversampling")))

      .def("intdata",&detector::intdata)

      .add_property("raw",
                     make_getter(&detector::raw,rbv()),
                     make_setter(&detector::raw,dcp()))
      .add_property("pixel_sz",
                     make_getter(&detector::pixel_sz,rbv()))
    ;

    class_<camera>("camera",init<>())
      .add_property("distance",
                     make_getter(&camera::distance,rbv()),
                     make_setter(&camera::distance,dcp()))
      .add_property("distance_mm",
                     make_function(&get_distance_mm,rbv()),
                     make_function(&set_distance_mm,dcp()))
      .add_property("Ybeam",
                     make_getter(&camera::Ybeam,rbv()),
                     make_setter(&camera::Ybeam,dcp()))
      .add_property("Zbeam",
                     make_getter(&camera::Zbeam,rbv()),
                     make_setter(&camera::Zbeam,dcp()))
      .add_property("lambda0",
                     make_getter(&camera::lambda0,rbv()),
                     make_setter(&camera::lambda0,dcp()))
      .add_property("dispersion",
                     make_getter(&camera::dispersion,rbv()),
                     make_setter(&camera::dispersion,dcp()))
      .add_property("dispsteps",
                     make_getter(&camera::dispsteps,rbv()),
                     make_setter(&camera::dispsteps,dcp()))
      .add_property("hdivrange",
                     make_getter(&camera::hdivrange,rbv()),
                     make_setter(&camera::hdivrange,dcp()))
      .add_property("vdivrange",
                     make_getter(&camera::vdivrange,rbv()),
                     make_setter(&camera::vdivrange,dcp()))
      .add_property("hdivstep",
                     make_getter(&camera::hdivstep,rbv()),
                     make_setter(&camera::hdivstep,dcp()))
      .add_property("vdivstep",
                     make_getter(&camera::vdivstep,rbv()),
                     make_setter(&camera::vdivstep,dcp()))
      .add_property("source_distance",
                     make_getter(&camera::source_distance,rbv()),
                     make_setter(&camera::source_distance,dcp()))
      .add_property("fluence",
                     make_getter(&camera::fluence,rbv()),
                     make_setter(&camera::fluence,dcp()))
      .def("get_wavelengths",&camera::get_wavelengths)
    ;

    class_<crystal>("crystal",init<>())
      .add_property("orientation",
                     make_getter(&crystal::orientation,rbv()),
                     make_setter(&crystal::orientation,dcp()))
      .add_property("miller",
                     make_getter(&crystal::miller,rbv()),
                     make_setter(&crystal::miller,dcp()))
      .add_property("amplitudes",
                     make_getter(&crystal::amplitudes,rbv()),
                     make_setter(&crystal::amplitudes,dcp()))
      .add_property("Na",
                     make_getter(&crystal::Na,rbv()),
                     make_setter(&crystal::Na,dcp()))
      .add_property("Nb",
                     make_getter(&crystal::Nb,rbv()),
                     make_setter(&crystal::Nb,dcp()))
      .add_property("Nc",
                     make_getter(&crystal::Nc,rbv()),
                     make_setter(&crystal::Nc,dcp()))
    ;

    class_<fast_bragg_simulation>("fast_bragg_simulation",init<>())

      .def("set_detector",&fast_bragg_simulation::set_detector)
      .def("set_camera",&fast_bragg_simulation::set_camera)
      .def("set_crystal",&fast_bragg_simulation::set_crystal)
      .def("sweep_over_detector",&fast_bragg_simulation::sweep_over_detector,
        (arg_("verbose")=false))
      .def("sweep_over_detector_float_indices",&fast_bragg_simulation::sweep_over_detector_get_indices<
            rstbx::diffraction::fastbragg::vec3,
            rstbx::diffraction::fastbragg::vec3_double_miller_index_policy>,
        (arg_("verbose")=false))
      .def("sweep_over_detector_integer_indices",&fast_bragg_simulation::sweep_over_detector_get_indices<
            rstbx::diffraction::fastbragg::miller_t,
            rstbx::diffraction::fastbragg::integer_miller_index_policy>,
        (arg_("verbose")=false))
      .def("to_smv_format",&fast_bragg_simulation::to_smv_format,
        (arg_("fileout"),arg_("intfile_scale")=0,
         arg_("saturation")=65535,arg_("verbose")=false))
    ;
  }

}}}}} // namespace rstbx::diffraction::fastbragg::boost_python::<anonymous>

BOOST_PYTHON_MODULE(rstbx_diffraction_fastbragg_ext)
{
  rstbx::diffraction::fastbragg::boost_python::init_module();
}