/*****************************************************************
 *
 * This file is part of the GMAPPING project
 *
 * GMAPPING Copyright (c) 2004 Giorgio Grisetti, 
 * Cyrill Stachniss, and Wolfram Burgard
 *
 * This software is licensed under the "Creative Commons 
 * License (Attribution-NonCommercial-ShareAlike 2.0)" 
 * and is copyrighted by Giorgio Grisetti, Cyrill Stachniss, 
 * and Wolfram Burgard.
 * 
 * Further information on this license can be found at:
 * http://creativecommons.org/licenses/by-nc-sa/2.0/
 * 
 * GMAPPING is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied 
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  
 *
 *****************************************************************/


#include "carmenwrapper.h"

using namespace GMapping;
using namespace std;

//static vars for the carmenwrapper
SensorMap CarmenWrapper::m_sensorMap;
deque<RangeReading> CarmenWrapper::m_rangeDeque;
pthread_mutex_t CarmenWrapper::m_mutex;  
sem_t CarmenWrapper::m_dequeSem;  
pthread_mutex_t CarmenWrapper::m_lock;  
pthread_t CarmenWrapper::m_readingThread;
RangeSensor* CarmenWrapper::m_frontLaser=0;
RangeSensor* CarmenWrapper::m_rearLaser=0;
bool CarmenWrapper::m_threadRunning=false;
OrientedPoint CarmenWrapper::m_truepos;
bool CarmenWrapper::stopped=true;


void CarmenWrapper::initializeIPC(const char* name) {
  carmen_ipc_initialize(1,(char **)&name);
}




int CarmenWrapper::registerLocalizationMessages(){
  lock();
  IPC_RETURN_TYPE err;

  /* register globalpos message */
  err = IPC_defineMsg(CARMEN_LOCALIZE_GLOBALPOS_NAME, IPC_VARIABLE_LENGTH, 
		      CARMEN_LOCALIZE_GLOBALPOS_FMT);
  carmen_test_ipc_exit(err, "Could not define", CARMEN_LOCALIZE_GLOBALPOS_NAME);

  /* register robot particle message */
  err = IPC_defineMsg(CARMEN_LOCALIZE_PARTICLE_NAME, IPC_VARIABLE_LENGTH, 
		      CARMEN_LOCALIZE_PARTICLE_FMT);
  carmen_test_ipc_exit(err, "Could not define", CARMEN_LOCALIZE_PARTICLE_NAME);

/*
  carmen_localize_subscribe_initialize_placename_message(NULL, 
							 (carmen_handler_t)
							 carmen_localize_initialize_placename_handler,
							 CARMEN_SUBSCRIBE_LATEST);

  // register map request message 
  err = IPC_defineMsg(CARMEN_LOCALIZE_MAP_QUERY_NAME, IPC_VARIABLE_LENGTH,
		      CARMEN_LOCALIZE_MAP_QUERY_FMT);
  carmen_test_ipc_exit(err, "Could not define", 
		       CARMEN_LOCALIZE_MAP_QUERY_NAME);

  err = IPC_defineMsg(CARMEN_LOCALIZE_MAP_NAME, IPC_VARIABLE_LENGTH,
		      CARMEN_LOCALIZE_MAP_FMT);
  carmen_test_ipc_exit(err, "Could not define", CARMEN_LOCALIZE_MAP_NAME);

  // subscribe to map request message 
  err = IPC_subscribe(CARMEN_LOCALIZE_MAP_QUERY_NAME, map_query_handler, NULL);
  carmen_test_ipc(err, "Could not subscribe", CARMEN_LOCALIZE_MAP_QUERY_NAME);
  IPC_setMsgQueueLength(CARMEN_LOCALIZE_MAP_QUERY_NAME, 1);


  // register globalpos request message 
  err = IPC_defineMsg(CARMEN_LOCALIZE_GLOBALPOS_QUERY_NAME, 
		      IPC_VARIABLE_LENGTH,
		      CARMEN_DEFAULT_MESSAGE_FMT);
  carmen_test_ipc_exit(err, "Could not define", 
		       CARMEN_LOCALIZE_MAP_QUERY_NAME);

  // subscribe to globalpos request message 
  err = IPC_subscribe(CARMEN_LOCALIZE_GLOBALPOS_QUERY_NAME, 
		      globalpos_query_handler, NULL);
  carmen_test_ipc(err, "Could not subscribe", 
		  CARMEN_LOCALIZE_GLOBALPOS_QUERY_NAME);
  IPC_setMsgQueueLength(CARMEN_LOCALIZE_GLOBALPOS_QUERY_NAME, 1);
*/
  unlock();
  return 0;
}

bool CarmenWrapper::start(const char* name){
	if (m_threadRunning)
		return false;
	carmen_robot_subscribe_frontlaser_message(NULL, (carmen_handler_t)robot_frontlaser_handler, CARMEN_SUBSCRIBE_LATEST);
	carmen_robot_subscribe_rearlaser_message(NULL, (carmen_handler_t)robot_rearlaser_handler, CARMEN_SUBSCRIBE_LATEST);
	carmen_simulator_subscribe_truepos_message(NULL,(carmen_handler_t) simulator_truepos_handler, CARMEN_SUBSCRIBE_LATEST);

	IPC_RETURN_TYPE err;

	err = IPC_subscribe(CARMEN_NAVIGATOR_GO_NAME, navigator_go_handler,  NULL);
	carmen_test_ipc_exit(err, "Could not subscribe", 
			     CARMEN_NAVIGATOR_GO_NAME);
	IPC_setMsgQueueLength(CARMEN_NAVIGATOR_GO_NAME, 1);

	err = IPC_subscribe(CARMEN_NAVIGATOR_STOP_NAME, navigator_stop_handler,  NULL);
	carmen_test_ipc_exit(err, "Could not subscribe", 
			     CARMEN_NAVIGATOR_STOP_NAME);
	IPC_setMsgQueueLength(CARMEN_NAVIGATOR_STOP_NAME, 1);



	signal(SIGINT, shutdown_module);
	pthread_mutex_init(&m_mutex, 0);
	pthread_mutex_init(&m_lock, 0);
	sem_init(&m_dequeSem, 0, 0);
	m_threadRunning=true;
	pthread_create (&m_readingThread,0,m_reading_function,0);
	return true; 
}

void CarmenWrapper::lock(){
	//cerr <<"LOCK" << endl;
	pthread_mutex_lock(&m_lock);
}

void CarmenWrapper::unlock(){
	//cerr <<"UNLOCK" << endl;
	pthread_mutex_unlock(&m_lock);
}


bool CarmenWrapper::sensorMapComputed(){
	pthread_mutex_lock(&m_mutex);
	bool smok=m_frontLaser;
	pthread_mutex_unlock(&m_mutex);
	return smok;
}

const SensorMap& CarmenWrapper::sensorMap(){
	return m_sensorMap;
}
 
bool CarmenWrapper::isRunning(){
	return m_threadRunning;
}

bool CarmenWrapper::isStopped(){
	return stopped;
}

int CarmenWrapper::queueLength(){
	int ql=0;
	pthread_mutex_lock(&m_mutex);
	ql=m_rangeDeque.size();
	pthread_mutex_unlock(&m_mutex);
	return ql;
}

OrientedPoint CarmenWrapper::getTruePos(){
	return m_truepos;
}

bool CarmenWrapper::getReading(RangeReading& reading){
	bool present=false;
	sem_wait(&m_dequeSem);
	pthread_mutex_lock(&m_mutex);
	if (!m_rangeDeque.empty()){
//		cerr << __PRETTY_FUNCTION__ << ": queue size=" <<m_rangeDeque.size() << endl;
		reading=m_rangeDeque.front();
		m_rangeDeque.pop_front();
		present=true;
	}
	int sval;
	sem_getvalue(&m_dequeSem,&sval);
//	cerr << "fetch. elements= "<< m_rangeDeque.size() << " sval=" << sval <<endl;
	pthread_mutex_unlock(&m_mutex);
	return present;
}

void CarmenWrapper::addReading(RangeReading& reading){
	pthread_mutex_lock(&m_mutex);
	m_rangeDeque.push_back(reading);
	pthread_mutex_unlock(&m_mutex);
	sem_post(&m_dequeSem);
	int sval;
	sem_getvalue(&m_dequeSem,&sval);
//	cerr << "post. elements= "<< m_rangeDeque.size() << " sval=" << sval <<endl;
}


//RangeSensor::RangeSensor(std::string name, unsigned int beams_num, unsigned int res, const OrientedPoint& position, double span, double maxrange): 

void CarmenWrapper::robot_frontlaser_handler(carmen_robot_laser_message* frontlaser) {
/*	if (! m_rangeSensor){
		double res=0;
		if (frontlaser->num_readings==180 || frontlaser->num_readings==181)
			res=M_PI/180;
		if (frontlaser->num_readings==360 || frontlaser->num_readings==361)
			res=M_PI/360;
		assert(res>0);
		m_rangeSensor=new RangeSensor("FLASER",frontlaser->num_readings, res, OrientedPoint(0,0,0), 0, 89.9);
		m_sensorMap.insert(make_pair(string("FLASER"), m_rangeSensor));
		
		cout << __PRETTY_FUNCTION__ 
		     << ": FrontLaser configured." 
		     << " Readings " << m_rangeSensor->beams().size() 
		     << " Resolution " << res << endl;
	}
	
	RangeReading reading(m_rangeSensor, frontlaser->timestamp);
	reading.resize(m_rangeSensor->beams().size());
	for (unsigned int i=0; i< (unsigned int)frontlaser->num_readings; i++){
		reading[i]=(double)frontlaser->range[i];
	}
	reading.setPose(OrientedPoint(frontlaser->x, frontlaser->y, frontlaser->theta));
*/	
	RangeReading reading=carmen2reading(*frontlaser);
	addReading(reading);
}

void CarmenWrapper::robot_rearlaser_handler(carmen_robot_laser_message* rearlaser) {
/*	if (! m_rangeSensor){
		double res=0;
		if (frontlaser->num_readings==180 || frontlaser->num_readings==181)
			res=M_PI/180;
		if (frontlaser->num_readings==360 || frontlaser->num_readings==361)
			res=M_PI/360;
		assert(res>0);
		m_rangeSensor=new RangeSensor("FLASER",frontlaser->num_readings, res, OrientedPoint(0,0,0), 0, 89.9);
		m_sensorMap.insert(make_pair(string("FLASER"), m_rangeSensor));
		
		cout << __PRETTY_FUNCTION__ 
		     << ": FrontLaser configured." 
		     << " Readings " << m_rangeSensor->beams().size() 
		     << " Resolution " << res << endl;
	}
	
	RangeReading reading(m_rangeSensor, frontlaser->timestamp);
	reading.resize(m_rangeSensor->beams().size());
	for (unsigned int i=0; i< (unsigned int)frontlaser->num_readings; i++){
		reading[i]=(double)frontlaser->range[i];
	}
	reading.setPose(OrientedPoint(frontlaser->x, frontlaser->y, frontlaser->theta));
*/	
	RangeReading reading=carmen2reading(*rearlaser);
	addReading(reading);
}




void CarmenWrapper:: navigator_go_handler(MSG_INSTANCE msgRef, BYTE_ARRAY callData, void*) {
  carmen_navigator_go_message msg;
  FORMATTER_PTR formatter;
  IPC_RETURN_TYPE err;

  formatter = IPC_msgInstanceFormatter(msgRef);
  err = IPC_unmarshallData(formatter, callData, &msg,
			   sizeof(carmen_navigator_go_message));  
  IPC_freeByteArray(callData);

  carmen_test_ipc_return
    (err, "Could not unmarshall", IPC_msgInstanceName(msgRef));
  cerr<<"go"<<endl;
  stopped=false;
}


void CarmenWrapper:: navigator_stop_handler(MSG_INSTANCE msgRef, BYTE_ARRAY callData, void*) {
  carmen_navigator_stop_message msg;
  FORMATTER_PTR formatter;
  IPC_RETURN_TYPE err;

  formatter = IPC_msgInstanceFormatter(msgRef);
  err = IPC_unmarshallData(formatter, callData, &msg,
			   sizeof(carmen_navigator_stop_message));  
  IPC_freeByteArray(callData);

  carmen_test_ipc_return
    (err, "Could not unmarshall", IPC_msgInstanceName(msgRef));
  cerr<<"stop"<<endl;
  stopped=true;
}



void CarmenWrapper::simulator_truepos_handler(carmen_simulator_truepos_message* truepos){
	m_truepos.x=truepos->truepose.x;
	m_truepos.y=truepos->truepose.y;
	m_truepos.theta=truepos->truepose.theta;
}

RangeReading CarmenWrapper::carmen2reading(const carmen_robot_laser_message& msg){
	//either front laser or rear laser
	double dth=msg.laser_pose.theta-msg.robot_pose.theta;
	dth=atan2(sin(dth), cos(dth));

	if (msg.laser_pose.theta==msg.robot_pose.theta && !m_frontLaser){
		double res=0;
		res = msg.config.angular_resolution;
// 		if (msg.num_readings==180 || msg.num_readings==181)
// 			res=M_PI/180;
// 		if (msg.num_readings==360 || msg.num_readings==361)
// 			res=M_PI/360;
		assert(res>0);
		string sensorName="FLASER";
		OrientedPoint rpose(msg.robot_pose.x, msg.robot_pose.y, msg.robot_pose.theta);
		OrientedPoint lpose(msg.laser_pose.x, msg.laser_pose.y, msg.laser_pose.theta);
		OrientedPoint dp=absoluteDifference(lpose, rpose);
		m_frontLaser=new RangeSensor(sensorName,msg.num_readings, res, OrientedPoint(0,0,msg.laser_pose.theta-msg.robot_pose.theta), 0, 
					      msg.config.maximum_range);
		m_frontLaser->updateBeamsLookup();
		m_sensorMap.insert(make_pair(sensorName, m_frontLaser));
		
		cout << __PRETTY_FUNCTION__ 
		     << ": " << sensorName <<" configured." 
		     << " Readings " << m_frontLaser->beams().size() 
		     << " Resolution " << res << endl;
	}
	if (msg.laser_pose.theta!=msg.robot_pose.theta && !m_rearLaser){
		double res=0;
		res = msg.config.angular_resolution;
// 		if (msg.num_readings==180 || msg.num_readings==181)
// 			res=M_PI/180;
// 		if (msg.num_readings==360 || msg.num_readings==361)
// 			res=M_PI/360;
		assert(res>0);
		OrientedPoint rpose(msg.robot_pose.x, msg.robot_pose.y, msg.robot_pose.theta);
		OrientedPoint lpose(msg.laser_pose.x, msg.laser_pose.y, msg.laser_pose.theta);
		OrientedPoint dp=absoluteDifference(lpose, rpose);
		string sensorName="RLASER";
		m_rearLaser=new RangeSensor(sensorName,msg.num_readings, res, OrientedPoint(0,0,msg.laser_pose.theta-msg.robot_pose.theta), 0, 
					      msg.config.maximum_range);
		m_rearLaser->updateBeamsLookup();
		m_sensorMap.insert(make_pair(sensorName, m_rearLaser));
		
		cout << __PRETTY_FUNCTION__ 
		     << ": " << sensorName <<" configured." 
		     << " Readings " << m_rearLaser->beams().size() 
		     << " Resolution " << res << endl;
	}	

	const RangeSensor * rs=(msg.laser_pose.theta==msg.robot_pose.theta)?m_frontLaser:m_rearLaser;
	RangeReading reading(rs, msg.timestamp);
	reading.resize(rs->beams().size());
	for (unsigned int i=0; i< (unsigned int)msg.num_readings; i++){
		reading[i]=(double)msg.range[i];
	}
	reading.setPose(OrientedPoint(msg.robot_pose.x, msg.robot_pose.y, msg.robot_pose.theta));
	return reading;
}

void CarmenWrapper::publish_globalpos(carmen_localize_summary_p summary)
{
  lock();
  static carmen_localize_globalpos_message globalpos;
  IPC_RETURN_TYPE err;
  
  globalpos.timestamp = carmen_get_time();
  globalpos.host = carmen_get_host();
  globalpos.globalpos = summary->mean;
  globalpos.globalpos_std = summary->std;
  globalpos.globalpos_xy_cov = summary->xy_cov;
  globalpos.odometrypos = summary->odometry_pos;
  globalpos.converged = summary->converged;
  err = IPC_publishData(CARMEN_LOCALIZE_GLOBALPOS_NAME, &globalpos);
  carmen_test_ipc_exit(err, "Could not publish", 
		       CARMEN_LOCALIZE_GLOBALPOS_NAME);  
  unlock();
}

/* publish a particle message */

void CarmenWrapper::publish_particles(carmen_localize_particle_filter_p filter, 
		       carmen_localize_summary_p summary)
{
  lock();
  static carmen_localize_particle_message pmsg;
  IPC_RETURN_TYPE err;

  pmsg.timestamp = carmen_get_time();
  pmsg.host = carmen_get_host();
  pmsg.globalpos = summary->mean;
  pmsg.globalpos_std = summary->mean;
  pmsg.num_particles = filter->param->num_particles;
  pmsg.particles = (carmen_localize_particle_ipc_p)filter->particles;
  err = IPC_publishData(CARMEN_LOCALIZE_PARTICLE_NAME, &pmsg);
  carmen_test_ipc_exit(err, "Could not publish", 
		       CARMEN_LOCALIZE_PARTICLE_NAME);  
  fprintf(stderr, "P");
  unlock();
}





void * CarmenWrapper::m_reading_function(void*){
	while (true) {
		lock();
		IPC_listen(100);
		unlock();
		usleep(20000);
	}    
	return 0;
}

void CarmenWrapper::shutdown_module(int sig){
  if(sig == SIGINT) {
      carmen_ipc_disconnect();

      fprintf(stderr, "\nDisconnecting (shutdown_module(%d) called).\n",sig);
      exit(0);
  }
}
/*
typedef struct {
  int num_readings;
  float *range;
  char *tooclose;
  double x, y, theta;//position of the laser on the robot
  double odom_x, odom_y, odom_theta; //position of the center of the robot
  double tv, rv;
  double forward_safety_dist, side_safety_dist;
  double turn_axis;
  double timestamp;
  char host[10];
} carmen_robot_laser_message;
*/

carmen_robot_laser_message CarmenWrapper::reading2carmen(const RangeReading& reading){
	carmen_robot_laser_message frontlaser;
	frontlaser.num_readings=reading.size();
	frontlaser.range = new float[frontlaser.num_readings];
	frontlaser.tooclose=0;
	frontlaser.laser_pose.x=frontlaser.robot_pose.x=reading.getPose().x;
	frontlaser.laser_pose.y=frontlaser.robot_pose.y=reading.getPose().y;
	frontlaser.laser_pose.theta=frontlaser.robot_pose.theta=reading.getPose().theta;
	frontlaser.tv=frontlaser.rv=0;
	frontlaser.forward_safety_dist=frontlaser.side_safety_dist=0;
	frontlaser.turn_axis=0;
	frontlaser.timestamp=reading.getTime();
	for (unsigned int i=0; i< reading.size(); i++){
		frontlaser.range[i]=(float)reading[i];
	}
	return frontlaser;
}

carmen_point_t CarmenWrapper::point2carmen (const OrientedPoint& p){
	return (carmen_point_t){p.x,p.y,p.theta};
}

OrientedPoint CarmenWrapper::carmen2point (const carmen_point_t& p){
	return OrientedPoint(p.x, p.y, p.theta);
}


/*
int main (int argc, char** argv) {
	CarmenWrapper::start(argc, argv);
	while(1){
		sleep(2);
		RangeReading reading(0,0);
		while(CarmenWrapper::getReading(reading)){
			cout << "FLASER " <<  reading.size();
			for (int i=0; i<reading.size(); i++)
				cout << " " << reading[i];
			cout << reading.getPose().x << " "
			     << reading.getPose().y << " "
			     << reading.getPose().theta << " 0 cazzo 0" << endl;
		}
		cout << endl;
	}
	return 1;
}
*/

