MlNdt::MlNdt(ros::NodeHandle & n,ros::NodeHandle & n_private):nh_(n),nh_private_(n_private){
  
  // find tf_prefix if exists add it to tf_prefix_ variable
  tf_prefix_="";
  std::string tf_prefix_path;
  if (nh_.searchParam("tf_prefix", tf_prefix_path))
  {
    nh_.getParam(tf_prefix_path, tf_prefix_);
  }

   robot_base_frame_ = nh_private_.param<std::string>("robot_base_frame","base_link");
   world_farme_ = nh_private_.param<std::string>("world_farme","odom");
   odom_topic_ = nh_private_.param<std::string>("odom_topic","/odom");
   laser_topic_ = nh_private_.param<std::string>("laser_topic","/laser");

   if(tf_prefix_ != ""){
    robot_base_frame_=tf_prefix_+"/"+robot_base_frame_;
   }

    // subscribers to acceleromer and gyroscope
  message_filters::Subscriber<nav_msgs::Odometry> odom_sub (nh_,odom_topic_, 1000);
  message_filters::Subscriber<sensor_msgs::LaserScan> laser_sub (nh_,laser_topic_, 1000);
  
  // sync messages using approximate alghorithm
  constexpr int sync_delay = 10;
  typedef message_filters::sync_policies::ApproximateTime<nav_msgs::Odometry, sensor_msgs::LaserScan> ImuSyncPolicy;
  message_filters::Synchronizer<ImuSyncPolicy> msg_sync (ImuSyncPolicy(sync_delay), odom_sub,laser_sub);
  msg_sync.registerCallback(boost::bind(&MlNdt::data_cb,this,_1,_2));


}
int main(int argc, char **argv)
{
    ros::init(argc, argv, "mcl_node");
    ros::NodeHandle n("/mcl_node");;

    n.param<std::string>("map_frame", mapFrame, "map");
    n.param<std::string>("robot_base_link", baseFrame, "base_link");
    n.param<double>("map_offset_x", coords.x0, 0.0);
    n.param<double>("map_offset_y", coords.y0, 0.0);
    n.param<int>("map_occupied_min_threshold", minOccupied, 10);

    double odom_a1, odom_a2, odom_a3, odom_a4;
    n.param<std::string>("odometry_frame", odomFrame, "odom");
    n.param<double>("odometry_model_a1", odom_a1, 0.05);
    n.param<double>("odometry_model_a2", odom_a2, 0.01);
    n.param<double>("odometry_model_a3", odom_a3, 0.02);
    n.param<double>("odometry_model_a4", odom_a4, 0.01);

    //TODO: setup these IR constants more properly.
    double irZhit, irZrm;
    n.param<double>("ir_model_sigma_hit", irSigma, 0.1);
    n.param<double>("ir_model_z_hit", irZhit, 0.95);
    n.param<double>("ir_model_z_random_over_z_max", irZrm, 0.05);

    int nParticles, mcl_rate;
    double minDelta, aslow, afast;
    double crashRadius, crashYaw, stdXY, stdYaw, locStdXY, locStdYaw;
    n.param<int>("mcl_particles", nParticles, 200);
    n.param<int>("mcl_rate", mcl_rate, 5);
    n.param<double>("mcl_init_cone_radius", initConeRadius, 0.2);
    n.param<double>("mcl_init_yaw_variance", initYawVar, 0.3);
    n.param<double>("mcl_min_position_delta", minDelta, 0.001);
    n.param<double>("mcl_aslow", aslow, 0.01);
    n.param<double>("mcl_afast", afast, 0.2);
    n.param<double>("mcl_crash_radius", crashRadius, 0.1);
    n.param<double>("mcl_crash_yaw", crashYaw, 0.2);
    n.param<double>("mcl_good_std_xy", stdXY, 0.05);
    n.param<double>("mcl_good_std_yaw", stdYaw, 0.6);
    n.param<double>("mcl_localized_std_xy", locStdXY, 0.05);
    n.param<double>("mcl_localized_std_yaw", locStdYaw, 0.6);

    tf::TransformBroadcaster broadcaster_obj;
    tf_broadcaster = &broadcaster_obj;
    tf::TransformListener listener_obj;
    tf_listener = &listener_obj;

    odom2map = new tf::Stamped<tf::Pose>;

    message_filters::Subscriber<nav_msgs::Odometry> odom_sub(n, "/odom", 1);
    message_filters::Subscriber<ir_sensors::RangeArray> range_sub(n,"/ir_publish/sensors", 1);
    message_filters::Synchronizer<SyncPolicy> sync(SyncPolicy(10), odom_sub, range_sub);
    sync.registerCallback(boost::bind(&odomRangeUpdate, _1, _2));

    ros::Subscriber map_version_sub = n.subscribe<std_msgs::Int32>("/map_node/version", 2, mapVersionCB);

    ros::Publisher particle_pub_obj = n.advertise<geometry_msgs::PoseArray>("/mcl/particles", 5);
    ros::Publisher localized_pub = n.advertise<std_msgs::Bool>("/mcl/is_localized", 5);
    geometry_msgs::PoseArray poseArray;
    poseArray.header.frame_id = mapFrame;

    //Wait 8 s for the map service.
    if(!ros::service::waitForService("/map_node/get_map", 8000)) {
        ROS_ERROR("Map service unreachable.");
        return -1;
    }
    mapInflated = new nav_msgs::OccupancyGrid();
    mapDistance = new nav_msgs::OccupancyGrid();
    ros::ServiceClient map_client_obj = n.serviceClient<map_tools::GetMap>("/map_node/get_map");
    map_client  = &map_client_obj;

    if(!updateMap()) {
        return -1;
    }

    struct PoseState pose, goodStd, locStd;
    goodStd.set(stdXY);
    goodStd.yaw = stdYaw;
    locStd.set(locStdXY);
    locStd.yaw = locStdYaw;
    pose.set(0.0);
    pose.x += coords.x0;
    pose.y += coords.y0;
    om = new OdometryModel(odom_a1, odom_a2, odom_a3, odom_a4);

    mclEnabled = false;
    coords.x = coords.x0;
    coords.y = coords.y0;
    coords.th = 0.0;

    mc = new MonteCarlo(om, &isPointFree, nParticles, minDelta,
                        aslow, afast, crashRadius, crashYaw, goodStd);
    irm = new RangeModel(&getDist, irZhit, irZrm);
    mc->addSensor(irm);


    double csz = mapInflated->info.resolution;
    double wc = ((double)mapInflated->info.width);
    double hc = ((double)mapInflated->info.height);
    assert(csz > 0.00001);

    mc->init(pose, initConeRadius, initYawVar, wc*csz, hc*csz);
    //mc->init(wc*csz, hc*csz);

    mclEnabled = true;

    current_time = ros::Time::now();
    int rate = 40, counter = 0;
    ros::Rate loop_rate(rate);

    if(!updateMap())
        return -1;

    *odom2map = mclTransform();
    struct PoseState odom0;
    bool firstMcl = true;
    double dx = 0, dy = 0, dyaw = 0, dx1 = 0, dy1 = 0, dyaw1 = 0;

    std_msgs::Bool isLocalizedMsg;
    isLocalizedMsg.data = isLocalized;

    ros::Subscriber init_mcl_sub = n.subscribe<geometry_msgs::Pose>("/mcl/initial_pose", 2, initMcl);

    while (ros::ok())
    {
        if(!firstMcl) {
            dx = odomState.x - odom0.x;
            dy = odomState.y - odom0.y;
            dyaw = odomState.yaw - odom0.yaw;

            dx1 = dx;
            dy1 = dy;
            dyaw1 = dyaw;
        }

        if(counter % (rate/mcl_rate) == 0) {
            if(mclEnabled) {
                if(firstMcl) {
                    odom0 = odomState;
                    firstMcl = false;
                }

                odom0 = odomState;
                if(runMonteCarlo()) {
                    dx = 0;
                    dy = 0;
                    dyaw = 0;
                }
                particles2PoseArray(mc->getParticles(), poseArray);
                particle_pub_obj.publish(poseArray);

                struct PoseState std = mc->getStd();
                if(std.x > locStd.x || std.y > locStd.y || std.yaw > locStd.yaw) {
                    isLocalized = false;
                } else {
                    isLocalized = true;
                }
            }
            isLocalizedMsg.data = isLocalized;
            localized_pub.publish(isLocalizedMsg);
            counter = 0;
        }


        counter++;

        if(!firstMcl) {
            struct PoseState avg = mc->getState();
            coords.x = avg.x + dx;
            coords.y = avg.y + dy;
            coords.th = avg.yaw + dyaw;
            //Do not update transform when rotating quickly on spot.
            if((std::sqrt(dx1*dx1 + dy1*dy1) > 0.03/(double)mcl_rate)
                    && isLocalized)
                *odom2map = mclTransform();
        }

        publishTransform(*odom2map);
        ros::spinOnce();
        loop_rate.sleep();

        ros::Duration last_update = ros::Time::now() - current_time;
        if(last_update > ros::Duration(1.2/(double)rate))
            current_time = ros::Time::now();



    }


    return 0;
}