/*
 * Copyright (c) 2011, Ivan Dryanovski, William Morris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the CCNY Robotics Lab nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*  This package uses Canonical Scan Matcher [1], written by
 *  Andrea Censi
 *
 *  [1] A. Censi, "An ICP variant using a point-to-line metric"
 *  Proceedings of the IEEE International Conference
 *  on Robotics and Automation (ICRA), 2008
 */

#ifndef LASER_SCAN_MATCHER_LASER_SCAN_MATCHER_H
#define LASER_SCAN_MATCHER_LASER_SCAN_MATCHER_H

#include <ros/ros.h>
#include <sensor_msgs/Imu.h>
#include <sensor_msgs/LaserScan.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/Pose2D.h>
#include <geometry_msgs/PoseWithCovarianceStamped.h>
#include <geometry_msgs/PoseWithCovariance.h>
#include <geometry_msgs/TwistStamped.h>
#include <nav_msgs/Odometry.h>
#include <tf/transform_datatypes.h>
#include <tf/transform_listener.h>
#include <tf/transform_broadcaster.h>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl_ros/point_cloud.h>

#include <csm/csm_all.h>  // csm defines min and max, but Eigen complains
#undef min
#undef max

namespace scan_tools
{

class LaserScanMatcher
{
  public:

    LaserScanMatcher(ros::NodeHandle nh, ros::NodeHandle nh_private);
    ~LaserScanMatcher();

  private:

    // **** ros

    ros::NodeHandle nh_;
    ros::NodeHandle nh_private_;

    ros::Subscriber scan_subscriber_;

    tf::TransformListener    tf_listener_;
    tf::TransformBroadcaster tf_broadcaster_;

    tf::Transform base_to_laser_; // static, cached
    tf::Transform laser_to_base_; // static, cached, calculated from base_to_laser_

    // **** parameters

    std::string base_frame_;
    std::string fixed_frame_;
    bool publish_tf_;
    std::vector<double> position_covariance_;
    std::vector<double> orientation_covariance_;

    double kf_dist_linear_;
    double kf_dist_linear_sq_;
    double kf_dist_angular_;

    // **** state variables

    boost::mutex mutex_;

    bool initialized_;

    tf::Transform f2b_;    // fixed-to-base tf (pose of base frame in fixed frame)
    tf::Transform f2b_kf_; // pose of the last keyframe scan in fixed frame

    ros::Time last_icp_time_;

    sensor_msgs::Imu latest_imu_msg_;
    sensor_msgs::Imu last_used_imu_msg_;
    nav_msgs::Odometry latest_odom_msg_;
    nav_msgs::Odometry last_used_odom_msg_;

    geometry_msgs::Twist latest_vel_msg_;

    std::vector<double> a_cos_;
    std::vector<double> a_sin_;

    sm_params input_;
    sm_result output_;
    LDP prev_ldp_scan_;
    LDP curr_ldp_scan_;

    ros::Publisher  origin_cloud_publisher_;
    ros::Publisher  current_cloud_publisher_;
    ros::Publisher  current_pcl_cloud_publisher_;
    pcl::PointCloud<pcl::PointXYZ> pc_origin_;
    pcl::PointCloud<pcl::PointXYZ> pc_curr_;

    double trans[3]={0};

    // **** methods

    void initParams();

    void processScan(LDP& curr_ldp_scan, const ros::Time& time);

    void laserScanToLDP(const sensor_msgs::LaserScan::ConstPtr& scan_msg,
                              LDP& ldp, double angle_min, double angle_max);

    void pointCloudToLDP(const pcl::PointCloud<pcl::PointXYZ>::Ptr &point_cloud_msg,
                        LDP &ldp, double angle_min, double angle_max);

    void scanCallback (const sensor_msgs::LaserScan::ConstPtr& scan_msg);

    void createCache (const sensor_msgs::LaserScan::ConstPtr& scan_msg);

    bool getBaseToLaserTf (const std::string& frame_id);

    bool newKeyframeNeeded(const tf::Transform& d);

    void createTfFromXYTheta(double x, double y, double theta, tf::Transform& t);

    void icp_process(pcl::PointCloud<pcl::PointXYZ> frame_left, pcl::PointCloud<pcl::PointXYZ> frame_right, tf::Transform transform);

    void ndt_process(pcl::PointCloud<pcl::PointXYZ> frame_left, pcl::PointCloud<pcl::PointXYZ> frame_right, tf::Transform transform);
};

} // namespace scan_tools

#endif // LASER_SCAN_MATCHER_LASER_SCAN_MATCHER_H
