#include <iostream>
#include <boost/program_options.hpp>
#include <string>
#include <random>
#include <ios>
#include <fstream>
#include "src/utils/util.h"
#include "src/utils/encoder.h"
#include "src/utils/djcs_t_aux.h"
#include "src/utils/pb_converter.h"
#include "src/client/client.h"
#include "src/models/cart_tree.h"
#include "src/models/feature.h"
#include "src/models/tree_node.h"
#include "src/models/random_forest.h"
#include "src/models/gbdt.h"

#include "tests/test_encoder.h"
#include "tests/test_djcs_t_aux.h"
#include "tests/test_pb_converter.h"

hcs_random *hr;
djcs_t_public_key *pk;
djcs_t_private_key *vk;
mpz_t n, positive_threshold, negative_threshold;
djcs_t_auth_server **au = (djcs_t_auth_server **)malloc(TOTAL_CLIENT_NUM * sizeof(djcs_t_auth_server *));
mpz_t *si = (mpz_t *)malloc(TOTAL_CLIENT_NUM * sizeof(mpz_t));

FILE * logger_out;
bool gbdt_flag = false;

void system_setup() {
    hr = hcs_init_random();
    pk = djcs_t_init_public_key();
    vk = djcs_t_init_private_key();

    djcs_t_generate_key_pair(pk, vk, hr, 1, 1024, TOTAL_CLIENT_NUM, TOTAL_CLIENT_NUM);
    mpz_t *coeff = djcs_t_init_polynomial(vk, hr);

    for (int i = 0; i < TOTAL_CLIENT_NUM; i++) {
        mpz_init(si[i]);
        djcs_t_compute_polynomial(vk, coeff, si[i], i);
        au[i] = djcs_t_init_auth_server();
        djcs_t_set_auth_server(au[i], si[i], i);
    }
    mpz_init(n);
    mpz_init(positive_threshold);
    mpz_init(negative_threshold);
    compute_thresholds(pk, n, positive_threshold, negative_threshold);

    djcs_t_free_polynomial(vk, coeff);
}

void system_free() {
    // free memory
    hcs_free_random(hr);
    djcs_t_free_public_key(pk);
    djcs_t_free_private_key(vk);
    mpz_clear(n);
    mpz_clear(positive_threshold);
    mpz_clear(negative_threshold);
    for (int i = 0; i < TOTAL_CLIENT_NUM; i++) {
        mpz_clear(si[i]);
        djcs_t_free_auth_server(au[i]);
    }
    free(si);
    free(au);
}

float decision_tree(Client & client, int solution_type,
    int optimization_type, int class_num, int tree_type,
    int max_bins, int max_depth, int num_trees) {
    logger(logger_out, "Begin decision tree training\n");
    struct timeval decision_tree_training_1, decision_tree_training_2;
    double decision_tree_training_time = 0;
    gettimeofday(&decision_tree_training_1, NULL);

    int m_global_feature_num = GLOBAL_FEATURE_NUM;
    int m_local_feature_num = client.local_data[0].size();
    int m_internal_node_num = 0;
    int m_type = tree_type;
    int m_classes_num = class_num;
    if (m_type == Regression) m_classes_num = CLASS_NUM_FOR_REGRESSION;
    int m_max_depth = max_depth;
    int m_max_bins = max_bins;
    int m_prune_sample_num = PRUNE_SAMPLE_NUM;
    float m_prune_threshold = PRUNE_VARIANCE_THRESHOLD;
    int m_solution_type = solution_type;
    int m_optimization_type = optimization_type;
    DecisionTree model(m_global_feature_num, m_local_feature_num,
        m_internal_node_num, m_type, m_classes_num,
        m_max_depth, m_max_bins, m_prune_sample_num,
        m_prune_threshold, m_solution_type, m_optimization_type);
    logger(logger_out, "Init decision tree model succeed\n");

    float split = SPLIT_PERCENTAGE;
    if (client.client_id == SUPER_CLIENT_ID) {
        model.init_datasets(client, split);
        //model.test_indicator_vector_correctness();
    } else {
        int *new_indexes = new int[client.sample_num];
        std::string recv_s;
        client.recv_long_messages(SUPER_CLIENT_ID, recv_s);
        deserialize_ids_from_string(new_indexes, recv_s);
        model.init_datasets_with_indexes(client, new_indexes, split);
        delete [] new_indexes;
    }
    logger(logger_out, "Training data size = %d\n", model.training_data.size());

    model.init_features();
    model.init_root_node(client);
    model.build_tree_node(client, 0);
    logger(logger_out, "End decision tree training\n");
    logger(logger_out, "The internal node number is %d\n", model.internal_node_num);

    gettimeofday(&decision_tree_training_2, NULL);
    decision_tree_training_time +=
        (double)((decision_tree_training_2.tv_sec - decision_tree_training_1.tv_sec) * 1000 +
        (double)(decision_tree_training_2.tv_usec - decision_tree_training_1.tv_usec) / 1000);
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "******** Decision tree training time: %'.3f ms **********\n", decision_tree_training_time);
    logger(logger_out, "*********************************************************************\n");
    struct timeval decision_tree_prediction_1, decision_tree_prediction_2;
    double decision_tree_prediction_average_time = 0;
    gettimeofday(&decision_tree_prediction_1, NULL);

    float accuracy = 0.0;
    model.test_accuracy(client, accuracy);

    gettimeofday(&decision_tree_prediction_2, NULL);
    decision_tree_prediction_average_time +=
        (double)((decision_tree_prediction_2.tv_sec - decision_tree_prediction_1.tv_sec) * 1000 +
        (double)(decision_tree_prediction_2.tv_usec - decision_tree_prediction_1.tv_usec) / 1000);
    decision_tree_prediction_average_time = decision_tree_prediction_average_time / (double) model.testing_data.size();
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "********* Average decision tree prediction time: %'.3f ms ************\n",
        decision_tree_prediction_average_time);
    logger(logger_out, "*********************************************************************\n");

    if (client.client_id == SUPER_CLIENT_ID) {
        logger(logger_out, "Accuracy = %f\n", accuracy);
//        std::string result_log_file = LOGGER_HOME;
//        result_log_file += "result.log";
//        std::ofstream result_log(result_log_file, std::ios_base::app | std::ios_base::out);
//        result_log << accuracy << std::endl;
    }
    return accuracy;
}

float random_forest(Client & client, int solution_type,
    int optimization_type, int class_num, int tree_type,
    int max_bins, int max_depth, int num_trees) {
    logger(logger_out, "Begin random forest training\n");
    struct timeval random_forest_training_1, random_forest_training_2;
    double random_forest_training_time = 0;
    gettimeofday(&random_forest_training_1, NULL);

    int m_tree_num = num_trees;
    int m_global_feature_num = GLOBAL_FEATURE_NUM;
    int m_local_feature_num = client.local_data[0].size();
    int m_internal_node_num = 0;
    int m_type = tree_type;
    int m_classes_num = class_num;
    if (m_type == Regression) m_classes_num = CLASS_NUM_FOR_REGRESSION;
    int m_max_depth = max_depth;
    int m_max_bins = max_bins;
    int m_prune_sample_num = PRUNE_SAMPLE_NUM;
    float m_prune_threshold = PRUNE_VARIANCE_THRESHOLD;
    RandomForest model(m_tree_num, m_global_feature_num, m_local_feature_num,
        m_internal_node_num, m_type, m_classes_num,
        m_max_depth, m_max_bins, m_prune_sample_num,
        m_prune_threshold, solution_type, optimization_type);
    // split datasets to training part and testing part
    float split = SPLIT_PERCENTAGE;
    if (client.client_id == SUPER_CLIENT_ID) {
        model.init_datasets(client, split);
    } else {
        int *new_indexes = new int[client.sample_num];
        std::string recv_s;
        client.recv_long_messages(SUPER_CLIENT_ID, recv_s);
        deserialize_ids_from_string(new_indexes, recv_s);
        model.init_datasets_with_indexes(client, new_indexes, split);
        delete [] new_indexes;
    }
    float sample_rate = RF_SAMPLE_RATE;
    model.build_forest(client, sample_rate);

    gettimeofday(&random_forest_training_2, NULL);
    random_forest_training_time +=
        (double)((random_forest_training_2.tv_sec - random_forest_training_1.tv_sec) * 1000 +
        (double)(random_forest_training_2.tv_usec - random_forest_training_1.tv_usec) / 1000);
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "******** Random forest training time: %'.3f ms **********\n", random_forest_training_time);
    logger(logger_out, "*********************************************************************\n");

    struct timeval random_forest_prediction_1, random_forest_prediction_2;
    double random_forest_prediction_average_time = 0;
    gettimeofday(&random_forest_prediction_1, NULL);

    float accuracy = 0.0;
    model.test_accuracy(client, accuracy);

    gettimeofday(&random_forest_prediction_2, NULL);
    random_forest_prediction_average_time +=
        (double)((random_forest_prediction_2.tv_sec - random_forest_prediction_1.tv_sec) * 1000 +
        (double)(random_forest_prediction_2.tv_usec - random_forest_prediction_1.tv_usec) / 1000);
    random_forest_prediction_average_time = random_forest_prediction_average_time / (double) model.testing_data.size();
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "********* Average random forest prediction time: %'.3f ms ************\n",
        random_forest_prediction_average_time);
    logger(logger_out, "*********************************************************************\n");

    if (client.client_id == SUPER_CLIENT_ID) {
        logger(logger_out, "Accuracy = %f\n", accuracy);
//        std::string result_log_file = LOGGER_HOME;
//        result_log_file += "result.log";
//        std::ofstream result_log(result_log_file, std::ios_base::app | std::ios_base::out);
//        result_log << accuracy << std::endl;
    }
    return accuracy;
}

float gbdt(Client & client, int solution_type,
    int optimization_type, int class_num, int tree_type,
    int max_bins, int max_depth, int num_trees) {
    logger(logger_out, "Begin GBDT training\n");
    struct timeval gbdt_training_1, gbdt_training_2;
    double gbdt_training_time = 0;
    gettimeofday(&gbdt_training_1, NULL);

    int m_tree_num = num_trees;
    int m_global_feature_num = GLOBAL_FEATURE_NUM;
    int m_local_feature_num = client.local_data[0].size();
    int m_internal_node_num = 0;
    int m_type = tree_type;
    int m_classes_num = class_num;
    if (m_type == Regression) m_classes_num = CLASS_NUM_FOR_REGRESSION;
    int m_max_depth = max_depth;
    int m_max_bins = max_bins;
    int m_prune_sample_num = PRUNE_SAMPLE_NUM;
    float m_prune_threshold = PRUNE_VARIANCE_THRESHOLD;
    GBDT model(m_tree_num, m_global_feature_num, m_local_feature_num,
        m_internal_node_num, m_type, m_classes_num,
        m_max_depth, m_max_bins, m_prune_sample_num,
        m_prune_threshold, solution_type, optimization_type);
    logger(logger_out, "Correct init gbdt\n");

    float split = SPLIT_PERCENTAGE;
    if (client.client_id == SUPER_CLIENT_ID) {
        model.init_datasets(client, split);
        //model.test_indicator_vector_correctness();
    } else {
        int *new_indexes = new int[client.sample_num];
        std::string recv_s;
        client.recv_long_messages(SUPER_CLIENT_ID, recv_s);
        deserialize_ids_from_string(new_indexes, recv_s);
        model.init_datasets_with_indexes(client, new_indexes, split);
        delete [] new_indexes;
    }
    model.build_gbdt_with_spdz(client);

    gettimeofday(&gbdt_training_2, NULL);
    gbdt_training_time += (double)((gbdt_training_2.tv_sec - gbdt_training_1.tv_sec) * 1000 +
        (double)(gbdt_training_2.tv_usec - gbdt_training_1.tv_usec) / 1000);
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "******** GBDT training time: %'.3f ms **********\n", gbdt_training_time);
    logger(logger_out, "*********************************************************************\n");

    struct timeval gbdt_prediction_1, gbdt_prediction_2;
    double gbdt_prediction_average_time = 0;
    gettimeofday(&gbdt_prediction_1, NULL);

    float accuracy = 0.0;
    model.test_accuracy(client, accuracy);

    gettimeofday(&gbdt_prediction_2, NULL);
    gbdt_prediction_average_time +=
        (double)((gbdt_prediction_2.tv_sec - gbdt_prediction_1.tv_sec) * 1000 +
        (double)(gbdt_prediction_2.tv_usec - gbdt_prediction_1.tv_usec) / 1000);
    gbdt_prediction_average_time = gbdt_prediction_average_time / (double) model.testing_data.size();
    logger(logger_out, "*********************************************************************\n");
    logger(logger_out, "********* Average GBDT prediction time: %'.3f ms ************\n", gbdt_prediction_average_time);
    logger(logger_out, "*********************************************************************\n");

    //model.test_accuracy_with_spdz(client, accuracy);
    if (client.client_id == SUPER_CLIENT_ID) {
        logger(logger_out, "Accuracy = %f\n", accuracy);
//        std::string result_log_file = LOGGER_HOME;
//        result_log_file += "result.log";
//        std::ofstream result_log(result_log_file, std::ios_base::app | std::ios_base::out);
//        result_log << accuracy << std::endl;
    }
    return accuracy;
}

int main(int argc, char *argv[]) {
    int client_id, client_num, class_num, algorithm_type, tree_type;
    int solution_type, optimization_type, max_bins, max_depth, num_trees;
    std::string network_file, data_file, logger_file_name;

    try {
        namespace po = boost::program_options;
        po::options_description description("Usage:");
        description.add_options()
            ("help,h", "display this help message")
            ("version,v", "display the version number")
            ("client-id", po::value<int>(&client_id), "current client id")
            ("client-num", po::value<int>(&client_num), "total client num")
            ("class-num", po::value<int>(&class_num), "num of classes for the task")
            ("algorithm-type", po::value<int>(&algorithm_type), "desired algorithm, decision tree, random forest, or gbdt")
            ("tree-type", po::value<int>(&tree_type), "classification or regression")
            ("solution-type", po::value<int>(&solution_type), "basic protocol or enhanced protocol")
            ("optimization-type", po::value<int>(&optimization_type), "optimization used for this protocol")
            ("network-file", po::value<std::string>(&network_file), "network file used")
            ("data-file", po::value<std::string>(&data_file), "dataset used for the task")
            ("logger-file", po::value<std::string>(&logger_file_name), "logger file header")
            ("max-bins", po::value<int>(&max_bins), "maximum bins for splitting each feature")
            ("max-depth", po::value<int>(&max_depth), "maximum tree depth")
            ("num-trees", po::value<int>(&num_trees), "num of trees in ensemble models");

        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(description).run(), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << "Usage: options_description [options]\n";
            std::cout << description;
            return 0;
        }
        std::cout << "parse parameters correct" << std::endl;

        std::cout << "client-id: " << vm["client-id"].as<int>() << std::endl;
        std::cout << "client-num: " << vm["client-num"].as<int>() << std::endl;
        std::cout << "class-num: " << vm["class-num"].as<int>() << std::endl;
        std::cout << "algorithm-type: " << vm["algorithm-type"].as<int>() << std::endl;
        std::cout << "tree-type: " << vm["tree-type"].as<int>() << std::endl;
        std::cout << "solution-type: " << vm["solution-type"].as<int>() << std::endl;
        std::cout << "optimization-type: " << vm["optimization-type"].as<int>() << std::endl;
        std::cout << "network-file: " << vm["network-file"].as< std::string >() << std::endl;
        std::cout << "data-file: " << vm["data-file"].as< std::string >() << std::endl;
        std::cout << "logger-file: " << vm["logger-file"].as< std::string >() << std::endl;
        std::cout << "max-bins: " << vm["max-bins"].as<int>() << std::endl;
        std::cout << "max-depth: " << vm["max-depth"].as<int>() << std::endl;
        std::cout << "num-trees: " << vm["num-trees"].as<int>() << std::endl;
    }
    catch(std::exception& e)
    {
        cout << e.what() << "\n";
        return 1;
    }

    //test_pb();

    if (client_id == SUPER_CLIENT_ID) {
        system_setup();
    }
    // create logger file
    std::string alg_name;
    switch (algorithm_type) {
        case RandomForestAlg:
            alg_name = "RF";
            break;
        case GBDTAlg:
            alg_name = "GBDT";
            break;
        default:
            alg_name = "DT";
            break;
    }
    logger_file_name += "_";
    logger_file_name += alg_name;
    logger_file_name += "_";
    logger_file_name += get_timestamp_str();
    logger_file_name += "_client";
    logger_file_name += to_string(client_id);
    logger_file_name += ".txt";
    logger_out = fopen(logger_file_name.c_str(), "wb");

    bool has_label = (client_id == SUPER_CLIENT_ID);

    Client client(client_id, client_num, has_label, network_file, data_file);
    // set up keys
    if (client.client_id == SUPER_CLIENT_ID) {
        client.set_keys(pk, hr, si[client.client_id], client.client_id);
        // send keys
        for (int i = 0; i < client.client_num; i++) {
            if (i != client.client_id) {
                std::string keys_i;
                client.serialize_send_keys(keys_i, pk, si[i], i);
                client.send_long_messages(i, keys_i);
            }
        }
    } else {
        // receive keys from client 0
        std::string recv_keys;
        client.recv_long_messages(SUPER_CLIENT_ID, recv_keys);
        client.recv_set_keys(recv_keys);
        mpz_init(n);
        mpz_init(positive_threshold);
        mpz_init(negative_threshold);
        compute_thresholds(client.m_pk, n, positive_threshold, negative_threshold);
    }

    switch(algorithm_type) {
        case RandomForestAlg:
            random_forest(client, solution_type, optimization_type,
                class_num, tree_type, max_bins, max_depth, num_trees);
            break;
        case GBDTAlg:
            gbdt_flag = true;
            gbdt(client, solution_type, optimization_type,
                class_num, tree_type, max_bins, max_depth, num_trees);
            break;
        default:
            decision_tree(client, solution_type, optimization_type,
                class_num, tree_type, max_bins, max_depth, num_trees);
            break;
    }

    if (client_id == SUPER_CLIENT_ID) {
        system_free();
    } else {
        mpz_clear(n);
        mpz_clear(positive_threshold);
        mpz_clear(negative_threshold);
    }
    logger(logger_out, "The End\n");
    return 0;
}


// Copyright (C) 2015, Pawel Tomulik <ptomulik@meil.pw.edu.pl>
//
// 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)
#define BOOST_TEST_MODULE test_txpl_vm_eval_binary_div

#include <txpl/test_config.hpp>
#include <boost/test/unit_test.hpp>

#ifndef TXPL_TEST_SKIP_VM_EVAL_BINARY_DIV

#include <txpl/vm/eval_binary_op.hpp>
#include <txpl/vm/basic_types.hpp>
#include <txpl/vm/value.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/get.hpp>

using namespace txpl::vm;
typedef basic_types<>::char_type   char_type;
typedef basic_types<>::int_type    int_type;
typedef basic_types<>::bool_type   bool_type;
typedef basic_types<>::real_type   real_type;
typedef basic_types<>::string_type string_type;
typedef basic_types<>::regex_type  regex_type;
typedef basic_types<>::blank_type  blank_type;
typedef array<value<> >            array_type;
typedef object<value<> >           object_type;

BOOST_AUTO_TEST_CASE(char__div__char)
{
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = char_type{'\4'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} / char_type{'\4'}));
  }
}
BOOST_AUTO_TEST_CASE(char__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = int_type{4};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} / int_type{4}));
  }
}
BOOST_AUTO_TEST_CASE(char__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'\3'};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\3'} / bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(char__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = real_type{2.1};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} / real_type{2.1}));
  }
}
BOOST_AUTO_TEST_CASE(char__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(int__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{2};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{2} / char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(int__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{2};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{2} / int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(int__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{3};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{3} / bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(int__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{12};
    const value<> v2 = real_type{3.21};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (int_type{12} / real_type{3.21}));
  }
}
BOOST_AUTO_TEST_CASE(int__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(bool__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} / char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} / int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} / bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = real_type{2.12};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (bool_type{true} / real_type{2.12}));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(real__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} / char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(real__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} / int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(real__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} / bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(real__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = real_type{3.4};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} / real_type{3.4}));
  }
}
BOOST_AUTO_TEST_CASE(real__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(string__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = char_type{'a'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type("asd");
    const value<> v2 = string_type{"qwer"};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(regex__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(array__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<array_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(object__div__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__div__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::div_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

#else
BOOST_AUTO_TEST_CASE(dummy)
{
  BOOST_CHECK(true);
}
#endif


//----------------------------------------------------------
// @file   : pot_mapping.cpp
// @author : Watanabe Yuuta
// @version: Ver0.1.4 (since 2014.05.02)
// @date   : 2014.11.12
//----------------------------------------------------------

#include <ros/ros.h>
#include <pthread.h>
#include <std_msgs/String.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>

pthread_mutex_t mutex_laser = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_target = PTHREAD_MUTEX_INITIALIZER;

#include "opencv/cv.h"
#include <sensor_msgs/LaserScan.h>
#include "tms_ss_pot/define.h"
#include "tms_ss_pot/Target.h"
#include "tms_ss_pot/Laser.h"

#include <boost/date_time/posix_time/posix_time.hpp>
#include <string>
#include <iostream>
#include <ctime>
#include <time.h>
#include <iostream>
#include <fstream>
#include <string>

std::vector<float> scanData;
CLaser laser;
bool CallbackCalled = false;
std::ofstream ofs("map.txt");

void *Processing( void *ptr )
{
    ros::Rate r(30);
    laser.Init();

    /**********************************/
    laser.m_bNodeActive[0] = true;
    laser.m_nConnectNum = 1;
    laser.GetLRFParam();
    laser.m_nStep[0] = laser.m_LRFParam[0].step;
    laser.m_StartAngle[0] = -laser.m_LRFParam[0].viewangle / 2.0;
    laser.m_DivAngle[0] = laser.m_LRFParam[0].divangle;
    /**********************************/
    CvMat *m_Rotate = cvCreateMat(2, 2, CV_64F);
    CvMat *m_Translate = cvCreateMat(2, 1, CV_64F);
    CvMat *Temp = cvCreateMat(2, 1, CV_64F);

    int count;
    double theta, range;

    if (laser.m_bNodeActive[0])
        while (!CallbackCalled) r.sleep();

    if (laser.m_bResetBackRangeData == true)
    {
        for (int it = 0; it < laser.m_ring; it++)
        {
            for (int n = 0; n < 1; n++)
            {
                if (laser.m_bNodeActive[n])
                {
                    pthread_mutex_lock(&mutex_laser);

                    laser.m_LRFData[n].clear();
                    laser.m_LRFData[n].resize(scanData.size());
                    for (int i = 0; i < scanData.size(); i++) laser.m_LRFData[n][i] = scanData[i];

                    pthread_mutex_unlock(&mutex_laser);
                }
            }
            //      laser.GetBackLRFData();
            laser.GetBackLRFDataGaussian();
            r.sleep();
        }
        laser.m_bResetBackRangeData = false;
        std::cout << "Back range data is stored" << std::endl;
    }
    /*
        for (int n = 0; n < 1; n++)
        {
            for (int i = 0; i < laser.m_LRFData[n].size(); i++)
            {
                std::cout << "laser.m_LRFData_Ave " << i << " " <<   laser.m_BackLRFDataAve[n][i] << std::endl;
            }
        }
    */

    for (int n = 0; n < 1; n++)
    {
        if (laser.m_bNodeActive[n])
        {
            cvmSet(m_Rotate, 0, 0, cos(deg2rad(laser.m_LRFParam[n].rz)));
            cvmSet(m_Rotate, 0, 1, -sin(deg2rad(laser.m_LRFParam[n].rz)));
            cvmSet(m_Rotate, 1, 0, sin(deg2rad(laser.m_LRFParam[n].rz)));
            cvmSet(m_Rotate, 1, 1, cos(deg2rad(laser.m_LRFParam[n].rz)));
            cvmSet(m_Translate, 0, 0, laser.m_LRFParam[n].tx);
            cvmSet(m_Translate, 1, 0, laser.m_LRFParam[n].ty);

            laser.m_LRFPoints[n].clear();
            laser.m_LRFPoints[n].resize(laser.m_LRFData[n].size());
            count = 0 ;
            std::cout << "laser.m_DivAngle " <<  laser.m_DivAngle[0] << std::endl;
            for ( int i = 0; i < laser.m_LRFData[n].size(); i++)
            {
                range = laser.m_BackLRFDataAve[n][i];
                theta = laser.m_DivAngle[0] * count + laser.m_StartAngle[0];
                //laser.m_LRFPoints[n][i].x = range * cos(deg2rad(theta));
                //laser.m_LRFPoints[n][i].y = range * sin(deg2rad(theta));
                // std::cout << "theta " <<  theta << std::endl;
                cvmSet(laser.m_LRFPos[n][i], 0, 0, range * cos(deg2rad(theta)));
                cvmSet(laser.m_LRFPos[n][i], 1, 0, range * sin(deg2rad(theta)));
                cvmMul(m_Rotate, laser.m_LRFPos[n][i], Temp);    //
                cvmAdd(m_Translate, Temp, laser.m_LRFPos[n][i]);  //
                laser.m_LRFPoints[n][i].x = cvmGet(laser.m_LRFPos[n][i], 0, 0) * 1000.0;
                laser.m_LRFPoints[n][i].y = cvmGet(laser.m_LRFPos[n][i], 1, 0) * 1000.0;
                ofs << -(laser.m_LRFPoints[n][i].y) << " " << laser.m_LRFPoints[n][i].x << " " <<  std::endl;
                count++;
            }
        }
    }

    std::cout << "mapping end" << std::endl;
    // for (int n = 0; n < 1; n++)
    // {
    //     for (int i = 0; i < laser.m_LRFData[n].size(); i++)
    //     {
    //         std::cout << "laser.m_LRFData_Ave " << i << " " <<   laser.m_BackLRFDataAve[n][i] << std::endl;
    //     }
    // }

    cvReleaseMat(&Temp);
    cvReleaseMat(&m_Rotate);
    cvReleaseMat(&m_Translate);

    return 0;

}

void LaserSensingCallback(const sensor_msgs::LaserScan::ConstPtr &scan)
{
    pthread_mutex_lock(&mutex_laser);
    std::cout << "scan start" << scan->ranges.size() << std::endl;
    int steps = 683;
    if ( scanData.size() == 0 ) scanData.resize(steps);

    for (int i = 0, j = scan->ranges.size() - steps; i < steps ; i++, j++)
    {
        scanData[i] = scan->ranges[j];
        std::cout << "j " << j << "scan->ranges" <<  scan->ranges[j] << std::endl;
    }
    std::cout << "scanData" << scanData.size() << std::endl;
    pthread_mutex_unlock(&mutex_laser);
    CallbackCalled = true;
}

int main( int argc, char **argv )
{
    std::cout << "mapping start" << std::endl;
    pthread_t thread_p;
    pthread_t thread_v;
    ros::MultiThreadedSpinner spinner(1);

    ros::init(argc, argv, "pot_tmapping");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("/LaserTracker0", 1000, LaserSensingCallback);

    if ( pthread_create( &thread_p, NULL, Processing, NULL) )
    {
        printf("error creating thread.");
        abort();
    }
    spinner.spin(); // spin() will not return until the node has been shutdown

    ros::waitForShutdown();

    if ( pthread_join( thread_p, NULL) )
    {
        printf("error joining thread.");
        abort();
    }
    scanData.clear();

    return 0;
}