/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2015-2018, Dataspeed Inc.
 *  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 Dataspeed Inc. 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.
 *********************************************************************/

#ifndef GAZEBO_ROS_OUSTER_LASER_H_
#define GAZEBO_ROS_OUSTER_LASER_H_

// Use the same source code for CPU and GPU plugins
#ifndef GAZEBO_GPU_RAY
#define GAZEBO_GPU_RAY 0
#endif

// Custom Callback Queue
#include <ros/advertise_options.h>
#include <ros/callback_queue.h>
#include <ros/ros.h>

#include <gazebo/common/Plugin.hh>
#include <gazebo/common/Time.hh>
#include <gazebo/msgs/MessageTypes.hh>
#include <gazebo/physics/physics.hh>
#include <gazebo/sensors/SensorTypes.hh>
#include <gazebo/transport/TransportTypes.hh>
#include <sdf/Param.hh>
#if GAZEBO_GPU_RAY
#include <gazebo/plugins/GpuRayPlugin.hh>
#else
#include <gazebo/plugins/RayPlugin.hh>
#endif

#include <boost/algorithm/string/trim.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>

#if GAZEBO_GPU_RAY
#define GazeboRosOusterLaser GazeboRosOusterGpuLaser
#define RayPlugin GpuRayPlugin
#define RaySensorPtr GpuRaySensorPtr
#endif

namespace gazebo {

class GazeboRosOusterLaser : public RayPlugin {
  /// \brief Constructor
  /// \param parent The parent entity, must be a Model or a Sensor
 public:
  GazeboRosOusterLaser();

  /// \brief Destructor
 public:
  ~GazeboRosOusterLaser();

  /// \brief Load the plugin
  /// \param take in SDF root element
 public:
  void Load(sensors::SensorPtr _parent, sdf::ElementPtr _sdf);

  /// \brief Subscribe on-demand
 private:
  void ConnectCb();

  /// \brief The parent ray sensor
 private:
  sensors::RaySensorPtr parent_ray_sensor_;

  /// \brief Pointer to ROS node
 private:
  ros::NodeHandle* nh_;

  /// \brief ROS publisher
 private:
  ros::Publisher pub_;

  /// \brief topic name
 private:
  std::string topic_name_;

  /// \brief frame transform name, should match link name
 private:
  std::string frame_name_;

  /// \brief Minimum range to publish
 private:
  double min_range_;

  /// \brief Maximum range to publish
 private:
  double max_range_;

  /// \brief Gaussian noise
 private:
  double gaussian_noise_;

  /// \brief Gaussian noise generator
 private:
  static double gaussianKernel(double mu, double sigma) {
    // using Box-Muller transform to generate two independent standard normally
    // distributed normal variables see wikipedia
    double U = (double)rand() /
               (double)RAND_MAX;  // normalized uniform random variable
    double V = (double)rand() /
               (double)RAND_MAX;  // normalized uniform random variable
    return sigma * (sqrt(-2.0 * ::log(U)) * cos(2.0 * M_PI * V)) + mu;
  }

  /// \brief A mutex to lock access
 private:
  boost::mutex lock_;

  /// \brief For setting ROS name space
 private:
  std::string robot_namespace_;

  // Custom Callback Queue
 private:
  ros::CallbackQueue laser_queue_;

 private:
  void laserQueueThread();

 private:
  boost::thread callback_laser_queue_thread_;

  // Subscribe to gazebo laserscan
 private:
  gazebo::transport::NodePtr gazebo_node_;

 private:
  gazebo::transport::SubscriberPtr sub_;

 private:
  void OnScan(const ConstLaserScanStampedPtr& _msg);
};

}  // namespace gazebo

#endif /* GAZEBO_ROS_OUSTER_LASER_H_ */
