/*
 * Copyright (C) 2012-2016 Open Source Robotics Foundation
 *
 * 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.
 *
 */
/* Desc: Laser Interface for Player
 * Author: Nate Koenig
 * Date: 2 March 2006
 */

#include <boost/algorithm/string.hpp>
#include <math.h>
#include <iostream>

#include "gazebo/math/gzmath.hh"
#include "gazebo/transport/transport.hh"
#include "GazeboDriver.hh"
#include "LaserInterface.hh"


/////////////////////////////////////////////////
LaserInterface::LaserInterface(player_devaddr_t _addr,
    GazeboDriver *_driver, ConfigFile *_cf, int _section)
: GazeboInterface(_addr, _driver, _cf, _section)
{
  this->datatime = -1;

  this->node = gazebo::transport::NodePtr(new gazebo::transport::Node());
  this->node->Init(this->worldName);
  this->laserName = _cf->ReadString(_section, "laser_name", "default");

  this->scanId = 0;
  memset(&this->data, 0, sizeof(this->data));
}

/////////////////////////////////////////////////
LaserInterface::~LaserInterface()
{
  player_laser_data_scanpose_t_cleanup(&this->data);
}

/////////////////////////////////////////////////
int LaserInterface::ProcessMessage(QueuePointer &_respQueue,
    player_msghdr_t *_hdr, void * /*_data*/)
{
  int result = -1;

  // Is it a request to set the laser's config?
  if (Message::MatchMessage(_hdr, PLAYER_MSGTYPE_REQ,
                            PLAYER_LASER_REQ_SET_CONFIG,
                            this->device_addr))
  {
    // player_laser_config_t* plc = (player_laser_config_t*)_data;

    if (_hdr->size == sizeof(player_laser_config_t))
    {
      // TODO: Complete this
      this->driver->Publish(this->device_addr, _respQueue,
                            PLAYER_MSGTYPE_RESP_ACK,
                            PLAYER_LASER_REQ_SET_CONFIG);
      result = 0;
    }
    else
    {
      printf("config request len is invalid (%d != %d)",
          static_cast<int>(_hdr->size),
          static_cast<int>(sizeof(player_laser_config_t)));
      result = -1;
    }
  }

  // Is it a request to get the laser's config?
  else if (Message::MatchMessage(_hdr, PLAYER_MSGTYPE_REQ,
                                 PLAYER_LASER_REQ_GET_CONFIG,
                                 this->device_addr))
  {
    if (_hdr->size == 0)
    {
      /* TODO:
      int intensity = 1;

      player_laser_config_t plc;
      memset(&plc, 0, sizeof(plc));

      plc.min_angle = this->iface->data->min_angle;
      plc.max_angle = this->iface->data->max_angle;
      plc.max_range = this->iface->data->max_range;
      plc.resolution = this->iface->data->res_angle;
      plc.range_res = this->iface->data->res_range;
      plc.intensity = intensity;

      this->driver->Publish(this->device_addr, _respQueue,
          PLAYER_MSGTYPE_RESP_ACK,
          PLAYER_LASER_REQ_GET_CONFIG,
          (void*)&plc, sizeof(plc), NULL);
          */
      result = 0;
    }
    else
    {
      printf("config request len is invalid (%d != %d)",
          static_cast<int>(_hdr->size), 0);
      result = -1;
    }
  }
  else if (Message::MatchMessage(_hdr, PLAYER_MSGTYPE_REQ,
           PLAYER_LASER_REQ_GET_GEOM, this->device_addr))
  {
    player_laser_geom_t rep;

    // TODO: get geometry from somewhere
    /*memset(&rep.pose, 0, sizeof(rep.pose));
    memset(&rep.size, 0, sizeof(rep.size));

    rep.pose.px = this->iface->data->pose.pos.x;
    rep.pose.py = this->iface->data->pose.pos.y;
    rep.pose.pyaw = this->iface->data->pose.yaw;

    rep.size.sw = this->iface->data->size.x;
    rep.size.sl = this->iface->data->size.y;
        */

    this->driver->Publish(this->device_addr, _respQueue,
        PLAYER_MSGTYPE_RESP_ACK,
        PLAYER_LASER_REQ_GET_GEOM, &rep, sizeof(rep), NULL);
    result = 0;
  }

  return result;
}

/////////////////////////////////////////////////
void LaserInterface::Update()
{
}

/////////////////////////////////////////////////
void LaserInterface::Subscribe()
{
  std::string topic = "~/";
  topic += this->laserName + "/scan";
  boost::replace_all(topic, "::", "/");

  this->laserScanSub = this->node->Subscribe(topic,
      &LaserInterface::OnScan, this);
}

/////////////////////////////////////////////////
void LaserInterface::Unsubscribe()
{
  this->laserScanSub->Unsubscribe();
  this->laserScanSub.reset();
}

/////////////////////////////////////////////////
void LaserInterface::OnScan(ConstLaserScanStampedPtr &_msg)
{
  int i;

  this->datatime = gazebo::msgs::Convert(_msg->time()).Double();

  double oldCount = this->data.scan.ranges_count;

  this->data.scan.min_angle = _msg->scan().angle_min();
  this->data.scan.max_angle = _msg->scan().angle_max();
  this->data.scan.resolution = _msg->scan().angle_step();
  this->data.scan.max_range = _msg->scan().range_max();
  this->data.scan.ranges_count =
    this->data.scan.intensity_count = _msg->scan().ranges_size();
  this->data.scan.id = this->scanId++;

  if (!gazebo::math::equal(oldCount,
        static_cast<double>(this->data.scan.ranges_count)))
  {
    delete [] this->data.scan.ranges;
    delete [] this->data.scan.intensity;

    this->data.scan.ranges = new float[this->data.scan.ranges_count];
    this->data.scan.intensity = new uint8_t[this->data.scan.intensity_count];
  }

  for (i = 0; i < _msg->scan().ranges_size(); ++i)
    this->data.scan.ranges[i] = _msg->scan().ranges(i);

  for (i = 0; i < _msg->scan().intensities_size(); ++i)
    this->data.scan.intensity[i] = (uint8_t)_msg->scan().intensities(i);

  this->data.pose.px = _msg->scan().world_pose().position().x();
  this->data.pose.py = _msg->scan().world_pose().position().y();
  this->data.pose.pa = gazebo::msgs::ConvertIgn(
      _msg->scan().world_pose().orientation()).Euler().Z();

  if (this->data.scan.ranges_count > 0)
  {
    this->driver->Publish(this->device_addr,
        PLAYER_MSGTYPE_DATA,
        PLAYER_LASER_DATA_SCANPOSE,
        static_cast<void*>(&this->data), sizeof(this->data), &this->datatime);
  }
}
