#include "apps/generate_model/process_input.h"
//#include "apps/run_experiments/verifiers_task.h"
#include "apps/generate_model/actions_pipeline.h"
#include "libs/json_parser/json_istream.h"
#include "libs/model_data/training_input_task.h"
//#include "apps/run_experiments/running_mode.h"
#include "libs/transform_action/map_values_conf.h"
#include "libs/system/exceptions.h"
#include "libs/classifier_models_actions/classifersmodels_task.h"
#include "libs/regression_models_actions/regressionsmodels_task.h"
#include "libs/scores_evaluation/verifiers.h"
#include "libs/classifier_models_actions/classifers_models.h"
#include "libs/classifier_models_actions/classifer_model_interface.h"
#include "libs/regression_models_actions/regression_model_interface.h"
#include "libs/scores_evaluation/verifier_types.h"
#include "libs/ml_models/EM.h"
#include "logging.h"
#include <boost/assign.hpp>
#include <map>

namespace
{

using action_parser = pipeline::config::trainer (*)(json::istream&, bool);

bool name2type(const std::string& n);
pipeline::config process(json::istream ji);
pipeline::config::path_type get_souce_location(json::istream& input);
bool get_label_id(json::istream& input,
        pipeline::config::label_id& label, unsigned int& iterations,
        bool& is_class);
pipeline::config::experiments get_experiments(json::istream& input);
pipeline::config::experiments labels2experiments(
        const std::vector<std::string>& labels);
pipeline::config::source_policy get_input_type(json::istream& input,
        const pipeline::config::label_id& label, bool& rand);
pipeline::config::trainer get_model_generators(json::istream& input,
                            bool is_class);
pipeline::config::trainer
    process_action_entry(json::istream& ji, bool is_class);
pipeline::config::experiments::value_type
    process_experiment_entry(const std::string& name);

pipeline::config::source_policy name2input_t(json::istream& input, const std::string& name,
        const pipeline::config::label_id& label, bool& rand);
pipeline::config::source_policy kfold_action(json::istream& input,
        const pipeline::config::label_id& label);
pipeline::config::source_policy percent_action(json::istream& input,
        const pipeline::config::label_id& label, bool& rand);
pipeline::config::trainer
    generate_svm_action(json::istream& input, bool classifier);
pipeline::config::trainer
    generate_em_action(json::istream& input, bool classifier);

pipeline::config::trainer
    generate_normal_bayes_action(json::istream& input, bool classifier);

pipeline::config::trainer
    generate_mlp_action(json::istream& input, bool classifier);

pipeline::config::trainer
generate_logistic_regression(json::istream& input, bool classifier);

pipeline::config::trainer
generate_k_nearest_action(json::istream& input, bool classifier);

pipeline::config::trainer
    generate_boosting_action(json::istream& input, bool classifier);
pipeline::config::trainer
    generate_decision_trees_action(json::istream& input, bool classifier);
pipeline::config::trainer
    generate_random_tree_action(json::istream& input, bool classifier);

mlmodels::expectation_maximization::args::covariance_matrices_t
    name2shape(const std::string& sn);
classifier::logistic_regression_train_sv::model_args::regularization_t
    convert_reg(const std::string& from);
classifier::logistic_regression_train_sv::model_args::train_method_t
    convert_method(const std::string& from);

pipeline::config::path_type
    get_export_path(json::istream& ji);

mlmodels::dtree_args
    read_dtree_args(json::istream& att);

///////////////////////////////////////////////////////////////////////////////

bool name2type(const std::string& n)
{
    if (n == "classifier") {
        return true;
    } else if (n == "regression") {
        return false;
    } else {
        LOG_ERROR<<"invalid type given "<<n<<" for model";
        throw invalid_conf{"invalid configuration value given for model type " + n};
    }
}

pipeline::config process(json::istream ji)
{
    auto source_location = get_souce_location(ji);
    pipeline::config::label_id id;
    unsigned int iters = 0;
    bool is_class = true;
    if (get_label_id(ji, id, iters, is_class)) {
        LOG_DEBUG<<"successfully read label id '"<<id<<"' and iterations: "<<iters;
    } else {
        LOG_ERROR<<"cannot process input message, pasrsing failed";
        throw invalid_conf{"invalid configuration failed to process label id or/and iterations count"};
    }
    bool rand = false;
    auto evals = get_experiments(ji);
    auto sr = get_input_type(ji, id, rand);
    auto t = get_model_generators(ji, is_class);
    auto p = get_export_path(ji);
    return pipeline::config{source_location, sr,
        t, evals, iters, p,
        lookup::map_values_conf{
            id, is_class ? actions::CLASSIFIER_MODEL : actions::REGRESSION_MODEL
        }, rand
    };
}

pipeline::config::path_type
    get_export_path(json::istream& ji)
{
    using namespace json::literals;

    if (ji) {
        std::string p, t;
        auto i = ji ^ json::_child(ji, "export"_n);
        if (i) {
            i ^ "path"_n ^ p ^ "type" ^ t;
            if (i && !p.empty()) {
                return pipeline::config::path_type{p};
            } else if (p.empty()) {
                throw invalid_conf{"empty export path or failed to read path from configuration"};
            } else {
                throw json_error{"failed to read export path from export"};
            }
        }
    }
    throw json_error{"cannot read export path - invalid json"};
}

pipeline::config::path_type get_souce_location(json::istream& input)
{
    using namespace json::literals;

    if (input) {
        auto p = input ^ json::_child(input, "datasource"_n);
        if (p) {
            std::string path;
            p ^ "path"_n ^ path;
            if (p) {
                return pipeline::config::path_type{path};
            }
        }
        LOG_ERROR<<"failed to read source path from JSON!";
        throw json_error{"failed to read path location from JSON"};
    } else {
        throw json_error{"cannot parse source location - no valid json input"};
    }
}

bool get_label_id(json::istream& input,
        pipeline::config::label_id& label, unsigned int& iterations,
        bool& is_class)
{
    using namespace json::literals;
    if (input) {
        auto c = input ^ json::_child(input, "model"_n);
        if (c) {
            std::string t;
            c ^"class"_n ^ label ^ "repeations"_n ^ iterations ^ "type"_n ^ t;
            if (c && !label.empty()) {
                is_class = name2type(t);
                return true;
            } else {
                LOG_ERROR<<"failed to parse model label ID and/or number of repeations";
                throw json_error{"missing 'class' or/and 'repeations' or they have invalid type"};
                return false;
            }
        } else {
            LOG_ERROR<<"no model label found";
            throw json_error{"missing 'model' tag in JSON configuration"};
        }
    }
    throw json_error{"failed to parse JSON"};
    return false;
}

pipeline::config::experiments get_experiments(json::istream& input)
{
    using namespace json::literals;

    if (input) {
        auto v = input ^ json::_child(input, "evaluation"_n);
        if (v) {
            std::vector<std::string> labels;
            v ^ labels;
            if (!labels.empty()) {
                return labels2experiments(labels);
            } else {
                LOG_ERROR<<"failed to read experiments for models";
                throw json_error{"failed to read list of evaluations from configuration"};
            }
        } else {
            LOG_ERROR<<"no 'evaluation' label in configuration";
            throw json_error{"failed to read evaluations list, missing from configuration"};
        }
    }
    LOG_ERROR<<"cannot process json as we are in invalid state in the json stream";
    throw json_error{"cannot process expriment list invalid input"};
}

pipeline::config::experiments labels2experiments(
        const std::vector<std::string>& labels)
{
    pipeline::config::experiments exprs;
    std::for_each(std::begin(labels), std::end(labels), [&exprs](auto& name) {
                        exprs.push_back(process_experiment_entry(name));
                    }
    );
    if (exprs.empty()) {
        LOG_ERROR<<"no valid verifiers were found in configuration!!";
        throw invalid_conf{"no valid verifiers in configuration"};
    }
    return exprs;
}

pipeline::config::experiments::value_type
    process_experiment_entry(const std::string& name)
{
                                  //("logisic-los", log_loss)
    static const std::map<std::string, pipeline::config::experiments::value_type> name2actions =
        boost::assign::map_list_of("auc-roc", AUC_ROC_t)
                                  ("mse", MSE)
                                  ("ks-test", kalmogorov_smirnov);
    auto i = name2actions.find(name);
    if (i == std::end(name2actions)) {
        LOG_ERROR<<"invalid verifier name '"<<name<<"' in configuration";
        throw invalid_conf{"uknown entry for experimention " + name};
    } else {
        LOG_DEBUG<<"creating new algorithm: "<<i->first;
        return i->second;
    }
}

pipeline::config::source_policy get_input_type(json::istream& input,
        const pipeline::config::label_id& label, bool& rand)
{
    using namespace json::literals;

    if (input) {
        auto v = input ^ json::_child(input, "validation"_n);
        if (v) {
            std::string name;
            v ^ "action"_n ^ name;
            return name2input_t(v, name, label, rand);
        } else {
            LOG_ERROR<<"missing 'validation' tag in configuration";
            throw json_error{"missing 'validation' in configuration"};
        }
    }

    LOG_ERROR<<"invalid json - cannot process";
    throw json_error{"cannot process validation type,  invalid input"};
}

pipeline::config::source_policy name2input_t(json::istream& input,
        const std::string& name, const pipeline::config::label_id& label,
        bool& rand)
{
    if (input) {
        if (name == "k-fold") {
            return kfold_action(input, label);
        } else if (name == "precent") {
            return percent_action(input, label, rand);
        } else {
            LOG_ERROR<<"uknown action type '"<<name<<"'";
            throw json_error{"unsupported validation name " + name};
        }
    }
    LOG_ERROR<<"cannot read validation action type - invalid input";
    throw json_error{"cannot read validation action type - invalid input"};
}

pipeline::config::source_policy kfold_action(json::istream& input,
        const pipeline::config::label_id& label)
{
    using namespace json::literals;

    auto a = input ^ json::_child(input, "attr"_n);
    if (a) {
        unsigned int s = 0;
        a ^ "arg"_n ^ s;
        return pipeline::config::source_policy{k_fold{s, label}};
    }
    LOG_ERROR<<"missing attributes for k fold input type";
    throw json_error{"no attribute in k fold"};
}

pipeline::config::source_policy percent_action(json::istream& input,
        const pipeline::config::label_id& label, bool& rand)
{
    using namespace json::literals;

    auto a = input ^ json::_child(input, "attr"_n);
    if (a) {
        unsigned int s = 0;
        a ^ "arg"_n ^ s ^ json::optional ^ "randomized"_n ^ rand;
        return pipeline::config::source_policy{percent_split{s, label}};
    }
    LOG_ERROR<<"missing attributes for precent input type";
    throw json_error{"no attribute in precent"};
}

pipeline::config::trainer
    process_action_entry(json::istream& ji, bool c)
{
    using namespace json::literals;
    static const std::map<std::string, action_parser> name2action =
                    boost::assign::map_list_of("model.svm", generate_svm_action)
                    ("model.em", generate_em_action)
                    ("model.norm-bayes", generate_normal_bayes_action)
                    ("model.boosting", generate_boosting_action)
                    ("model.dtrees", generate_decision_trees_action)
                    ("model.rtrees", generate_random_tree_action)
                    ("model.log-reg", generate_logistic_regression)
                    ("model.mlp", generate_mlp_action)
                    ("model.k_nearest", generate_k_nearest_action);
    std::string n;
    ji ^ "name"_n ^ n;
    if (!ji) {
        LOG_ERROR<<"failed to read model name/type from json";
        throw json_error{"missing or invalid model name or model type"};
    }
    auto i = name2action.find(n);
    if (i == std::end(name2action)) {
        LOG_ERROR<<"unknown model type '"<<n<<"'";
        throw invalid_conf{"invalid entry " + n};
    } else {
        LOG_DEBUG<<"creating new algo: "<<i->first;
        return i->second(ji, c);
    }
}

pipeline::config::trainer
generate_normal_bayes_action(json::istream&, bool )
{
    return pipeline::config::trainer{classifier::normal_bayes_train_sv{}, actions::CLASSIFIER_MODEL};
}

pipeline::config::trainer
    generate_mlp_action(json::istream& input, bool )
{
    using namespace json::literals;

    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        int it = -1;
        double e = 0.0;
        at ^ "iterations"_n ^ it ^ "epsilon"_n ^ e;
        if (at) {
            auto f = at ^ json::_child(at, "activation-function"_n);
            auto m = at ^ json::_child(at, "train-method"_n);
            auto l = at ^ json::_child(at, "layers"_n);
            if (f && m && l) {
                double a = 0, b = 0, x = 0, y = 0;
                std::string t;
                std::vector<int> layers;
                f ^ "alpha"_n ^ a ^ "beta"_n ^ b;
                m ^ "type"_n ^ t ^ "arg-a"_n ^ x ^ "arg-b"_n ^ y;
                l ^ json::start_arr ^ layers ^ json::end_arr;
                if (layers.empty()) {
                    LOG_ERROR<<"no layers defined or failed to read layers";
                    throw invalid_conf{"missing layers for MLP model"};
                }
                return pipeline::config::trainer{
                        regression::MLP_train_sv{
                            regression::MLP_train_sv::model_args{
                                it, e,
                                regression::MLP_train_sv::model_args::activation_function{a, b},
                                regression::MLP_train_sv::model_args::train_method{
                                    regression::MLP_train_sv::model_args::string2method(t), x, y
                                }, layers
                            }
                        },
                        actions::REGRESSION_MODEL
                    };
            }
        }
    }
    LOG_ERROR<<"failed to read attributes entry in json";
    throw invalid_conf{"failed to generate model MLP from configuration"};
}

pipeline::config::trainer
generate_k_nearest_action(json::istream& input, bool )
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        int nc = -1;
        std::string t;
        at ^ "count"_n ^ nc ^ "type"_n ^ t;
        if (at) {
            return pipeline::config::trainer{classifier::k_nearest_train_sv{
                classifier::k_nearest_train_sv::model_args{classifier::k_nearest_train_sv::model_args::string2type(t), nc}
                },
                actions::CLASSIFIER_MODEL
            };
        } else {
            LOG_ERROR<<"failed to read attributes for logistric regression";
            throw json_error{"invalid k nearst message format"};
        }
    } else {
        LOG_ERROR<<"failed to read attributes entry in json";
        throw invalid_conf{"failed to generate model K nearst from configuration"};
    }
}

pipeline::config::trainer
generate_logistic_regression(json::istream& input, bool classifier)
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        classifier::logistic_regression_train_sv::model_args arg;    // they have the same arg type
        std::string b, r;
        at ^ "learning-rate"_n ^ arg.learning_rate ^ "iterations"_n ^ arg.iterations ^
            "min-batch-size"_n ^ arg.min_batch_size ^ "reg-level"_n ^ r ^ "batch-type"_n ^ b;
        if (at) {
            arg.regular = convert_reg(r);
            arg.train_method = convert_method(b);
            if (classifier) {
                return pipeline::config::trainer{classifier::logistic_regression_train_sv{std::move(arg)},
                                    actions::CLASSIFIER_MODEL};
            } else {
                return pipeline::config::trainer{regression::logistic_regression_train_sv{std::move(arg)},
                                    actions::REGRESSION_MODEL};
            }
        } else {
            LOG_ERROR<<"failed to read attributes for logistric regression";
            throw json_error{"invalid logistic regresion message format"};
        }
    }
    LOG_ERROR<<"invalid logistic regression model definition at configuration";
    throw invalid_conf{"failed to generate model logistic regression from configuration"};
}

classifier::logistic_regression_train_sv::model_args::regularization_t
    convert_reg(const std::string& from)
{
    return classifier::logistic_regression_train_sv::model_args::string2reg(from);
}

classifier::logistic_regression_train_sv::model_args::train_method_t
    convert_method(const std::string& from)
{
    return classifier::logistic_regression_train_sv::model_args::string2train(from);
}

pipeline::config::trainer
    generate_svm_action(json::istream& input, bool classifier)
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        std::string k;
        at ^  "kernel"_n ^ k ;
        if (at) {
            if (classifier) {
                return pipeline::config::trainer{classifier::SVM_train_sv{
                    classifier::SVM_train_sv::model_args{
                            classifier::SVM_train_sv::model_impl::model::string2kernel(k),
                        }
                    },
                    actions::CLASSIFIER_MODEL
                };
            } else {
                return pipeline::config::trainer{regression::SVM_train_sv{
                    regression::SVM_train_sv::model_args{
                            regression::SVM_train_sv::model_impl::model::string2kernel(k)
                        }
                    },
                    actions::REGRESSION_MODEL
                };
            }
        } else {
            LOG_ERROR<<"failed to read SVM variables from configuration";
            throw json_error{"failed to read SVM variables from configuration"};
        }
    }
    LOG_ERROR<<"invalid SVM model definition at configuration";
    throw invalid_conf{"failed to generate model SVM from configuration"};
}

pipeline::config::trainer
    generate_em_action(json::istream& input, bool )
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        int cc = -1, mic = -1;
        float tc = 0.f;
        std::string s;
        at ^ "clusters"_n ^ cc ^ "cluster-min"_n ^ mic ^ "temination-count"_n ^ tc ^ "shape"_n ^ s;
        if (at) {
            return pipeline::config::trainer{
                classifier::EM_train_sv{classifier::EM_train_sv::model_args{cc, mic, tc, name2shape(s)}
                },
                actions::CLASSIFIER_MODEL
            };
        } else {
            LOG_ERROR<<"invalid EM model args";
            throw json_error{"failed to parse EM args"};
        }
    }
    LOG_ERROR<<"invalid EM model definition at configuration";
    throw invalid_conf{"failed to generate model EM from configuration"};
}

pipeline::config::trainer
    generate_boosting_action(json::istream& input, bool )
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        auto bs = read_dtree_args(at);
        int wc= -1;
        double w = -1.0;
        std::string t;
        at ^ "weak-count"_n ^ wc ^ "type"_n ^ t ^ "weight-trim-rate"_n ^ w;
        if (at) {
            return pipeline::config::trainer{
                classifier::boosting_train_sv{classifier::boosting_train_sv::model_args{
                    classifier:: boosting_train_sv::model_args::string2type(t), wc, w,
                                    bs.cv_fold, bs.max_categories, bs.max_depth,
                                    bs.min_samples, bs.use_surrogates,
                                    bs.prediction
                                }
                        }   ,
                actions::CLASSIFIER_MODEL
                };
        } else {
            LOG_ERROR<<"failed to read attributes for boosting";
            throw json_error{"error while processing boosting configuration"};
        }
    } else {
        LOG_ERROR<<"missing configuration values for boosting";
       throw invalid_conf{"missing confguration values for boosting"};
    }

}

pipeline::config::trainer
    generate_decision_trees_action(json::istream& input, bool )
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        auto arg = read_dtree_args(at);
        return pipeline::config::trainer{
                    classifier::decision_tree_train_sv{arg},
                    actions::CLASSIFIER_MODEL
                };
    } else {
        LOG_ERROR<<"failed to read attributes for decition trees";
        throw json_error{"error while processing decition trees configuration"};
    }
}

pipeline::config::trainer
    generate_random_tree_action(json::istream& input, bool )
{
    using namespace json::literals;
    auto at = input ^ json::_child(input, "attr"_n);
    if (at) {
        double ma = 0.0;
        bool vi = false;
        auto bs = read_dtree_args(at);
        at ^ "regression-accuracy"_n ^ ma ^ "var-importance"_n ^ vi;
        if (at) {
            return pipeline::config::trainer{
                    classifier::random_tree_train_sv{classifier::random_tree_train_sv::model_args{
                                  vi, ma, bs.cv_fold,
                                  bs.max_categories,
                                  bs.max_depth,
                                  bs.min_samples,
                                  bs.use_surrogates,
                                  bs.prediction
                                }
                            },
                            actions::CLASSIFIER_MODEL
                        };
        } else {
            LOG_ERROR<<"failed to read attributes for desition trees";
            throw json_error{"error while processing random trees configuration"};
        }
    } else {
        LOG_ERROR<<"missing configuration values for random trees";
       throw invalid_conf{"missing confguration values for random trees"};
    }
}

mlmodels::dtree_args
    read_dtree_args(json::istream& att)
{
    using namespace json::literals;

    int cvf = -1, mc = -1, md = -1,  ms = -1;
    bool us = false;
    std::string pt;
    att  ^ "flags"_n ^ pt ^ "max-categories"_n ^ mc ^ "cv-fold"_n ^ cvf ^ "max-depth"_n ^ md ^
        "min-samples"_n ^ ms ^ "use-surrogates"_n ^ us;
    if (att) {
        return mlmodels::dtree_args{cvf, mc, md, ms, us, mlmodels::dtree_args::string2type(pt)};
    } else {
        LOG_ERROR<<"failed to read decition tree attributes";
        throw json_error{"failed to read decition tree attributes"};
    }
}

mlmodels::expectation_maximization::args::covariance_matrices_t name2shape(const std::string& sn)
{
    return mlmodels::expectation_maximization::args::string2type(sn);
}

pipeline::config::trainer get_model_generators(json::istream& input, bool is_class)
{
    using namespace json::literals;

    if (input) {
        auto a = input ^ json::_child(input, "action"_n);
        if (a) {
            //return pipeline::config::trainers{};
            return process_action_entry(a, is_class);
        } else {
            LOG_ERROR<<"missing attribute actions in configuration";
            throw json_error{"missing actions in configuraiton"};
        }
    }
    LOG_ERROR<<"cannot read actions - invalid JSON";
    throw json_error{"invalid json - cannot process actions"};
}
}       // end of local namespace

///////////////////////////////////////////////////////////////////////////////
pipeline::config process_input(const std::string& from)
{
    static constexpr auto max_path = 256u;

    if (from.empty()) {
        throw invalid_conf{"empty configuration input"};
    }

    json::istream_root root;
    if (from.size() < max_path) {
        boost::filesystem::path p{from};
        if (boost::filesystem::exists(p)) {
            LOG_INFO<<"processing command from "<<from;
            return process(root ^ p);
        } else {
            LOG_INFO<<"processing command from command line";
            return process(root ^ from);
        }
    } else {
        LOG_INFO<<"processing command from command line";
        return process(root ^ from);
    }
}


// This file is a part of the OpenSurgSim project.
// Copyright 2013-2015, SimQuest Solutions Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "SurgSim/Devices/ReplayPoseDevice/ReplayPoseScaffold.h"

#include <Eigen/Core>

#include <fstream>

#include "SurgSim/DataStructures/DataGroupBuilder.h"
#include "SurgSim/Devices/ReplayPoseDevice/ReplayPoseDevice.h"
#include "SurgSim/Framework/ApplicationData.h"
#include "SurgSim/Framework/Log.h"
#include "SurgSim/Framework/Runtime.h"
#include "SurgSim/Framework/SharedInstance.h"

namespace
{
/// Missing istream "operator >>" for Eigen matrix type
/// http://eigen.tuxfamily.org/bz/show_bug.cgi?id=622
template<typename Derived>
std::istream& operator >>(std::istream& s, Eigen::MatrixBase<Derived>& m)
{
	for (int i = 0; i < m.rows(); ++i)
	{
		for (int j = 0; j < m.cols(); j++)
		{
			s >> m(i, j);
		}
	}
	return s;
}
}

namespace SurgSim
{
namespace Devices
{

ReplayPoseScaffold::ReplayPoseScaffold() :
	Framework::BasicThread("ReplayPoseScaffold"),
	m_logger(Framework::Logger::getLogger("Devices/ReplayPoseScaffold"))
{
	SURGSIM_LOG_DEBUG(m_logger) << "ReplayPose scaffold created.";
}

ReplayPoseScaffold::~ReplayPoseScaffold()
{
	if (m_device != nullptr)
	{
		unregisterDevice();
	}
	SURGSIM_LOG_DEBUG(m_logger) << "ReplayPose scaffold destroyed.";
}

std::shared_ptr<ReplayPoseScaffold> ReplayPoseScaffold::getOrCreateSharedInstance()
{
	static Framework::SharedInstance<ReplayPoseScaffold> sharedInstance;
	return sharedInstance.get();
}

bool ReplayPoseScaffold::doInitialize()
{
	return true;
}

bool ReplayPoseScaffold::doStartUp()
{
	return true;
}

bool ReplayPoseScaffold::doUpdate(double dt)
{
	boost::unique_lock<boost::mutex> scopedLock(m_deviceLock);

	SURGSIM_ASSERT(m_device != nullptr) << "DeviceData not properly allocated";

	return updateDevice(m_device.get());
}

struct ReplayPoseScaffold::DeviceData
{
	/// Constructor
	/// \param device Device to be managed by this scaffold
	explicit DeviceData(ReplayPoseDevice* device) :
		deviceObject(device),
		m_timestamp(0),
		m_index(0),
		m_fileLoaded(false)
	{
		m_fileLoaded = loadFile(deviceObject->getFileName());
		m_timer.start();
	}

	/// Retrieve the pose for the given time stamp "m_timestamp"
	/// \return The requested pose, Identity if none could be loaded
	Math::RigidTransform3d getPose()
	{
		if (m_motion.size() == 0)
		{
			return Math::RigidTransform3d::Identity();
		}

		if (m_motion.size() == 1 || (m_index == 0 && m_timestamp <= m_motion[0].first))
		{
			return m_motion[0].second;
		}

		while (m_motion.size() > m_index && m_timestamp > m_motion[m_index].first)
		{
			m_index++;
		}

		// Requesting a timestamp over the higher limit recorded in the file
		if (m_motion.size() <= m_index)
		{
			return m_motion[m_motion.size() - 1].second;
		}

		// Are we requesting exactly the timestamp we just indexed
		if (m_timestamp == m_motion[m_index].first)
		{
			return m_motion[m_index].second;
		}

		// Otherwise, we need to interpolate between the index and the previous index
		double range = m_motion[m_index].first - m_motion[m_index - 1].first;
		double time = (m_timestamp - m_motion[m_index - 1].first) / range;
		return Math::interpolate(m_motion[m_index - 1].second, m_motion[m_index].second, time);
	}

	/// Device object managed by this scaffold.
	ReplayPoseDevice* const deviceObject;

	/// Timer to keep track of the time stamp and keep the replay in sync with the recording
	Framework::Timer m_timer;

	/// Time stamp for the device (which time stamp are we reproducing?)
	double m_timestamp;

	/// Series of poses through time loaded from the input file
	std::vector<std::pair<double, Math::RigidTransform3d>> m_motion;

	/// Index of the latest pose used
	size_t m_index;

	/// Valid file loaded successfully
	bool m_fileLoaded;

private:
	// Prevent copy construction and copy assignment.  (VS2012 does not support "= delete" yet.)
	DeviceData(const DeviceData&) /*= delete*/;
	DeviceData& operator=(const DeviceData&) /*= delete*/;

	/// \param fileName the file to be open and loaded
	/// \return True if the file was loaded successfully, False otherwise
	bool loadFile(const std::string& fileName)
	{
		auto logger = Framework::Logger::getLogger("Devices/ReplayPoseScaffold");

		std::ifstream inputFile;
		bool result = true;

		inputFile.open(fileName, std::ios::in);

		if (!inputFile.is_open())
		{
			std::string path;
			if (SurgSim::Framework::Runtime::getApplicationData()->tryFindFile(fileName, &path))
			{
				inputFile.open(path, std::ios::in);
			}
		}

		if (inputFile.is_open())
		{
			while (!inputFile.eof())
			{
				double timeStamp;
				Math::RigidTransform3d pose;
				inputFile >> timeStamp >> pose.matrix();
				m_motion.emplace_back(timeStamp, pose);
			}
			SURGSIM_LOG_INFO(logger) << "Loaded " << m_motion.size() << " timestamps";
			if (m_motion.size() >= 1)
			{
				SURGSIM_LOG_INFO(logger) << "The loaded timestamps cover a range of " <<
										 m_motion[m_motion.size() - 1].first - m_motion[0].first << " second(s)";
			}
			SURGSIM_LOG_IF(m_motion.size() == 0, logger, WARNING) <<
					"No poses could be properly loaded, Identity pose will be used";

			inputFile.close();
		}
		else
		{
			SURGSIM_LOG_WARNING(logger) << "Could not find or open the file " << fileName <<
				"\nReplay will use Identity pose.";
			result = false;
		}

		return result;
	}
};

bool ReplayPoseScaffold::registerDevice(ReplayPoseDevice* device)
{
	boost::unique_lock<boost::mutex> scopedLock(m_deviceLock);
	SURGSIM_ASSERT(m_device == nullptr) << "Can't register two ReplayPoseDevice.";

	m_device.reset(new ReplayPoseScaffold::DeviceData(device));
	if (m_device == nullptr)
	{
		SURGSIM_LOG_CRITICAL(m_logger) << "Failed to create a DeviceData";
		return false;
	}
	if (!m_device->m_fileLoaded)
	{
		SURGSIM_LOG_CRITICAL(m_logger) << "Failed to load the file to replay";
		return false;
	}
	SURGSIM_LOG_DEBUG(m_logger) << "Registered device " << device->getName();

	bool success = true;
	if (!isRunning())
	{
		std::shared_ptr<Framework::Barrier> barrier = std::make_shared<Framework::Barrier>(2);
		start(barrier);
		barrier->wait(true); // Wait for initialize
		barrier->wait(true); // Wait for startup
		success = isInitialized();
	}

	if (success)
	{
		setRate(device->getRate());
	}

	return success;
}

bool ReplayPoseScaffold::unregisterDevice()
{

	if (isRunning())
	{
		stop();
	}

	// #threadsafety After unregistering, another thread could be in the process of registering.
	boost::unique_lock<boost::mutex> scopedLock(m_deviceLock);
	m_device.reset();
	SURGSIM_LOG_DEBUG(m_logger) << "Unregistered device";
	return true;
}

bool ReplayPoseScaffold::updateDevice(ReplayPoseScaffold::DeviceData* info)
{
	DataStructures::DataGroup& inputData = m_device->deviceObject->getInputData();

	info->m_timer.markFrame();
	info->m_timestamp += info->m_timer.getLastFramePeriod();

	Math::RigidTransform3d pose = info->getPose();
	inputData.poses().set(DataStructures::Names::POSE, pose);

	m_device->deviceObject->pushInput();

	return true;
}

DataStructures::DataGroup ReplayPoseScaffold::buildDeviceInputData()
{
	DataStructures::DataGroupBuilder builder;
	builder.addPose(DataStructures::Names::POSE);
	return builder.createData();
}

};  // namespace Devices
};  // namespace SurgSim


#include "loki/worker.h"
#include "loki/search.h"
#include "baldr/datetime.h"
#include "baldr/rapidjson_utils.h"
#include "midgard/logging.h"
#include <boost/property_tree/json_parser.hpp>

using namespace valhalla;
using namespace valhalla::baldr;

namespace {
  PointLL to_ll(const odin::Location& l) {
    return PointLL{l.ll().lng(), l.ll().lat()};
  }

  void check_distance(const google::protobuf::RepeatedPtrField<odin::Location>& locations, float matrix_max_distance, float& max_location_distance) {
    //see if any locations pairs are unreachable or too far apart
    for(auto source = locations.begin(); source != locations.end() - 1; ++source){
      for(auto target = source + 1; target != locations.end(); ++target){
        //check if distance between latlngs exceed max distance limit
        auto path_distance = to_ll(*source).Distance(to_ll(*target));

        if (path_distance >= max_location_distance) {
          max_location_distance = path_distance;
        }

        if (path_distance > matrix_max_distance)
          throw valhalla_exception_t{154};
      }
    }
  }
}

namespace valhalla {
  namespace loki {

    void loki_worker_t::init_isochrones(valhalla_request_t& request) {
      //strip off unused information
      parse_locations(request.options.mutable_locations());
      if(request.options.locations_size() < 1)
        throw valhalla_exception_t{120};
      for(auto& l : *request.options.mutable_locations())
        l.clear_heading();

      //make sure the isoline definitions are valid
      auto contours = rapidjson::get_optional<rapidjson::Value::ConstArray>(request.document, "/contours");
      if(! contours)
        throw valhalla_exception_t{113};
      //check that the number of contours is ok
      if(contours->Size() > max_contours)
        throw valhalla_exception_t{152, std::to_string(max_contours)};
      size_t prev = 0;
      for(const auto& contour : *contours) {
        const int c = rapidjson::get_optional<int>(contour, "/time").get_value_or(-1);
        if(c < prev || c == -1)
          throw valhalla_exception_t{111};
        if(c > max_time)
          throw valhalla_exception_t{151, std::to_string(max_time)};
        prev = c;
      }
      parse_costing(request);
    }
    void loki_worker_t::isochrones(valhalla_request_t& request) {
      init_isochrones(request);
      //check that location size does not exceed max
      if (request.options.locations_size() > max_locations.find("isochrone")->second)
        throw valhalla_exception_t{150, std::to_string(max_locations.find("isochrone")->second)};

      //check the distances
      auto max_location_distance = std::numeric_limits<float>::min();
      check_distance(request.options.locations(), max_distance.find("isochrone")->second, max_location_distance);
      if (!request.options.do_not_track())
        valhalla::midgard::logging::Log("max_location_distance::" + std::to_string(max_location_distance * kKmPerMeter) + "km", " [ANALYTICS] ");

      try{
        //correlate the various locations to the underlying graph
        auto locations = PathLocation::fromPBF(request.options.locations());
        const auto projections = loki::Search(locations, reader, edge_filter, node_filter);
        for(size_t i = 0; i < locations.size(); ++i) {
          const auto& projection = projections.at(locations[i]);
          PathLocation::toPBF(projection, request.options.mutable_locations(i), reader);
        }
      }
      catch(const std::exception&) {
        throw valhalla_exception_t{171};
      }
    }

  }
}


/*
 Copyright (C) 2020 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <orea/aggregation/dynamiccreditxvacalculator.hpp>

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/error_of_mean.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/stats.hpp>

using namespace std;
using namespace QuantLib;

using namespace boost::accumulators;

namespace ore {
namespace analytics {

DynamicCreditXvaCalculator::DynamicCreditXvaCalculator(
    //! Driving portfolio consistent with the cube below
    const boost::shared_ptr<Portfolio> portfolio, const boost::shared_ptr<Market> market,
    const string& configuration, const string& baseCurrency, const string& dvaName,
    const string& fvaBorrowingCurve, const string& fvaLendingCurve,
    const bool applyDynamicInitialMargin,
    const boost::shared_ptr<DynamicInitialMarginCalculator> dimCalculator,
    const boost::shared_ptr<NPVCube> tradeExposureCube,
    const boost::shared_ptr<NPVCube> nettingSetExposureCube,
    const boost::shared_ptr<NPVCube>& cptyCube,
    const Size tradeEpeIndex, const Size tradeEneIndex,
    const Size nettingSetEpeIndex, const Size nettingSetEneIndex, const Size cptySpIndex,
    const bool flipViewXVA, const string& flipViewBorrowingCurvePostfix, const string& flipViewLendingCurvePostfix)
    : ValueAdjustmentCalculator(portfolio, market, configuration, baseCurrency, dvaName,
                                fvaBorrowingCurve, fvaLendingCurve, applyDynamicInitialMargin,
                                dimCalculator, tradeExposureCube, nettingSetExposureCube, tradeEpeIndex, tradeEneIndex,
                                nettingSetEpeIndex, nettingSetEneIndex,
                                flipViewXVA, flipViewBorrowingCurvePostfix, flipViewLendingCurvePostfix),
      cptyCube_(cptyCube), cptySpIndex_(cptySpIndex) {
    // check consistency of input

    QL_REQUIRE(tradeExposureCube_->numDates() == cptyCube->numDates(),
        "number of dates in tradeExposureCube and cptyCube mismatch ("
        << tradeExposureCube_->numDates() << " vs " << cptyCube->numDates() << ")");

    QL_REQUIRE(cptySpIndex < cptyCube->depth(), "cptySpIndex("
        << cptySpIndex << ") exceeds depth of cptyCube("
        << cptyCube->depth() << ")");

    for (Size i = 0; i < tradeExposureCube_->numDates(); i++) {
        QL_REQUIRE(tradeExposureCube_->dates()[i] == cptyCube->dates()[i],
            "date at " << i << " in tradeExposureCube and cptyCube mismatch ("
            << tradeExposureCube_->dates()[i] << " vs " << cptyCube->dates()[i] << ")");
    }
}

const Real DynamicCreditXvaCalculator::calculateCvaIncrement(
    const string& tid, const string& cid, const Date& d0, const Date& d1, const Real& rr) {
    Real increment = 0.0;
    for (Size k = 0; k < tradeExposureCube_->samples(); ++k) {
        Real s0 = d0 == asof() ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = cptyCube_->get(cid, d1, k, cptySpIndex_);
        Real epe = tradeExposureCube_->get(tid, d1, k, tradeEpeIndex_);
        increment += (s0 - s1) * epe;
    }
    return (1.0 - rr) * increment / tradeExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateDvaIncrement(
    const string& tid, const Date& d0, const Date& d1, const Real& rr) {
    Real increment = 0.0;
    for (Size k = 0; k < tradeExposureCube_->samples(); ++k) {
        Real s0 = d0 == asof() ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real s1 = cptyCube_->get(dvaName_, d1, k, cptySpIndex_);
        Real ene = tradeExposureCube_->get(tid, d1, k, tradeEneIndex_);
        increment += (s0 - s1) * ene;
    }
    return (1.0 - rr) * increment / tradeExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateNettingSetCvaIncrement(
    const string& nid, const string& cid, const Date& d0, const Date& d1, const Real& rr) {
    Real increment = 0.0;
    for (Size k = 0; k < nettingSetExposureCube_->samples(); ++k) {
        Real s0 = d0 == asof() ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = cptyCube_->get(cid, d1, k, cptySpIndex_);
        Real epe = nettingSetExposureCube_->get(nid, d1, k, nettingSetEpeIndex_);
        increment += (s0 - s1) * epe;
    }
    return (1.0 - rr) * increment / nettingSetExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateNettingSetDvaIncrement(
    const string& nid, const Date& d0, const Date& d1, const Real& rr) {
    Real increment = 0.0;
    for (Size k = 0; k < nettingSetExposureCube_->samples(); ++k) {
        Real s0 = d0 == asof() ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real s1 = cptyCube_->get(dvaName_, d1, k, cptySpIndex_);
        Real ene = nettingSetExposureCube_->get(nid, d1, k, nettingSetEneIndex_);
        increment += (s0 - s1) * ene;
    }
    return (1.0 - rr) * increment / nettingSetExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateFbaIncrement(
    const string& tid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Real increment = 0.0;
    for (Size k = 0; k < tradeExposureCube_->samples(); ++k) {
        Real s0 = (d0 == asof() || cid == "") ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = (d0 == asof() || dvaName == "") ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real ene = tradeExposureCube_->get(tid, d1, k, tradeEneIndex_);
        increment += s0 * s1 * ene;
    }
    return increment * dcf / tradeExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateFcaIncrement(
    const string& tid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Real increment = 0.0;
    for (Size k = 0; k < tradeExposureCube_->samples(); ++k) {
        Real s0 = (d0 == asof() || cid == "") ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = (d0 == asof() || dvaName == "") ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real epe = tradeExposureCube_->get(tid, d1, k, tradeEpeIndex_);
        increment += s0 * s1 * epe;
    }
    return increment * dcf / tradeExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateNettingSetFbaIncrement(
    const string& nid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Real increment = 0.0;
    for (Size k = 0; k < nettingSetExposureCube_->samples(); ++k) {
        Real s0 = (d0 == asof() || cid == "") ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = (d0 == asof() || dvaName == "") ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real ene = nettingSetExposureCube_->get(nid, d1, k, nettingSetEneIndex_);
        increment += s0 * s1 * ene;
    }
    return increment * dcf / nettingSetExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateNettingSetFcaIncrement(
    const string& nid, const string& cid, const string& dvaName,
    const Date& d0, const Date& d1, const Real& dcf) {
    Real increment = 0.0;
    for (Size k = 0; k < nettingSetExposureCube_->samples(); ++k) {
        Real s0 = (d0 == asof() || cid == "") ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = (d0 == asof() || dvaName == "") ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real epe = nettingSetExposureCube_->get(nid, d1, k, nettingSetEpeIndex_);
        increment += s0 * s1 * epe;
    }
    return increment * dcf / nettingSetExposureCube_->samples();
}

const Real DynamicCreditXvaCalculator::calculateNettingSetMvaIncrement(
    const string& nid, const string& cid, const Date& d0, const Date& d1, const Real& dcf) {

    Real increment = 0.0;
    for (Size k = 0; k < nettingSetExposureCube_->samples(); ++k) {
        Real s0 = (d0 == asof() || cid == "") ? 1.0 : cptyCube_->get(cid, d0, k, cptySpIndex_);
        Real s1 = (d0 == asof() || dvaName_ == "") ? 1.0 : cptyCube_->get(dvaName_, d0, k, cptySpIndex_);
        Real im = dimCalculator_->dimCube()->get(nid, d1, k);
        increment += s0 * s1 * im;
    }
    return increment * dcf / nettingSetExposureCube_->samples();
}

} // namespace analytics
} // namespace ore


// Author(s): Aad Mathijssen, Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// 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)
//
/// \file typecheck_test.cpp
/// \brief Add your file description here.

// Some tests rely on type check failures, so we have to set this flag.
#define MCRL2_DISABLE_TYPECHECK_ASSERTIONS

#include "mcrl2/data/data_specification.h"
#include "mcrl2/data/experimental/type_check_tree.h"
#include "mcrl2/data/parse.h"
#include "mcrl2/data/print.h"
#include "mcrl2/data/typecheck.h"
#include "mcrl2/data/untyped_sort.h"
#include "mcrl2/utilities/text_utility.h"
#include <boost/test/included/unit_test_framework.hpp>
#include <iostream>
#include <sstream>

using namespace mcrl2;

inline
data::sort_expression pos()
{
  return data::sort_pos::pos();
}

inline
data::sort_expression nat()
{
  return data::sort_nat::nat();
}

inline
data::sort_expression list(const data::sort_expression& x)
{
  return data::sort_list::list(x);
}

inline
data::variable var(const std::string& name, const data::sort_expression& sort)
{
  return data::variable(name, sort);
}

// Expected failures, these are not going to be fixed in the current
// implementation of the type checker
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_multiple_variables, 1)
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_multiple_variables_reversed, 1)
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_matching_ambiguous, 1)               // Fails because of reordering in type checker / pretty printer
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_matching_ambiguous_rhs, 1)           // Fails because of reordering in type checker / pretty printer
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_ambiguous_function_application4, 1)  // Fails because of reordering in type checker / pretty printer
//BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(test_ambiguous_function_application4a, 1) // Fails because of reordering in type checker / pretty printer

// Parse functions that do not change any context (i.e. do not typecheck and
// normalise sorts).
data::sort_expression parse_sort_expression(const std::string& de_in)
{
  data::sort_expression result;
  try {
    result = data::detail::parse_sort_expression_new(de_in);
    std::string de_out = data::pp(result);
    if (de_in != de_out)
    {
      std::clog << "aterm : " << result << std::endl;
      std::clog << "de_in : " << de_in << std::endl;
      std::clog << "de_out: " << de_out << std::endl;
      std::clog << "The following sort expressions should be the same:" << std::endl << "  " << de_in  << std::endl << "  " << de_out << std::endl;
      BOOST_CHECK_EQUAL(de_in, de_out);
    }
  }
  catch (...)
  {
    BOOST_CHECK(false);
  }
  return result;
}

data::data_expression parse_data_expression(const std::string& de_in)
{
  data::data_expression result;
  try {
    result = data::detail::parse_data_expression_new(de_in);
#ifdef MCRL2_ENABLE_TYPECHECK_PP_TESTS
    std::string de_out = data::pp(result);
    if (de_in != de_out)
    {
      std::clog << "aterm : " << result << std::endl;
      std::clog << "de_in : " << de_in << std::endl;
      std::clog << "de_out: " << de_out << std::endl;
      std::clog << "The following data expressions should be the same:" << std::endl << "  " << de_in  << std::endl << "  " << de_out << std::endl;
      BOOST_CHECK_EQUAL(de_in, de_out);
    }
#endif
  }
  catch (...)
  {
    BOOST_CHECK(false);
  }
  return result;
}

data::data_specification parse_data_specification(const std::string& de_in, bool expect_success = true)
{
  data::data_specification result;
  try {
    result = data::detail::parse_data_specification_new(de_in);
    std::string de_out = data::pp(result);

    std::string input = utilities::trim_copy(de_in);
    std::string output = utilities::trim_copy(de_out);
    if (input != output)
    {
      std::clog << "aterm : " << data::detail::data_specification_to_aterm(result) << std::endl;
      std::clog << "de_in : " << de_in << std::endl;
      std::clog << "de_out: " << de_out << std::endl;
      std::clog << "The following data specifications should be the same:" << std::endl << "  " << de_in  << std::endl << "  " << de_out << std::endl;
      BOOST_CHECK_EQUAL(input, output);
    }
  }
  catch (...)
  {
    BOOST_CHECK(!expect_success);
  }
  return result;
}

void test_data_expression(const std::string& de_in,
                          const data::variable_vector& variable_context,
                          bool expect_success = true,
                          const std::string& expected_sort = "",
                          bool test_type_checker = true)
{
  data::data_expression x(parse_data_expression(de_in));
  std::clog << std::endl
            << "==========================================" << std::endl
            << "Testing type checking of data expression: " << std::endl
            << "  de_in:  " << de_in << std::endl
            << "  de_out: " << pp(x) << std::endl
            << "  expect success: " << (expect_success?("yes"):("no")) << std::endl
            << "  expected type: " << expected_sort << std::endl
            << "  detected type: " << pp(x.sort()) << " (before typecheckeing) " << std::endl;

  if (test_type_checker)
  {
    if (expect_success)
    {
      BOOST_CHECK_NO_THROW(x = data::typecheck_data_expression(x, variable_context));
      BOOST_CHECK_NE(x, data::data_expression());

      std::string de_out = data::pp(x);
      //std::clog << "The following data expressions should be the same:" << std::endl << "  " << de_in  << std::endl << "  " << de_out << std::endl;
//#ifdef MCRL2_ENABLE_TYPECHECK_PP_TESTS
      BOOST_CHECK_EQUAL(de_in, de_out);
//#endif
      // TODO: this check should be uncommented
      //BOOST_CHECK(!search_sort_expression(x.sort(), data::untyped_sort()));
      if (expected_sort != "")
      {
        BOOST_CHECK_EQUAL(x.sort(), parse_sort_expression(expected_sort));
        std::clog << "    expression x in internal format: " << x << std::endl;
      }
      else
      {
        std::clog << "  failed to typecheck" << std::endl;
      }
    }
    else
    {
      BOOST_CHECK_THROW(x = data::typecheck_data_expression(x), mcrl2::runtime_error);
    }
  }
}

void test_data_expression(const std::string& de_in,
                          bool expect_success = true,
                          const std::string& expected_sort = "",
                          bool test_type_checker = true)
{
  data::variable_vector v;
  test_data_expression(de_in, v, expect_success, expected_sort, test_type_checker);
}

BOOST_AUTO_TEST_CASE(test_true)
{
  //test boolean data expressions
  test_data_expression("true", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_if)
{
  test_data_expression("if(true, true, false)", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_not)
{
  test_data_expression("!true", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_and)
{
  test_data_expression("true && false", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_zero)
{
  test_data_expression("0", true, "Nat");
}

BOOST_AUTO_TEST_CASE(test_minus_one)
{
  test_data_expression("-1", true, "Int");
}

BOOST_AUTO_TEST_CASE(test_zero_plus_one)
{
  test_data_expression("0 + 1", true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_one_plus_zero)
{
  test_data_expression("1 + 0", true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_zero_plus_zero)
{
  test_data_expression("0 + 0", true, "Nat");
}

BOOST_AUTO_TEST_CASE(test_one_plus_one)
{
  test_data_expression("1 + 1", true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_one_times_two_plus_three)
{
  test_data_expression("1 * 2 + 3", true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_empty_list)
{
  test_data_expression("[]", true); // List unknown
}

BOOST_AUTO_TEST_CASE(test_empty_list_concat)
{
  test_data_expression("[] ++ []", true); // List unknown
}

BOOST_AUTO_TEST_CASE(test_empty_list_size)
{
  test_data_expression("#[]", true, "Nat");
}

BOOST_AUTO_TEST_CASE(test_empty_list_in)
{
  test_data_expression("true in []", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_list_true_false)
{
  test_data_expression("[true, false]", true, "List(Bool)");
}

BOOST_AUTO_TEST_CASE(test_list_zero)
{
  test_data_expression("[0]", true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_one_two)
{
  test_data_expression("[1, 2]", true, "List(Pos)");
}

BOOST_AUTO_TEST_CASE(test_list_zero_concat_one_two)
{
  test_data_expression("[0] ++ [1, 2]", true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_nat_pos)
{
  test_data_expression("[0, 1, 2]", true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_pos_nat)
{
  test_data_expression("[1, 0, 2]", true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_nat_concat_one_two)
{
  test_data_expression("l ++ [1, 2]", { var("l", list(nat())) }, true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_pos_concat_one_two)
{

  test_data_expression("l ++ [1, 2]", { var("l", list(pos())) }, true, "List(Pos)");
}

BOOST_AUTO_TEST_CASE(test_list_zero_concat_list_pos)
{
  test_data_expression("[0] ++ l", { var("l", list(pos())) }, false);
}

BOOST_AUTO_TEST_CASE(test_list_zero_concat_list_nat)
{
  test_data_expression("[0] ++ l", { var("l", list(nat())) }, true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_list_pos_concat_list_nat)
{
  test_data_expression("x ++ y", { var("x", list(pos())), var("y", list(nat())) }, false);
}

BOOST_AUTO_TEST_CASE(test_list_is_list_nat)
{
  test_data_expression("x == y", { var("x", list(pos())),  var("y", list(nat())) }, false);
}

BOOST_AUTO_TEST_CASE(test_emptyset)
{
  test_data_expression("{}", true);
}

BOOST_AUTO_TEST_CASE(test_emptyset_complement)
{
  test_data_expression("!{}", true);
}

BOOST_AUTO_TEST_CASE(test_emptyset_complement_subset)
{
  test_data_expression("!{} <= {}", true);
}

BOOST_AUTO_TEST_CASE(test_emptyset_complement_subset_reverse)
{
  test_data_expression("{} <= !{}", true);
}

BOOST_AUTO_TEST_CASE(test_set_true_false)
{
  test_data_expression("{ true, false }", true, "FSet(Bool)");
}

BOOST_AUTO_TEST_CASE(test_set_numbers)
{
  test_data_expression("{ 1, 2, -7 }", true, "FSet(Int)");
}

BOOST_AUTO_TEST_CASE(test_set_comprehension)
{
  test_data_expression("{ x: Nat | x mod 2 == 0 }", true, "Set(Nat)");
}

BOOST_AUTO_TEST_CASE(test_emptybag)
{
  test_data_expression("{:}", true);
}

BOOST_AUTO_TEST_CASE(test_emptybag_complement)
{
  test_data_expression("!{:}", false);
}
BOOST_AUTO_TEST_CASE(test_bag_true_false)
{
  test_data_expression("{ true: 1, false: 2 }", true, "FBag(Bool)");
}

BOOST_AUTO_TEST_CASE(test_bag_numbers)
{
  test_data_expression("{ 1: 1, 2: 2, -8: 8 }", true, "FBag(Int)");
}

BOOST_AUTO_TEST_CASE(test_bag_comprehension)
{
  test_data_expression("{ x: Nat | (lambda y: Nat. y * y)(x) }", true, "Bag(Nat)");
}

BOOST_AUTO_TEST_CASE(test_function_updates)
{
  //test function updates
  test_data_expression("(lambda x: Bool. x)[true -> false]", true);
  test_data_expression("(lambda x: Bool. x)[0 -> false]", false);
  test_data_expression("(lambda x: Bool. x)[true -> false][false -> true]", true);
  test_data_expression("(lambda n: Nat. n mod 2 == 0)[0 -> false]", true);
}

BOOST_AUTO_TEST_CASE(test_inline_struct_recogniser)
{
  test_data_expression("lambda x: struct t?is_t. x == t", false);
}

BOOST_AUTO_TEST_CASE(test_inline_struct)
{
  test_data_expression("lambda x: struct t. x == t", false);
}

BOOST_AUTO_TEST_CASE(test_inline_structs_compare)
{
  test_data_expression("lambda x,y: struct t. x == y", true, "struct t # struct t -> Bool");
}

BOOST_AUTO_TEST_CASE(test_inline_structs_compare_recogniser)
{
  test_data_expression("lambda x: struct t?is_t, y: struct t. x == y", false);
}

BOOST_AUTO_TEST_CASE(test_lambda_aliasing)
{
  test_data_expression("lambda f: Nat. lambda f: Nat -> Bool. f(f)", false);
}

BOOST_AUTO_TEST_CASE(test_forall_structs_compare)
{
  test_data_expression("forall x,y: struct t. x == y", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_forall_simple)
{
  test_data_expression("forall n: Nat. n >= 0", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_forall_simple_nat_vs_int)
{
  test_data_expression("forall n: Nat. n > -1", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_exists_structs_compare)
{
  test_data_expression("exists x,y: struct t. x == y", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_exists_simple)
{
  test_data_expression("exists n: Nat. n > 481", true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_equal_context)
{
  test_data_expression("x == y", { var("x", parse_sort_expression("struct t?is_t")), var("y", parse_sort_expression("struct t?is_t")) }, true, "Bool");
}

BOOST_AUTO_TEST_CASE(test_not_equal_context)
{
  test_data_expression("x == y", { var("x", parse_sort_expression("struct t")), var("y", parse_sort_expression("struct t?is_t")) }, false);
}

BOOST_AUTO_TEST_CASE(test_where)
{
  test_data_expression("x + y whr x = 3, y = 10 end", true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_var_one_occurs_in_two)
{
  test_data_expression("x + y whr x = 3, y = x + 10 end", false);
}

BOOST_AUTO_TEST_CASE(test_where_var_one_and_two_occur_in_two)
{
  test_data_expression("x + y whr x = 3, y = x + y + 10 end", false);
}

BOOST_AUTO_TEST_CASE(test_where_var_two_occurs_in_one)
{
  test_data_expression("x + y whr x = y + 10, y = 3 end", false);
}

BOOST_AUTO_TEST_CASE(test_where_var_one_occurs_in_two_and_vice_versa)
{
  test_data_expression("x + y whr x = y + 10, y = x + 3 end", false);
}

BOOST_AUTO_TEST_CASE(test_where_in_context)
{
  test_data_expression("x + y whr x = 3, y = 0 end", { var("x", pos()), var("y", nat()) }, true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_var_one_occurs_in_two_in_context)
{
  test_data_expression("x + y whr x = 3, y = x + 10 end", { var("x", pos()), var("y", pos()) }, true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_var_one_and_two_occur_in_two_in_context)
{
  test_data_expression("x + y whr x = 3, y = x + y + 10 end", { var("x", pos()), var("y", pos()) }, true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_var_two_occurs_in_one_in_context)
{
  test_data_expression("x + y whr x = y + 10, y = 0 end", { var("x", pos()), var("y", nat()) }, true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_var_one_occurs_in_two_and_vice_versa_in_context)
{
  test_data_expression("x + y whr x = y + 10, y = x + 3 end", { var("x", pos()), var("y", pos()) }, true, "Pos");
}

BOOST_AUTO_TEST_CASE(test_where_mix_nat_pos_list)
{
  test_data_expression("x ++ y whr x = [0, y], y = [x] end", { var("x", pos()), var("y", nat()) }, false);
}

BOOST_AUTO_TEST_CASE(test_where_mix_nat_list)
{
  test_data_expression("x1 ++ y whr x1 = [0, z], y = [x] end", { var("x", nat()), var("z", nat()) }, true, "List(Nat)");
}

BOOST_AUTO_TEST_CASE(test_upcast_pos2nat)
{
  test_data_expression("x + y", { var("x", pos()), var("y", nat()) }, true, "Pos");
  test_data_expression("x == y", { var("x", pos()), var("y", nat()) }, true, "Bool");
}

void test_data_specification(const std::string& ds_in,
                             bool expect_success = true,
                             bool test_type_checker = true)
{
  data::data_specification ds(parse_data_specification(ds_in, expect_success));

  if (test_type_checker)
  {

    if (expect_success)
    {
      data::typecheck_data_specification(ds);
      //Cannot pretty print a data specification anymore.
      //std::string ds_out = data::pp(ds);
      //BOOST_CHECK_EQUAL(ds_in, ds_out);
    }
    else
    {
      BOOST_CHECK_THROW(data::typecheck_data_specification(ds), mcrl2::runtime_error);
    }
  }
}

BOOST_AUTO_TEST_CASE(test_data_specification_struct_with_projection)
{
  //test data specification involving structured sorts
  //in which projection functions are reused
  test_data_specification(
    "sort S = struct c(p: Bool) | d(p: Bool, q: S);\n"
  );
}

// What is the desired result here? I would expect an exception (JK)
BOOST_AUTO_TEST_CASE(test_duplicate_sort)
{
  test_data_specification(
    "sort S = struct c;\n"
    "     S = Nat;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_data_specification_constructor_same_signature)
{
  //test data specification involving multiple constructors/functions with
  //different signatures
  test_data_specification(
    "sort S;\n"
    "     T;\n"
    "\n"
    "cons f: S;\n"
    "     f: T;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_data_specification_constructor_map_same_signature)
{
  //test data specification involving multiple constructors/functions with
  //different signatures
  test_data_specification(
    "sort S;\n"
    "     T;\n"
    "\n"
    "cons f: S;\n"
    "\n"
    "map  f: T;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_data_specification_constructor_different_signature)
{
  test_data_specification(
    "sort S;\n"
    "     T;\n"
    "\n"
    "cons f: S;\n"
    "     f: S -> T;\n"
  );
}

BOOST_AUTO_TEST_CASE(test_data_specification_nested_struct)
{
  test_data_specification(
    "sort S = struct t(struct e(Nat));\n"
  );
}

//BOOST_AUTO_TEST_CASE(test_multiple_variables)
//{
//  test_data_specification(
//    "sort S;\n\n"
//    "var  x: Nat;\n"
//    "     x: S;\n"
//    "eqn  x == x + 1  =  true;\n",
//    false
//  );
//}
//
//BOOST_AUTO_TEST_CASE(test_multiple_variables_reversed)
//{
//  test_data_specification(
//    "sort S;\n\n"
//    "var  x: S;\n"
//    "     x: Nat;\n"
//    "eqn  x == x + 1  =  true;\n",
//    false
//  );
//}

BOOST_AUTO_TEST_CASE(test_sort_as_variable)
{
  test_data_specification(
    "sort S;\n\n"
    "map  S: S -> Bool;\n\n"
    "var  S: S;\n"
    "eqn  S(S)  =  S == S;\n",
    false
  );
}

/* BOOST_AUTO_TEST_CASE(test_predefined_aliases)   // This test case leads to a parse error, not a typecheck error.
{
  test_data_specification(
    "sort Nat = Int;\n",
    false, // parse error
    false  // so do not test type checker
  );
} */

/* BOOST_AUTO_TEST_CASE(test_conflicting_aliases) // This test case leads to a parse error, due to the use of Nat.
                                                  // This is not a typecheck error. Therefore this case is outcommented.
{
  test_data_specification(
    "sort S = Nat;\n"
    "     S = T;\n"
    "     T = Int;\n",
    false
  );
} */

/* BOOST_AUTO_TEST_CASE(test_conflicting_aliases_predefined_left)  // This test case leads to a parse error, due to the use of Nat.
{
  test_data_specification(
    "sort Nat = S;\n"
    "     S = T;\n"
    "     T = Int;\n",
    false, // parse error
    false  // so do not test type checker
  );
} */

BOOST_AUTO_TEST_CASE(test_cyclic_aliases)
{
  test_data_specification(
    "sort S = U;\n"
    "     U = S;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_cyclic_aliases_indirect)
{
  test_data_specification(
    "sort S = U;\n"
    "     U = T;\n"
    "     T = S;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_matching)
{
  test_data_specification(
    "map  f: Pos # Nat -> Bool;\n\n"
    "var  x: Pos;\n"
    "     y: Nat;\n"
    "eqn  f(x, y)  =  true;\n",
    true
  );
}

BOOST_AUTO_TEST_CASE(test_matching_non_strict)
{
  test_data_specification(
    "map  f: Pos # Nat -> Bool;\n\n"
    "var  x: Pos;\n"
    "     y: Nat;\n"
    "eqn  f(x, x)  =  true;\n",
    true
  );
}

//BOOST_AUTO_TEST_CASE(test_matching_ambiguous)
//{
//  test_data_specification(
//    "map  f: Pos # Nat -> Bool;\n"
//    "     f: Nat # Nat -> Bool;\n\n"
//    "var  x: Pos;\n"
//    "     y: Nat;\n"
//    "eqn  f(x, y)  =  false;\n\n"
//    "var  x: Pos;\n"
//    "     y: Nat;\n"
//    "eqn  f(y, y)  =  true;\n",
//    true
//  );
//}
//
//BOOST_AUTO_TEST_CASE(test_matching_ambiguous_rhs)
//{
//  test_data_specification(
//    "map  f: Int;\n\n"
//    "var  x: Pos;\n"
//    "eqn  f(x)  =  -5;\n\n"
//    "var  x: Pos;\n"
//    "eqn  f(x)  =  3;\n",
//    false
//  );
//}

BOOST_AUTO_TEST_CASE(test_function_alias)
{
  test_data_specification(
    "sort Array = Nat -> Nat;\n\n"
    "map  update: Nat # Nat # Array -> Array;\n\n"
    "var  i,n: Nat;\n"
    "     f: Array;\n"
    "eqn  update(i, n, f)  =  lambda j: Nat. if(i == j, n, f(j));\n",
    true
  );
}

// Test case for bug #787
BOOST_AUTO_TEST_CASE(test_eqn_set_where)
{
  test_data_specification(
    "map  f_dot: Set(Bool);\n\n"
    "eqn  f_dot  =  if(true, {}, { o: Bool | true whr z=true end });\n",
    true
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_function_sort)
{
  test_data_specification(
    "sort G;\n"
    "     F = F -> G;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_function_sort_reverse)
{
  test_data_specification(
    "sort G;\n"
    "     F = G -> F;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_struct_no_base)
{
  test_data_specification(
    "sort D = struct f(D);\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_struct_via_function)
{
  test_data_specification(
    "sort G = struct f(Nat -> G);\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_struct_list)
{
  test_data_specification(
    "sort P = struct b(x: List(P));\n",
    true
  );
}

BOOST_AUTO_TEST_CASE(test_recursive_struct_list_indirect)
{
  test_data_specification(
    "sort LP = List(P);\n"
    "     P = struct b(x: LP);\n",
    true
  );
}

BOOST_AUTO_TEST_CASE(test_alias_loop) // This is a correct declaration, Typical elements of sort B are [], [f([])].
{
  test_data_specification(
    "sort B = List(struct f(B));\n",
    true
  );
}

BOOST_AUTO_TEST_CASE(test_alias_loop_extended)
{
  test_data_specification(
    "sort B = List(struct f(B) | c);\n",
    true
  );
}

void test_data_expression_in_specification_context(const std::string& de_in,
    const std::string& ds_in,
    const data::variable_vector& variable_context,
    bool expect_success = true,
    const std::string& expected_sort = "",
    bool test_type_checker = true)
{
  data::data_specification ds(parse_data_specification(ds_in));

  if (test_type_checker)
  {
    data::typecheck_data_specification(ds);
    std::string ds_out = data::pp(ds);
    if (utilities::trim_copy(ds_in) != utilities::trim_copy(ds_out))
    {
      std::clog << "Warning, ds_in != ds_out; [" << utilities::trim_copy(ds_in) << " != " << utilities::trim_copy(ds_out) << "]" << std::endl;
    }
    BOOST_CHECK_EQUAL(utilities::trim_copy(ds_in), utilities::trim_copy(ds_out));
  }

  data::data_expression de(parse_data_expression(de_in));

  if (test_type_checker)
  {
    if (expect_success)
    {
      de = data::typecheck_data_expression(de, variable_context, ds);

      std::string de_out = data::pp(de);

      BOOST_CHECK_EQUAL(de_in, de_out);
      if (expected_sort != "")
      {
        BOOST_CHECK_EQUAL(de.sort(), parse_sort_expression(expected_sort));
      }
    }
    else
    {
      BOOST_CHECK_THROW(de = data::typecheck_data_expression(de, variable_context, ds), mcrl2::runtime_error);
    }
  }
}

void test_data_expression_in_specification_context(const std::string& de_in,
    const std::string& ds_in,
    bool expect_success = true,
    const std::string& expected_sort = "",
    bool test_type_checker = true)
{
  data::variable_vector v;
  test_data_expression_in_specification_context(de_in, ds_in, v, expect_success, expected_sort, test_type_checker);
}

BOOST_AUTO_TEST_CASE(test_data_expressions_different_signature)
{
  test_data_expression_in_specification_context(
    "f(f)",
    "sort S;\n"
    "     T;\n"
    "\n"
    "cons f: S;\n"
    "     f: S -> T;\n",
    true,
    "T"
  );
}

BOOST_AUTO_TEST_CASE(test_data_expressions_struct)
{
  test_data_expression_in_specification_context(
    "x == t(e(3))",
    "sort S = struct t(struct e(Nat));\n",
    { var("x", data::basic_sort("S")) },
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_lambda_variable_aliasing)
{
  test_data_expression_in_specification_context(
    "lambda x: S. x(x)",
    "sort S;\n"
    "     T;\n",
    { var("x", make_function_sort(data::basic_sort("S"), data::basic_sort("T"))) },
    false);
}

BOOST_AUTO_TEST_CASE(test_lambda_anonymous_struct)
{
  test_data_expression_in_specification_context(
    "lambda x: struct t. f(x)",
    "map  f: struct t -> Bool;\n",
    true,
    "struct t -> Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_application)
{
  test_data_expression_in_specification_context(
    "f(f)",
    "map  f: Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Nat"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_reverse)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat;\n"
    "     f: Nat -> Bool;\n",
    true,
    "Nat"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_larger)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat -> Bool;\n"
    "     f: Nat # Nat -> Bool;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_horrible)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat -> Bool;\n"
    "     f: Nat # Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Nat"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_horrible_app1)
{
  test_data_expression_in_specification_context(
    "f(f)",
    "map  f: Nat -> Bool;\n"
    "     f: Nat # Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_horrible_app2)
{
  test_data_expression_in_specification_context(
    "f(f, f)",
    "map  f: Nat -> Bool;\n"
    "     f: Nat # Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_horrible_abs)
{
  test_data_expression_in_specification_context(
    "f((lambda x: Bool. f)(f(f, f)))",
    "map  f: Nat -> Bool;\n"
    "     f: Nat # Nat -> Bool;\n"
    "     f: Nat;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_different_arity_functional)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat -> Nat -> Bool;\n"
    "     f: Nat -> Bool;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_same_arity)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Pos -> Nat;\n"
    "     f: Nat -> Pos;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_same_arity_application_nat_constant)
{
  test_data_expression_in_specification_context(
    "f(0)",
    "map  f: Pos -> Nat;\n"
    "     f: Nat -> Pos;\n",
    true,
    "Pos"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_same_arity_application_pos_constant)
{
  test_data_expression_in_specification_context(
    "f(1)",
    "map  f: Pos -> Nat;\n"
    "     f: Nat -> Pos;\n",
    true,
    "Nat"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_same_arity_application_nat_variable)
{
  test_data_expression_in_specification_context(
    "f(x)",
    "map  f: Pos -> Nat;\n"
    "     f: Nat -> Pos;\n",
    { var("x", nat()) },
    true,
    "Pos"
  );
}

BOOST_AUTO_TEST_CASE(test_duplicate_function_same_arity_application_pos_variable)
{
  test_data_expression_in_specification_context(
    "f(x)",
    "map  f: Pos -> Nat;\n"
    "     f: Nat -> Pos;\n",
    { var("x", pos()) },
    true,
    "Nat"
  );
}

BOOST_AUTO_TEST_CASE(test_function_symbol)
{
  test_data_expression_in_specification_context(
    "f",
    "map  f: Nat -> Bool;\n",
    true,
    "Nat -> Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_pos_constant)
{
  test_data_expression_in_specification_context(
    "f(1)",
    "map  f: Nat -> Bool;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_nat_constant)
{
  test_data_expression_in_specification_context(
    "f(0)",
    "map  f: Nat -> Bool;\n",
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_int_constant)
{
  test_data_expression_in_specification_context(
    "f(-1)",
    "map  f: Nat -> Bool;\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_pos_variable)
{
  test_data_expression_in_specification_context(
    "f(x)",
    "map  f: Nat -> Bool;\n",
    { var("x", pos()) },
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_nat_variable)
{
  test_data_expression_in_specification_context(
    "f(x)",
    "map  f: Nat -> Bool;\n",
    { var("x", nat()) },
    true,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_function_application_int_variable)
{
  test_data_expression_in_specification_context(
    "f(x)",
    "map  f: Nat -> Bool;\n",
    { var("x", data::sort_int::int_()) },
    false
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor)
{
  test_data_expression_in_specification_context(
    "c",
    "sort S = struct c(Nat);\n",
    true,
    "Nat -> S"
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_pos_constant)
{
  test_data_expression_in_specification_context(
    "c(1)",
    "sort S = struct c(Nat);\n",
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_nat_constant)
{
  test_data_expression_in_specification_context(
    "c(0)",
    "sort S = struct c(Nat);\n",
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_int_constant)
{
  test_data_expression_in_specification_context(
    "c(-1)",
    "sort S = struct c(Nat);\n",
    false
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_pos_variable)
{
  test_data_expression_in_specification_context(
    "c(x)",
    "sort S = struct c(Nat);\n",
    { var("x", pos()) },
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_nat_variable)
{
  test_data_expression_in_specification_context(
    "c(x)",
    "sort S = struct c(Nat);\n",
    { var("x", nat()) },
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_struct_constructor_application_int_variable)
{
  test_data_expression_in_specification_context(
    "c(x)",
    "sort S = struct c(Nat);\n",
    { var("x", data::sort_int::int_()) },
    false
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function)
{
  test_data_expression_in_specification_context(
    "f",
    "sort U;\n"
    "     S;\n"
    "     T;\n\n"
    "map  f: Pos;\n"
    "     f: Pos # Nat -> U;\n"
    "     f: Pos # Pos -> S;\n"
    "     f: Nat # Pos -> T;\n",
    true,
    "Pos"
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application1)
{
  test_data_expression_in_specification_context(
    "f(x, x)",
    "sort U;\n"
    "     S;\n"
    "     T;\n\n"
    "map  f: Pos;\n"
    "     f: Pos # Nat -> U;\n"
    "     f: Pos # Pos -> S;\n"
    "     f: Nat # Pos -> T;\n",
    { var("x", pos()), var("y", nat()) },
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application2)
{
  test_data_expression_in_specification_context(
    "f(x, y)",
    "sort U;\n"
    "     S;\n"
    "     T;\n\n"
    "map  f: Pos;\n"
    "     f: Pos # Nat -> U;\n"
    "     f: Pos # Pos -> S;\n"
    "     f: Nat # Pos -> T;\n",
    { var("x", pos()), var("y", nat()) },
    true,
    "U"
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application3)
{
  test_data_expression_in_specification_context(
    "f(y, x)",
    "sort U;\n"
    "     S;\n"
    "     T;\n\n"
    "map  f: Pos;\n"
    "     f: Pos # Nat -> U;\n"
    "     f: Pos # Pos -> S;\n"
    "     f: Nat # Pos -> T;\n",
    { var("x", pos()), var("y", nat()) },
    true,
    "T"
  );
}

//BOOST_AUTO_TEST_CASE(test_ambiguous_function_application4)
//{
//  test_data_expression_in_specification_context(
//    "f(x, x)",
//    "sort S;\n"
//    "     T;\n"
//    "     U;\n\n"
//    "map  f: Pos;\n"
//    "     f: Pos # Nat -> U;\n"
//    "     f: Nat # Nat -> S;\n"
//    "     f: Nat # Pos -> T;\n",
//    { var("x", pos()), var("y", nat()) },
//    true,
//    "S"
//  );
//}
//
//BOOST_AUTO_TEST_CASE(test_ambiguous_function_application4a)
//{
//  test_data_expression_in_specification_context(
//    "f(x, x)",
//    "sort U;\n"
//    "     S;\n"
//    "     T;\n\n"
//    "map  f: Pos;\n"
//    "     f: Pos # Nat -> U;\n"
//    "     f: Nat # Nat -> S;\n"
//    "     f: Nat # Pos -> T;\n",
//    { var("x", pos()), var("y", nat()) },
//    true,
//    "S"
//  );
//}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application5)
{
  test_data_expression_in_specification_context(
    "f(x, x)",
    "sort S;\n"
    "     T;\n"
    "     U;\n\n"
    "map  f: Pos;\n"
    "     f: Nat # Nat -> S;\n"
    "     f: Nat # Pos -> T;\n"
    "     f: Pos # Nat -> U;\n",
    { var("x", pos()), var("y", nat()) },
    true,
    "S"
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application_recursive)
{
  test_data_expression_in_specification_context(
    "g(f(x))",
    "map  g: Int -> Bool;\n"
    "     f: Pos -> Nat;\n"
    "     f: Pos -> Int;\n",
    { var("x", pos()) },
    false
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application_recursive2)
{
  test_data_expression_in_specification_context(
    "g(f(x))",
    "map  g: Int -> Bool;\n"
    "     f: Pos -> Nat;\n"
    "     f: Pos -> Int;\n"
    "     g: Int -> Int;\n",
    { var("x", pos()) },
    false
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application_recursive3)
{
  test_data_expression_in_specification_context(
    "g(f(x))",
    "map  g: Int -> Bool;\n"
    "     f: Pos -> Nat;\n"
    "     f,g: Int -> Int;\n",
    { var("x", pos()) },
    false,
    "Bool"
  );
}

BOOST_AUTO_TEST_CASE(test_ambiguous_function_application_recursive4)
{
  test_data_expression_in_specification_context(
    "g(f(x))",
    "map  g: Int -> Bool;\n"
    "     f: Pos -> Nat;\n"
    "     f: Pos -> Int;\n"
    "     g: Nat -> Int;\n",
    { var("x", pos()) },
    false
  );
}

BOOST_AUTO_TEST_CASE(test_aliases)
{
  test_data_expression_in_specification_context(
    "f == g",
    "sort B;\n"
    "     A = List(List(B));\n"
    "     C = List(B);\n",
    { var("f", data::basic_sort("A")), var("g", list(data::basic_sort("C"))) },
    true
  );
}

BOOST_AUTO_TEST_CASE(test_bag_with_pos_as_argument)
{
  test_data_expression_in_specification_context(
    "{ n: Pos | n + 1 }",
    "sort dummy;\n",
    { },
    true,
    "Bag(Pos)"
  );
}

BOOST_AUTO_TEST_CASE(test_bag_with_nat_as_argument1)
{
  test_data_expression_in_specification_context(
    "{ n: Pos | 0 }",
    "sort dummy;\n",
    { },
    true,
    "Bag(Pos)"
  );
}

BOOST_AUTO_TEST_CASE(test_bag_with_nat_as_argument2)
{
  test_data_expression_in_specification_context(
    "{ n: Nat | n }",
    "sort dummy;\n",
    { },
    true,
    "Bag(Nat)"
  );
}

BOOST_AUTO_TEST_CASE(test_bag_with_real_as_argument)
{
  test_data_expression_in_specification_context(
    "{ n: Pos | 2 / 3 }",
    "sort dummy;\n",
    data::variable_vector(),
    false
  );
}

/* The test below shows an ambiguous projection function that
 * cannot be resolved with the current typechecker. This test should
 * be enabled with a new typechecker. */
BOOST_AUTO_TEST_CASE(test_ambiguous_projection_function)
{
  test_data_expression_in_specification_context(
    "R(pi_1(p)) && IS_T1(p)",
    "sort S;\n"
    "     T = struct T0 | T1(pi_1: T)?IS_T1 | T2(pi_1: S)?IS_T2;\n\n"
    "map  R: T -> Bool;\n",
    { var("p", data::basic_sort("T")) },
    false     // <-------------- Should be set to true with a new typechecker ---------------------------------------
  );
}

BOOST_AUTO_TEST_CASE(test_lambda_term_with_wrong_number_of_arguments)
{
  /* The typechecker couldn't catch the wrongly typed term below in november 2012,
     which led to a core dump */
  test_data_expression("((lambda x: Nat. x)(1, 2) > 0)",false);
}

/* The example below has the nasty feature that the sort of
   # in the expression below can be #:List(Nat)->Nat,
      List(Int)->Nat and List(Real)->Nat. In version 10169 of
      the toolset the type of # became List(PossibleTypes([Nat, Int, Real])
      causing confusion in the other tools */
BOOST_AUTO_TEST_CASE(test_avoidance_of_possible_types)
{
  test_data_expression_in_specification_context(
    "#[0, 1] == -1",
    "sort dummy;\n",
    { },
    true,
    "Bool"
  );
}

/* The next example checks whether Int2Pos is properly typed. */
BOOST_AUTO_TEST_CASE(test_proper_use_of_int2pos)
{
  test_data_expression_in_specification_context(
    "f(Int2Pos(-1))",
    "map  f: Pos -> Bool;\n",
    { },
    true,
    "Bool"
  );
}

/* This example checks whether explicit transformations among
 * numbers are properly typable.*/
BOOST_AUTO_TEST_CASE(test_proper_use_of_int2pos1)
{
  test_data_expression_in_specification_context(
    "fpos(Nat2Pos(0)) && fpos(Int2Pos(-1)) && fpos(Real2Pos(1 / 2)) && "
    "fnat(Int2Nat(-1)) && fnat(Real2Nat(1 / 2)) && "
    "fint(Real2Int(1 / 2))",
    "map  fpos: Pos -> Bool;\n"
    "     fnat: Nat -> Bool;\n"
    "     fint: Int -> Bool;\n",
    { },
    true,
    "Bool"
  );
}

class testable_sort_type_checker: public data::sort_type_checker
{
  public:
    std::pair<bool, bool> check_alias(const data::alias& x)
    {
      // search for the alias x
      // auto x_iter = m_normalized_aliases.end();
      // for (auto i = m_normalized_aliases.begin(); i != m_normalized_aliases.end(); ++i)
      bool found=false;
      for (const data::alias& a: get_sort_specification().user_defined_aliases())
      {
        if (a == x)
        {
          found=true;
          break;
        }
      }
      // if (x_iter == m_normalized_aliases.end())
      if (!found)
      {
        throw mcrl2::runtime_error("could not find alias " + data::pp(x));
      }

      std::map < data::basic_sort, data::sort_expression > alias_map;
      for(const data::alias& a: get_sort_specification().user_defined_aliases())
      {
        alias_map[a.name()]=a.reference();
      }
      std::set<data::basic_sort> sort_already_seen;

      bool first, second;
      try
      {
        first = true;

        // check_alias_recursion(x.name(), x.reference());
        check_for_sort_alias_loop_through_function_sort(x.name(),x.reference(),sort_already_seen, false, alias_map);
        assert(sort_already_seen.size()==0);
      }
      catch (mcrl2::runtime_error& e)
      {
        mCRL2log(log::debug) << e.what() << std::endl;
        first = false;
      }
      try
      {
        second = true;
        // check_alias_circularity(x.name(), x.reference());
        check_alias_circularity(x.name(), x.reference(),sort_already_seen, alias_map);
        assert(sort_already_seen.size()==0);
      }
      catch (mcrl2::runtime_error& e)
      {
        mCRL2log(log::debug) << e.what() << std::endl;
        second = false;
      }
      return std::make_pair(first, second);
    }

    /// \brief constructs a sort expression checker.
    testable_sort_type_checker(const data::sort_specification& sort_spec)
      : data::sort_type_checker(sort_spec, false)
    {}

};

BOOST_AUTO_TEST_CASE(test_sort_aliases)
{
  std::pair<data::basic_sort_vector, data::alias_vector> sortspec = data::parse_sort_specification(
      "sort                           \n"
      "  A;                           \n"
      "  B;                           \n"
      "  C;                           \n"
      "  A1  = A1;                    \n"
      "  A2  = List(A2);              \n"
      "  A3  = Set(A3);               \n"
      "  A4  = Bag(A4);               \n"
      "  A5  = A5 -> B;               \n"
      "  A6  = B -> A6;               \n"
      "  A7  = struct f(A7);          \n"
      "  A8  = struct f(Nat -> A8);   \n"
      "  A9  = struct f(x: List(A9)); \n"
      "  A10 = List(struct f(A10));   \n"
      "  A11 = struct A11 | B;        \n"
      "  A12 = FSet(A12);             \n"
      "  A13 = FBag(A13);             \n"
      "  A14 = struct f(FSet(A14)) | c;\n"
      "  A15 = struct f(FSet(A15)) | g(FBag(A15)) | c;\n"
  );

  std::string expected_results(
      "  A1  true false  \n"
      "  A2  true false  \n"
      "  A3  false false  \n"
      "  A4  false false  \n"
      "  A5  false false  \n"
      "  A6  false false  \n"
      "  A7  true true   \n"
      "  A8  false true  \n"
      "  A9  true true   \n"
      "  A10 true true   \n"
      "  A11 true true   \n"
      "  A12 true false  \n"
      "  A13 true false  \n"
      "  A14 true true  \n"
      "  A15 true true  \n"
  );

  std::map<std::string, std::pair<bool, bool> > expected_result_map;
  for (const std::string& line: utilities::regex_split(expected_results, "\\n"))
  {
    auto words = utilities::regex_split(utilities::trim_copy(line), "\\s+");
    if (words.size() == 3)
    {
      std::string name = words[0];
      bool result1 = words[1] == "true";
      bool result2 = words[2] == "true";
      expected_result_map[name] = std::make_pair(result1, result2);
    }
  }

  data::sort_specification sp(sortspec.first,sortspec.second);
  testable_sort_type_checker checker(sp);
  for (const data::alias& a: sortspec.second)
  {
    std::pair<bool, bool> result = checker.check_alias(a);
    std::string name = core::pp(a.name().name());
    std::pair<bool, bool> expected_result = expected_result_map[name];
    if (result != expected_result)
    {
      std::clog << "ERROR: alias " << a
                << " result = " << std::boolalpha << result.first << " " << std::boolalpha << result.second
                << " expected result = " << std::boolalpha << expected_result.first << " " << std::boolalpha << expected_result.second
                << std::endl;
    }
    BOOST_CHECK(result == expected_result);
  }
}

boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[])
{
  return nullptr;
}