#ifndef BIORBD_CONFIG_H
#define BIORBD_CONFIG_H

//Some day, if Eigen can be compile in DLL
//#ifdef _WIN32
//#  ifdef BIORBD_API_EXPORTS
//#    define BIORBD_API __declspec(dllexport)
//#  else
//#    define BIORBD_API __declspec(dllimport)
//#  endif
//#else
//#  define BIORBD_API
//#endif
#define BIORBD_API
#ifdef _WIN32
#define _ENABLE_EXTENDED_ALIGNED_STORAGE
#endif

// Current version of biorbd
#define BIORBD_VERSION "1.4.4"

// Choice of linear algebra backend
/* #undef BIORBD_USE_EIGEN3_MATH */
#define BIORBD_USE_CASADI_MATH

namespace biorbd {

enum LINEAR_ALGEBRA_BACKEND{
    EIGEN3,
    CASADI
};

inline LINEAR_ALGEBRA_BACKEND currentLinearAlgebraBackend(){
#ifdef BIORBD_USE_CASADI_MATH
    return LINEAR_ALGEBRA_BACKEND::CASADI;
#else
    return LINEAR_ALGEBRA_BACKEND::EIGEN3;
#endif
}

}

// Make sure rbdl was compile according to the requested linear algebra library
#include "rbdl_config.h"
#ifdef BIORBD_USE_CASADI_MATH
#ifndef RBDL_USE_CASADI_MATH

#endif
#endif

// Choice of modules
/* #undef MODULE_KALMAN */
#define MODULE_ACTUATORS
#define MODULE_MUSCLES
/* #undef MODULE_STATIC_OPTIM */
#define MODULE_VTP_FILES_READER

// Define some skip if ones doesn't want to compile them
/* #undef SKIP_ASSERT */
/* #undef SKIP_LONG_TESTS */

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#ifndef _WIN32
#ifndef NAN
    //static const unsigned long __nan[2] = {0xffffffff, 0x7fffffff};
    #define NAN (*(const float *) __nan)
#endif // NAN
#endif // _WIN32

// Interface for using biorbd independently that EIGEN or CasADi is used
#ifndef SWIG

#ifdef BIORBD_USE_CASADI_MATH
#define DECLARE_GENERALIZED_COORDINATES(varname, model) \
    casadi::DM varname(model.nbQ(), 1);\
    biorbd::rigidbody::GeneralizedCoordinates varname##_sym(casadi::MX::sym(#varname, model.nbQ(), 1));
#define DECLARE_GENERALIZED_VELOCITY(varname, model) \
    casadi::DM varname(model.nbQdot(), 1);\
    biorbd::rigidbody::GeneralizedVelocity varname##_sym(casadi::MX::sym(#varname, model.nbQdot(), 1));
#define DECLARE_GENERALIZED_ACCELERATION(varname, model) \
    casadi::DM varname(model.nbQddot(), 1);\
    biorbd::rigidbody::GeneralizedAcceleration varname##_sym(casadi::MX::sym(#varname, model.nbQddot(), 1));
#define DECLARE_GENERALIZED_TORQUE(varname, model) \
    casadi::DM varname(model.nbGeneralizedTorque(), 1);\
    biorbd::rigidbody::GeneralizedTorque varname##_sym(casadi::MX::sym(#varname, model.nbGeneralizedTorque(), 1));
#define DECLARE_VECTOR(varname, nbElements) \
    casadi::DM varname(nbElements, 1);\
    biorbd::utils::Vector varname##_sym(casadi::MX::sym(#varname, nbElements, 1));

#define CALL_BIORBD_FUNCTION_1ARG(varname, model, funcname, arg1) \
    casadi::Function func_##funcname(#funcname, {arg1##_sym}, {model.funcname(arg1##_sym)}, {#arg1}, {#funcname});\
    auto varname = func_##funcname(casadi::DMDict{ {#arg1, arg1} }).at(#funcname);
#define CALL_BIORBD_FUNCTION_2ARGS(varname, model, funcname, arg1, arg2) \
    casadi::Function func_##funcname(#funcname, {arg1##_sym, arg2##_sym}, {model.funcname(arg1##_sym, arg2##_sym)}, {#arg1, #arg2}, {#funcname});\
    auto varname = func_##funcname(casadi::DMDict{ {#arg1, arg1}, {#arg2, arg2} }).at(#funcname);
#define CALL_BIORBD_FUNCTION_3ARGS(varname, model, funcname, arg1, arg2, arg3) \
    casadi::Function func_##funcname(#funcname, {arg1##_sym, arg2##_sym, arg3##_sym}, {model.funcname(arg1##_sym, arg2##_sym, arg3##_sym)}, {#arg1, #arg2, #arg3}, {#funcname});\
    auto varname = func_##funcname(casadi::DMDict{ {#arg1, arg1}, {#arg2, arg2}, {#arg3, arg3} }).at(#funcname);

#define CALL_BIORBD_FUNCTION_3ARGS1PARAM(varname, model, funcname, arg1, arg2, arg3, param1) \
    casadi::Function func_##funcname(#funcname, {arg1##_sym, arg2##_sym, arg3##_sym}, {model.funcname(arg1##_sym, arg2##_sym, arg3##_sym, param1)}, {#arg1, #arg2, #arg3}, {#funcname});\
    auto varname = func_##funcname(casadi::DMDict{ {#arg1, arg1}, {#arg2, arg2}, {#arg3, arg3} }).at(#funcname);

#define SCALAR_TO_DOUBLE(varname, scalar) \
    double varname; \
    { \
    casadi::Function func_hopefully_not_defined_else_where("scalar", {}, {scalar}, {}, {"ouputName"});\
    double varname_weirdly_named(func_hopefully_not_defined_else_where(casadi::DMDict{ }).at("ouputName")(0, 0)); \
    varname = varname_weirdly_named;\
    }

#else
#define DECLARE_GENERALIZED_COORDINATES(varname, model) \
    biorbd::rigidbody::GeneralizedCoordinates varname(model);
#define DECLARE_GENERALIZED_VELOCITY(varname, model) \
    biorbd::rigidbody::GeneralizedVelocity varname(model);
#define DECLARE_GENERALIZED_ACCELERATION(varname, model) \
    biorbd::rigidbody::GeneralizedAcceleration varname(model);
#define DECLARE_GENERALIZED_TORQUE(varname, model) \
    biorbd::rigidbody::GeneralizedTorque varname(model);
#define DECLARE_VECTOR(varname, nbElements) \
    biorbd::utils::Vector varname(nbElements);

#define CALL_BIORBD_FUNCTION_1ARG(varname, model, funcname, arg1) \
    auto varname = model.funcname(arg1);
#define CALL_BIORBD_FUNCTION_2ARGS(varname, model, funcname, arg1, arg2) \
    auto varname = model.funcname(arg1, arg2);
#define CALL_BIORBD_FUNCTION_3ARGS(varname, model, funcname, arg1, arg2, arg3) \
    auto varname = model.funcname(arg1, arg2, arg3);

#define CALL_BIORBD_FUNCTION_3ARGS1PARAM(varname, model, funcname, arg1, arg2, arg3, param1) \
    auto varname = model.funcname(arg1, arg2, arg3, param1);

#define SCALAR_TO_DOUBLE(varname, scalar) \
    double varname(scalar);

#endif

#define FILL_VECTOR(varname, filler) \
    for (unsigned int i=0; i<varname.rows(); ++i){\
        varname(i, 0) = filler[i];\
    }
#define FILL_MATRIX(varname, filler) \
    { \
        int cmpWithAWeirdName(0); \
        for (unsigned int i=0; i<varname.rows(); ++i){\
            for (unsigned int j=0; j<varname.cols(); ++j){\
                varname(i, j) = filler[cmpWithAWeirdName++];\
            }\
        } \
    }

#endif

#endif // BIORBD_CONFIG_H
