#include "homer_navigation/homer_navigation_node.h"

HomerNavigationNode::HomerNavigationNode()
{
	ros::NodeHandle nh;

	//subscribers
	m_map_sub 					= nh.subscribe<nav_msgs::OccupancyGrid>("/map", 1, &HomerNavigationNode::mapCallback, this);
	m_pose_sub					= nh.subscribe<geometry_msgs::PoseStamped>("/pose", 1, &HomerNavigationNode::poseCallback, this);
	m_laser_data_sub			= nh.subscribe<sensor_msgs::LaserScan>("/scan", 1, &HomerNavigationNode::laserDataCallback, this);
	m_down_laser_data_sub 		= nh.subscribe<sensor_msgs::LaserScan>("/front_scan", 1, &HomerNavigationNode::downlaserDataCallback, this);
	m_start_navigation_sub 		= nh.subscribe<homer_mapnav_msgs::StartNavigation>("/homer_navigation/start_navigation", 1, &HomerNavigationNode::startNavigationCallback, this);
	m_move_base_simple_goal_sub = nh.subscribe<geometry_msgs::PoseStamped>("/move_base_simple/goal", 1, &HomerNavigationNode::moveBaseSimpleGoalCallback, this); // for RVIZ usage
	m_stop_navigation_sub 		= nh.subscribe<std_msgs::Empty>("/homer_navigation/stop_navigation", 1, &HomerNavigationNode::stopNavigationCallback, this);
	m_navigate_to_poi_sub		= nh.subscribe<homer_mapnav_msgs::NavigateToPOI>("/homer_navigation/navigate_to_POI", 1, &HomerNavigationNode::navigateToPOICallback, this);
	m_unknown_threshold_sub 	= nh.subscribe<std_msgs::Int8>("/homer_navigation/unknown_threshold", 1, &HomerNavigationNode::unknownThresholdCallback, this);	
	m_refresh_param_sub 		= nh.subscribe<std_msgs::Empty>("/homer_navigation/refresh_params", 1, &HomerNavigationNode::refreshParamsCallback, this);
	m_max_move_depth_sub 		= nh.subscribe<std_msgs::Float32>("/homer_navigation/max_depth_move_distance", 1, &HomerNavigationNode::maxDepthMoveDistanceCallback, this);

	m_cmd_vel_pub				= nh.advertise<geometry_msgs::Twist>("/robot_platform/cmd_vel", 1);
	m_target_reached_string_pub = nh.advertise<std_msgs::String>("/homer_navigation/target_reached", 1);
	//m_target_reached_empty_pub 	= nh.advertise<std_msgs::Empty>("/homer_navigation/target_reached", 1);
	m_target_unreachable_pub 	= nh.advertise<homer_mapnav_msgs::TargetUnreachable>("/homer_navigation/target_unreachable", 1);
	m_path_pub 					= nh.advertise<nav_msgs::Path>("/homer_navigation/path", 1);
	m_ptu_center_world_point_pub= nh.advertise<homer_ptu_msgs::CenterWorldPoint>( "/ptu/center_world_point", 1);
	m_set_pan_tilt_pub			= nh.advertise<homer_ptu_msgs::SetPanTilt>("/ptu/set_pan_tilt", 1);
	m_debug_pub     		    = nh.advertise<std_msgs::String>("/homer_navigation/debug",1);

	m_get_POIs_client 			= nh.serviceClient<homer_mapnav_msgs::GetPointsOfInterest>("/map_manager/get_pois");

	m_MainMachine.setName( "HomerNavigation Main" );
	ADD_MACHINE_STATE( m_MainMachine, IDLE );
	ADD_MACHINE_STATE( m_MainMachine, AWAITING_PATHPLANNING_MAP );
	ADD_MACHINE_STATE( m_MainMachine, FOLLOWING_PATH );
	ADD_MACHINE_STATE( m_MainMachine, AVOIDING_COLLISION );
	ADD_MACHINE_STATE( m_MainMachine, FINAL_TURN );

	init();
}

void HomerNavigationNode::loadParameters()
{
	//Explorer constructor 
	ros::param::param("/homer_mapping/resolution", 						m_resolution, (double) 0.05);
	ros::param::param("/homer_navigation/allowed_obstacle_distance/min", 	m_AllowedObstacleDistance.first, (float) 0.3);
	ros::param::param("/homer_navigation/allowed_obstacle_distance/max", 	m_AllowedObstacleDistance.second, (float) 5.0);
	ros::param::param("/homer_navigation/safe_obstacle_distance/min", 	m_SafeObstacleDistance.first, (float) 0.7);
	ros::param::param("/homer_navigation/safe_obstacle_distance/max", 	m_SafeObstacleDistance.second, (float) 1.5);
	ros::param::param("/homer_navigation/frontier_safeness_factor", 		m_FrontierSafenessFactor, (float) 1.4);
	ros::param::param("/homer_navigation/safe_path_weight", 				m_SafePathWeight, (double) 1.2);
	ros::param::param("/homer_navigation/waypoint_sampling_threshold", 	m_waypoint_sampling_threshold, (float) 1.5);    
	m_AllowedObstacleDistance.first 	/= m_resolution;
	m_AllowedObstacleDistance.second	/= m_resolution;
	m_SafeObstacleDistance.first 		/= m_resolution;
	m_SafeObstacleDistance.second 		/= m_resolution;

	//check path 
	ros::param::param("/homer_navigation/check_path", 					m_check_path, (bool) true);
	ros::param::param("/homer_navigation/check_path_max_distance", 		m_check_path_max_distance, (float) 2.0);

	//collision 
	ros::param::param("/homer_navigation/collision_distance", 			m_collision_distance, (float) 0.3);
	ros::param::param("/homer_navigation/collision_distance_near_target", m_collision_distance_near_target, (float) 0.2);
	ros::param::param("/homer_navigation/backward_collision_distance", 	m_backward_collision_distance, (float) 0.5);

	//cmd_vel config values
	ros::param::param("/homer_navigation/min_turn_angle", 				m_min_turn_angle, (float) 0.15);
	ros::param::param("/homer_navigation/max_turn_speed", 				m_max_turn_speed, (float) 0.6);
	ros::param::param("/homer_navigation/min_turn_speed",					m_min_turn_speed, (float) 0.3);
	ros::param::param("/homer_navigation/max_move_speed", 				m_max_move_speed, (float) 0.4);
	ros::param::param("/homer_navigation/max_drive_angle",				m_max_drive_angle, (float) 0.6);

	//caution factors
	ros::param::param("/homer_navigation/map_speed_factor", 				m_map_speed_factor, (float) 1.0);	
	ros::param::param("/homer_navigation/waypoint_speed_factor", 			m_waypoint_speed_factor, (float) 1.0);	
	ros::param::param("/homer_navigation/obstacle_speed_factor", 			m_obstacle_speed_factor, (float) 1.0);	
	ros::param::param("/homer_navigation/target_distance_speed_factor", 	m_target_distance_speed_factor, (float) 0.4);	

	//robot dimensions
	ros::param::param("/homer_navigation/min_x", 							m_min_x, (float) 0.3);
	ros::param::param("/homer_navigation/min_y",							m_min_y, (float) 0.27);

	//error durations
	ros::param::param("/homer_navigation/callback_error_duration", 		m_callback_error_duration, (float) 0.3);

	ros::param::param("/homer_navigation/use_ptu",						m_use_ptu, (bool) false);

	ros::param::param("/homer_navigation/unknown_threshold", 				m_unknown_threshold, (int) 50);
	ros::param::param("/homer_navigation/waypoint_radius_factor",			m_waypoint_radius_factor, (float) 0.25);
}

void HomerNavigationNode::init()
{
	m_max_move_sick				= 40.0;
	m_max_move_down				= 40.0;
	m_max_move_depth			= 40.0;
	m_robot_pose.position.x 	= 0.0;
	m_robot_pose.position.y 	= 0.0;
	m_robot_pose.position.z 	= 0.0;   
	m_robot_pose.orientation 	= tf::createQuaternionMsgFromYaw(0.0);
	m_robot_last_pose = m_robot_pose;
	m_avoided_collision 		= false; 
	m_act_speed 				= 0;
	m_angular_avoidance 		= 0;
	m_last_calculations_failed 	= 0;
	m_last_check_path_res		= false;
	m_new_target 				= true;

	loadParameters();

	m_explorer = new Explorer ( m_AllowedObstacleDistance.first, m_AllowedObstacleDistance.second,
			m_SafeObstacleDistance.first, m_SafeObstacleDistance.second,
			m_SafePathWeight, m_FrontierSafenessFactor, m_unknown_threshold );
	m_last_map_data = new std::vector<int8_t>(0);

	m_MainMachine.setState ( IDLE );
}

HomerNavigationNode::~HomerNavigationNode()
{
	if(m_explorer)
	{
		delete m_explorer;
	}
	if(m_last_map_data)
	{
		delete m_last_map_data;
	}
}

void HomerNavigationNode::stopRobot()
{
	m_act_speed = 0;
	geometry_msgs::Twist cmd_vel_msg;
	cmd_vel_msg.linear.x  = 0;
	cmd_vel_msg.linear.y  = 0;
	cmd_vel_msg.linear.z  = 0;
	cmd_vel_msg.angular.x = 0;
	cmd_vel_msg.angular.y = 0;
	cmd_vel_msg.angular.z = 0;
	m_cmd_vel_pub.publish(cmd_vel_msg);
	ros::Duration(0.1).sleep();
	m_cmd_vel_pub.publish(cmd_vel_msg);
	ros::Duration(0.1).sleep();
	m_cmd_vel_pub.publish(cmd_vel_msg);
}

void HomerNavigationNode::idleProcess()
{
	if ( m_MainMachine.state() == FOLLOWING_PATH )
	{
		if ( (ros::Time::now() - m_last_laser_time) > ros::Duration( m_callback_error_duration ) )
		{
			ROS_ERROR_STREAM("Laser data timeout!\n");
			stopRobot();
		}
		if ( (ros::Time::now() - m_last_pose_time) > ros::Duration( m_callback_error_duration )  )
		{
			ROS_ERROR_STREAM("Pose timeout!\n");
			stopRobot();
		}
	}
}

void HomerNavigationNode::calculatePath()
{
	if ( m_distance_to_target < m_desired_distance && !m_new_target)
	{
		m_path_reaches_target = true;
		return;
	}
	m_explorer->setOccupancyMap(m_width, m_width, m_origin, &(*m_last_map_data)[0]);
	m_explorer->setStart( map_tools::toMapCoords( m_robot_pose.position,  m_origin, m_resolution) );

	bool success;
	m_pixel_path = m_explorer->getPath( success );
	if ( !success)
	{
		ROS_WARN_STREAM("No path found for navigation" );
		m_last_calculations_failed++;
		ROS_INFO_STREAM("last_calculation_failed: "<<m_last_calculations_failed);
		if(m_last_calculations_failed > 8)
		{
			sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
		}
	}
	else
	{
		m_last_calculations_failed = 0;
		std::vector<Eigen::Vector2i> waypoint_pixels = m_explorer->sampleWaypointsFromPath ( m_pixel_path, m_waypoint_sampling_threshold );
		m_waypoints.clear();
		ROS_INFO_STREAM("homer_navigation::calculatePath - Path Size: " << waypoint_pixels.size());
		if(waypoint_pixels.size() > 0)
		{
			for(std::vector<Eigen::Vector2i>::iterator it = waypoint_pixels.begin(); it != waypoint_pixels.end(); ++it)
			{
				geometry_msgs::PoseStamped poseStamped;
				poseStamped.header.frame_id = "/map";
				poseStamped.pose.position = map_tools::fromMapCoords(*it, m_origin, m_resolution);
				poseStamped.pose.orientation.x = 0.0;
				poseStamped.pose.orientation.y = 0.0;
				poseStamped.pose.orientation.z = 0.0;
				poseStamped.pose.orientation.w = 1.0;
				m_waypoints.push_back(poseStamped);
			}
			sendPathData();
		}
		else
		{
			sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);		
		}

		m_last_laser_time = ros::Time::now();
		m_last_pose_time  = ros::Time::now();
	}
}

void HomerNavigationNode::startNavigation()
{
	if ( m_distance_to_target < m_desired_distance && !m_new_target)
	{
		ROS_INFO_STREAM( "Will not (re-)plan path: Target position already reached." );
		m_path_reaches_target = true;
		targetPositionReached();
		return;
	}
	ROS_INFO_STREAM( "Distance to target still too large (" << m_distance_to_target << "m; requested: " << m_desired_distance << "m)" );

	if(m_fast_path_planning)
	{
		maskMap();
	}

	m_explorer->setOccupancyMap(m_width, m_width, m_origin, &(*m_last_map_data)[0]);
	m_explorer->setStart(map_tools::toMapCoords(m_robot_pose.position, m_origin, m_resolution));
	Eigen::Vector2i new_target_approx = m_explorer->getNearestAccessibleTarget(map_tools::toMapCoords(m_target_point, m_origin, m_resolution));

	geometry_msgs::Point new_target_approx_world = map_tools::fromMapCoords(new_target_approx, m_origin, m_resolution);
	ROS_INFO_STREAM("start Navigation: Approx target: " << new_target_approx_world);

	m_path_reaches_target = ( map_tools::distance(new_target_approx_world, m_target_point ) < m_desired_distance );

	bool new_approx_is_better = ( map_tools::distance( m_robot_pose.position, m_target_point ) - map_tools::distance( new_target_approx_world, m_target_point ) ) > 2 * m_desired_distance;
	if ( !new_approx_is_better && !m_path_reaches_target )
	{
		ROS_WARN_STREAM( "No better way to target found, turning and then reporting as unreachable."
				<<     std::endl << "Distance to target: " << m_distance_to_target << "m; requested: " << m_desired_distance << "m" );
		m_MainMachine.setState( FINAL_TURN );
	}
	else
	{
		m_explorer->setTarget ( new_target_approx);
		m_MainMachine.setState ( FOLLOWING_PATH );
		calculatePath();
	}
}

void HomerNavigationNode::sendPathData()
{
	nav_msgs::Path msg;
	msg.header.frame_id = "/map";
	msg.header.stamp = ros::Time::now();
	if(m_waypoints.size() > 0)
	{
		msg.poses = m_waypoints;
		geometry_msgs::PoseStamped pose_stamped;
		pose_stamped.pose = m_robot_pose;
		pose_stamped.header.frame_id = "/map";
		pose_stamped.header.stamp = ros::Time::now();
		msg.poses.insert(msg.poses.begin(), pose_stamped);
	}
	m_path_pub.publish(msg);
}

void HomerNavigationNode::sendTargetReachedMsg() 
{
	stopRobot();
	m_MainMachine.setState( IDLE );
	std_msgs::String reached_string_msg;
	reached_string_msg.data = m_target_name;
	m_target_reached_string_pub.publish(reached_string_msg);
	m_waypoints.clear();
	nav_msgs::Path empty_path_msg;
	empty_path_msg.poses = m_waypoints;
	m_path_pub.publish(empty_path_msg);
	ROS_INFO_STREAM("=== Reached Target " << m_target_name << " ===");
}

void HomerNavigationNode::sendTargetUnreachableMsg( int8_t reason ) 
{
	stopRobot();
	m_MainMachine.setState( IDLE );
	homer_mapnav_msgs::TargetUnreachable unreachable_msg;
	unreachable_msg.reason = reason;
	m_target_unreachable_pub.publish(unreachable_msg);
	m_waypoints.clear();
	nav_msgs::Path empty_path_msg;
	empty_path_msg.poses = m_waypoints;
	m_path_pub.publish(empty_path_msg);
	ROS_INFO_STREAM("=== TargetUnreachableMsg ===");
}

void HomerNavigationNode::targetPositionReached()
{
	ROS_INFO_STREAM( "Target position reached. Distance to target: " << m_distance_to_target << "m. Desired distance:" << m_desired_distance << "m" );
	stopRobot();
	m_waypoints.clear();
	sendPathData();
	m_MainMachine.setState( FINAL_TURN );
	ROS_INFO_STREAM("Turning to look-at point");
}

bool HomerNavigationNode::checkPath()
{
	if(m_pixel_path.size() != 0)
	{
		for ( unsigned i = 0; i < m_pixel_path.size()-1; i++ )
		{
			geometry_msgs::Point p = map_tools::fromMapCoords(m_pixel_path.at(i), m_origin, m_resolution);
			if ( map_tools::distance( m_robot_pose.position, p) > m_check_path_max_distance )
			{
				return true;
			}
			for(int a = 0; a < 5; a++)
			{
				if (map_tools::findValue( m_last_map_data, m_width, m_height, 
							m_pixel_path[i].x() + (m_pixel_path[i+1].x() - m_pixel_path[i].x()) * a/4, 
							m_pixel_path[i].y() + (m_pixel_path[i+1].y() - m_pixel_path[i].y()) * a/4,
							90, 2 ))
				{
					ROS_WARN_STREAM("Obstacle detected in current path recalculating" );
					return false;
				}
			}
		}
	}
}

void HomerNavigationNode::handleCollision ()
{
	if ( m_MainMachine.state() == FOLLOWING_PATH )
	{
		stopRobot();
		m_MainMachine.setState( AVOIDING_COLLISION );
		ROS_WARN_STREAM( "Collision detected while following path!" );
	}
}

void HomerNavigationNode::performNextMove()
{
	if(m_MainMachine.state() == FOLLOWING_PATH )
	{
		if ( m_distance_to_target < m_desired_distance && !m_new_target)
		{
			ROS_INFO_STREAM( "Desired distance to target was reached." );
			targetPositionReached();
			return;
		}
		//if we have accidentaly skipped waypoints, recalculate path
		float minDistance = FLT_MAX;
		float distance;
		unsigned nearestWaypoint=0;
		for ( unsigned i=0; i<m_waypoints.size(); i++ )
		{
			distance = map_tools::distance( m_robot_pose.position, m_waypoints[i].pose.position );
			if ( distance < minDistance ) {
				nearestWaypoint = i;
				minDistance = distance;
			}
		}
		if ( nearestWaypoint != 0 )
		{
			//if the target is nearer than the waypoint don't recalculate
			if(m_waypoints.size() != 2)
			{
				ROS_WARN_STREAM("Waypoints skipped. Recalculating path!");
				calculatePath();
				if ( m_MainMachine.state() != FOLLOWING_PATH ) 
				{ 
					return; 
				}
			}
			else
			{
				m_waypoints.erase(m_waypoints.begin());
			}
		}

		Eigen::Vector2i waypointPixel = map_tools::toMapCoords(m_waypoints[0].pose.position, m_origin, m_resolution);
		float obstacleDistanceMap = m_explorer->getObstacleTransform()->getValue( waypointPixel.x(), waypointPixel.y() ) * m_resolution;
		float waypointRadius = m_waypoint_radius_factor * obstacleDistanceMap;

		if ( ( waypointRadius < m_resolution ) || ( m_waypoints.size() == 1 ) )
		{
			waypointRadius = m_resolution;
		}

		if(m_waypoints.size() != 0)
		{
			//calculate distance between last_pose->current_pose and waypoint
			Eigen::Vector2f line; //direction of the line
			line[0] = m_robot_pose.position.x - m_robot_last_pose.position.x;
			line[1] = m_robot_pose.position.y - m_robot_last_pose.position.y;
			Eigen::Vector2f point_to_start; //vector from the point to the start of the line
			point_to_start[0] = m_robot_last_pose.position.x - m_waypoints[0].pose.position.x;
			point_to_start[1] = m_robot_last_pose.position.y - m_waypoints[0].pose.position.y;
			float dot = point_to_start[0] * line[0] + point_to_start[1] * line[1]; //dot product of point_to_start * line
			Eigen::Vector2f distance; //shortest distance vector from point to line
			distance[0] = point_to_start[0] - dot * line[0]; 
			distance[1] = point_to_start[1] - dot * line[1];
			float distance_to_waypoint = sqrt((double)(distance[0] * distance[0] + distance[1] * distance[1]));

			//check if current waypoint has been reached
			if((distance_to_waypoint < waypointRadius && m_waypoints.size() > 1) || (m_distance_to_target < waypointRadius))
			{
				m_waypoints.erase ( m_waypoints.begin() );
			}

		}

		sendPathData();
		//last wayoint reached
		if ( m_waypoints.size() == 0 )
		{
			ROS_INFO_STREAM("Last waypoint reached");
			currentPathFinished();
			return;
		}

		if(m_use_ptu)
		{
			ROS_INFO_STREAM("Moving PTU to center next Waypoint");
			homer_ptu_msgs::CenterWorldPoint ptu_msg;
			ptu_msg.point.x = m_waypoints[0].pose.position.x;
			ptu_msg.point.y = m_waypoints[0].pose.position.y;
			ptu_msg.point.z = 0;
			ptu_msg.permanent = true;
			m_ptu_center_world_point_pub.publish(ptu_msg);
		}

		float distanceToWaypoint = map_tools::distance( m_robot_pose.position, m_waypoints[0].pose.position );
		float angleToWaypoint = angleToPointDeg ( m_waypoints[0].pose.position );
		if (angleToWaypoint < -180)
		{
			angleToWaypoint += 360;
		}
		float angle = deg2Rad( angleToWaypoint );

		//linear speed calculation
		if(m_avoided_collision)
		{
			if( std::abs(angleToWaypoint) < 10)
			{
				m_avoided_collision = false;
			}
		}
		else if (abs(angle) > m_max_drive_angle)
		{
			m_act_speed = 0.0;
		}
		else
		{
			float obstacleMapDistance = 1;
			for(int wpi = -1; wpi < std::min((int)m_waypoints.size(),(int) 2); wpi++)
			{
				Eigen::Vector2i robotPixel;
				if(wpi == -1)
				{
					robotPixel = map_tools::toMapCoords(m_robot_pose.position, m_origin, m_resolution);
				}
				else
				{
					robotPixel = map_tools::toMapCoords(m_waypoints[wpi].pose.position, m_origin, m_resolution);
				}
				obstacleMapDistance = std::min((float)obstacleMapDistance,(float)( m_explorer->getObstacleTransform()->getValue( robotPixel.x(), robotPixel.y() ) * m_resolution));
			}

			float max_move_distance_speed = m_max_move_speed * m_max_move_distance * m_obstacle_speed_factor; 
			float max_map_distance_speed = m_max_move_speed * obstacleMapDistance * m_map_speed_factor;	
			m_act_speed = std::min(std::max((float)0.1,m_distance_to_target * m_target_distance_speed_factor) ,std::min(std::min(m_max_move_speed, max_move_distance_speed), std::min(max_map_distance_speed, distanceToWaypoint *  m_waypoint_speed_factor)));
			std_msgs::String tmp; 
			std::stringstream str; 
			str << "m_obstacle_speed " << max_move_distance_speed <<" max_map_distance_speed "<<max_map_distance_speed;
			tmp.data = str.str();
			m_debug_pub.publish(tmp);
		}

		//angular speed calculation
		if (angle < 0 )
		{
			angle = std::max(angle * (float) 0.8, -m_max_turn_speed);
			m_act_speed = m_act_speed + angle / 2.0;
			if(m_act_speed < 0 )
			{
				m_act_speed = 0;
			}
		}
		else
		{
			angle = std::min(angle * (float) 0.8, m_max_turn_speed);
			m_act_speed = m_act_speed - angle / 2.0;
			if(m_act_speed < 0 ) 
			{
				m_act_speed = 0;
			}
		}
		geometry_msgs::Twist cmd_vel_msg;
		cmd_vel_msg.linear.x = m_act_speed;			
		cmd_vel_msg.angular.z = angle;
		m_cmd_vel_pub.publish(cmd_vel_msg);

		ROS_DEBUG_STREAM("Driving & turning" << std::endl << "linear: " << m_act_speed << " angular: " << angle << std::endl
				<< "distanceToWaypoint:" << distanceToWaypoint << "angleToWaypoint: " << angleToWaypoint << std::endl );
	}
	else if(m_MainMachine.state()== AVOIDING_COLLISION)
	{
		if( m_distance_to_target < m_desired_distance && !m_new_target)
		{
			ROS_INFO_STREAM("Collision detected near target. Switch to final turn.");
			targetPositionReached();
		} 
		else if ( m_max_move_distance <= m_collision_distance && m_waypoints.size() > 1 || m_max_move_distance <= m_collision_distance_near_target )
		{
			ROS_WARN_STREAM( "Maximum driving distance too short (" << m_max_move_distance << "m)! Moving back." );
			geometry_msgs::Twist cmd_vel_msg;
			if(!HomerNavigationNode::backwardObstacle())
			{
				cmd_vel_msg.linear.x = -0.2;
			}
			else
			{
				if(m_angular_avoidance == 0)
				{
					float angleToWaypoint = angleToPointDeg ( m_waypoints[0].pose.position );
					if(angleToWaypoint < -180)
					{
						angleToWaypoint += 360;
					}
					if (angleToWaypoint < 0 )
					{
						m_angular_avoidance = -0.4; 
					}
					else
					{
						m_angular_avoidance = 0.4;
					}
				}
				cmd_vel_msg.angular.z = m_angular_avoidance;
			}
			m_cmd_vel_pub.publish(cmd_vel_msg);	
		} 
		else
		{
			m_angular_avoidance = 0;
			m_avoided_collision = true;
			ROS_WARN_STREAM( "Collision avoided. Updating path." );
			currentPathFinished();
		}
	}
	else if(m_MainMachine.state()== FINAL_TURN)
	{
		if(m_use_ptu)
		{
			//reset PTU
			homer_ptu_msgs::SetPanTilt msg;
			msg.absolute 	= true;
			msg.panAngle 	= 0;
			msg.tiltAngle 	= 0;
			m_set_pan_tilt_pub.publish(msg);
		}

		if ( m_skip_final_turn ) 
		{
			ROS_INFO_STREAM("Final turn skipped. Target reached.");
			if(m_path_reaches_target)
			{
				sendTargetReachedMsg();
			}
			else
			{
				sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
			}
			return;
		}

		float turnAngle = minTurnAngle( tf::getYaw(m_robot_pose.orientation), m_target_orientation );
		ROS_DEBUG_STREAM("homer_navigation::PerformNextMove:: Final Turn. Robot orientation: " << rad2Deg(tf::getYaw(m_robot_pose.orientation)) << ". Target orientation: " << rad2Deg(m_target_orientation) << "homer_navigation::PerformNextMove:: turnAngle: " << rad2Deg(turnAngle));

		if (std::fabs(turnAngle) < m_min_turn_angle) 
		{
			ROS_INFO_STREAM(":::::::NEAREST WALKABLE TARGET REACHED BECAUSE lower " << m_min_turn_angle);
			ROS_INFO_STREAM("target angle = "<< m_target_orientation );
			ROS_INFO_STREAM("is angle = "<< tf::getYaw(m_robot_pose.orientation));
			ROS_INFO_STREAM("m_distance_to_target = " << m_distance_to_target);
			ROS_INFO_STREAM("m_desired_distance = " << m_desired_distance);
			if(m_path_reaches_target)
			{
				sendTargetReachedMsg();
			}
			else
			{
				sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
			}
			return;
		}
		else
		{
			if (turnAngle< 0 )
			{
				turnAngle= std::max(std::min(turnAngle, -m_min_turn_speed ),-m_max_turn_speed);
			}
			else
			{
				turnAngle = std::min(std::max(turnAngle, m_min_turn_speed ),m_max_turn_speed);
			}
			geometry_msgs::Twist cmd_vel_msg;
			cmd_vel_msg.angular.z = turnAngle;
			m_cmd_vel_pub.publish(cmd_vel_msg);				
		}
	}
}

void HomerNavigationNode::currentPathFinished()
{
	ROS_INFO_STREAM( "Current path was finished, initiating recalculation.");
	m_waypoints.clear();
	stopRobot();
	m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
}

int HomerNavigationNode::angleToPointDeg ( geometry_msgs::Point target )
{
	double cx = m_robot_pose.position.x;
	double cy = m_robot_pose.position.y;
	int targetAngle = rad2Deg( atan2 ( target.y - cy, target.x - cx ) );
	int currentAngle = rad2Deg( tf::getYaw(m_robot_pose.orientation) );

	int angleDiff = targetAngle - currentAngle;
	angleDiff = (angleDiff + 180) % 360 - 180;
	return angleDiff;
}

bool HomerNavigationNode::drawPolygon ( std::vector< geometry_msgs::Point > vertices)
{
	if ( vertices.size() == 0 )
	{
		ROS_INFO_STREAM( "No vertices given!" );
		return false;
	}
	//make temp. map
	std::vector<int> data(m_width * m_height);
	for ( int i = 0; i < data.size(); i++ )
	{
		data[i] = 0;
	}

	//draw the lines surrounding the polygon
	for ( unsigned int i = 0; i < vertices.size(); i++ )
	{
		int i2 = ( i+1 ) % vertices.size();
		drawLine ( data, vertices[i].x, vertices[i].y, vertices[i2].x, vertices[i2].y,  30);
	}
	//calculate a point in the middle of the polygon
	float midX = 0;
	float midY = 0;
	for ( unsigned int i = 0; i < vertices.size(); i++ )
	{
		midX += vertices[i].x;
		midY += vertices[i].y;
	}
	midX /= vertices.size();
	midY /= vertices.size();
	//fill polygon
	return fillPolygon ( data, (int)midX, (int)midY, 30 );
}

void HomerNavigationNode::drawLine ( std::vector<int> &data, int startX, int startY, int endX, int endY, int value )
{
	//bresenham algorithm
	int x, y, t, dist, xerr, yerr, dx, dy, incx, incy;
	// compute distances
	dx = endX - startX;
	dy = endY - startY;

	// compute increment
	if ( dx < 0 )
	{
		incx = -1;
		dx = -dx;
	}
	else
	{
		incx = dx ? 1 : 0;
	}

	if ( dy < 0 )
	{
		incy = -1;
		dy = -dy;
	}
	else
	{
		incy = dy ? 1 : 0;
	}

	// which distance is greater?
	dist = ( dx > dy ) ? dx : dy;
	// initializing
	x = startX;
	y = startY;
	xerr = dx;
	yerr = dy;

	// compute cells
	for ( t = 0; t < dist; t++ )
	{
		int index = x + m_width* y;
		if(index < 0 || index > data.size())
		{
			continue;
		}
		data[index] = value;

		xerr += dx;
		yerr += dy;
		if ( xerr > dist )
		{
			xerr -= dist;
			x += incx;
		}
		if ( yerr > dist )
		{
			yerr -= dist;
			y += incy;
		}
	}
}

bool HomerNavigationNode::fillPolygon ( std::vector<int> &data, int x, int y, int value )
{
	int index = x + m_width * y;
	bool tmp = false;

	if ((int)m_last_map_data->at(index)> 90)
	{
		tmp = true;
	}
	if ( data[index] != value )
	{
		data[index] = value;
		if (fillPolygon ( data, x + 1, y, value ))
		{
			tmp =  true;
		}
		if(fillPolygon ( data, x - 1, y, value ))
		{
			tmp = true;
		}
		if(fillPolygon ( data, x, y + 1, value ))
		{
			tmp = true;
		}
		if(fillPolygon ( data, x, y - 1, value ))
		{
			tmp = true;
		}
	}
	return tmp;
}

bool HomerNavigationNode::backwardObstacle()
{
	std::vector<geometry_msgs::Point> vertices; 
	geometry_msgs::Point base_link_point;
	geometry_msgs::Point map_point;
	Eigen::Vector2i map_coord;

	std::vector<float> x;
	std::vector<float> y;

	x.push_back(- m_min_x - m_backward_collision_distance);
	y.push_back(m_min_y);

	x.push_back(- m_min_x - m_backward_collision_distance);
	y.push_back(-m_min_y);

	x.push_back(-0.1);
	y.push_back(-m_min_y);

	x.push_back(-0.1);
	y.push_back(m_min_y);

	for(int i = 0; i<x.size(); i++)
	{
		base_link_point.x = x[i];
		base_link_point.y = y[i];
		map_coord = map_tools::toMapCoords(map_tools::transformPoint(base_link_point, m_transform_listener ,"/base_link", "/map"), m_origin, m_resolution);
		map_point.x = map_coord.x(); 
		map_point.y = map_coord.y();
		vertices.push_back(map_point);
	}

	return drawPolygon(vertices);
}

void HomerNavigationNode::maskMap()
{
	//generate bounding box
	ROS_INFO_STREAM("Calculating Bounding box for fast planning");
	Eigen::Vector2i pose_pixel =  map_tools::toMapCoords( m_robot_pose.position,  m_origin, m_resolution);
	Eigen::Vector2i target_pixel =  map_tools::toMapCoords( m_target_point,  m_origin, m_resolution);
	Eigen::Vector2i safe_pixel_distance(m_AllowedObstacleDistance.first * 4,
			m_AllowedObstacleDistance.first * 4);
	Eigen::AlignedBox2i planning_box;
	planning_box.extend(pose_pixel);
	planning_box.extend(target_pixel);
	ROS_INFO_STREAM("Bounding Box: (" << planning_box.min() << " " << planning_box.max());
	Eigen::AlignedBox2i safe_planning_box(planning_box.min() - safe_pixel_distance, planning_box.max() + safe_pixel_distance);
	ROS_INFO_STREAM("safe Bounding Box: (" << safe_planning_box.min() << " " << safe_planning_box.max());
	ROS_INFO_STREAM("min in m: " <<map_tools::fromMapCoords(safe_planning_box.min(), m_origin, m_resolution));
	ROS_INFO_STREAM("max in m: "<<map_tools::fromMapCoords(safe_planning_box.max(), m_origin, m_resolution));
	for(size_t x = 0; x < m_width; x++)
	{
		for(size_t y = 0; y < m_width; y++)
		{
			if(!safe_planning_box.contains(Eigen::Vector2i(x, y)))
			{
				m_last_map_data->at(y * m_width + x) = -1;
			}
		}		
	}	
}

//convenience math functions
float HomerNavigationNode::minTurnAngle( float angle1, float angle2 )
{
	angle1 *= 180.0/M_PI;
	angle2 *= 180.0/M_PI;

	int diff= angle2 - angle1;
	diff = (diff + 180) % 360 - 180;
    if (diff < -180)
    {
        diff += 360;
    }

	float ret = static_cast<double>(diff) * M_PI/180.0;
	return ret;
}

void HomerNavigationNode::refreshParamsCallback(const std_msgs::Empty::ConstPtr& msg)
{
	ROS_INFO_STREAM("Refreshing Parameters");
	loadParameters();
}

void HomerNavigationNode::mapCallback(const nav_msgs::OccupancyGrid::ConstPtr& msg)
{
    if(msg->info.height != msg->info.width)
	{
		ROS_ERROR_STREAM("Incoming Map not quadratic. No map update!");
		return;
	}
	if(m_last_map_data)
	{
		delete m_last_map_data;
	}

	m_last_map_data = new std::vector<int8_t>(msg->data);
	m_origin = msg->info.origin;
	m_width = msg->info.width;
	m_height = msg->info.height;
	m_resolution = msg->info.resolution;

	switch ( m_MainMachine.state() )
	{
		case AWAITING_PATHPLANNING_MAP:
			startNavigation();
			break;
		case FOLLOWING_PATH:
			{
				if ( m_check_path)
				{
					if( !checkPath())
					{
						if(!m_last_check_path_res)
						{
							calculatePath();
						}
						m_last_check_path_res = false;
					}
					else
					{
						m_last_check_path_res = true;
					}
				}
				break;
			}
	}
}

void HomerNavigationNode::poseCallback(const geometry_msgs::PoseStamped::ConstPtr& msg)
{
	m_robot_last_pose = m_robot_pose;
	m_robot_pose = msg->pose;
	m_last_pose_time = ros::Time::now();
	m_distance_to_target = map_tools::distance( m_robot_pose.position, m_target_point );
	m_new_target = false;
	performNextMove();
}

void HomerNavigationNode::calcMaxMoveDist()
{
	m_max_move_distance = std::min (m_max_move_sick, std::min(m_max_move_down, m_max_move_depth));
	if(m_max_move_distance <= m_collision_distance && std::fabs(m_act_speed) > 0.1 && m_waypoints.size() > 1 ||
			m_max_move_distance <= m_collision_distance_near_target && std::fabs(m_act_speed) > 0.1 && m_waypoints.size() == 1 ||
			m_max_move_distance <= 0.1 )
	{
		handleCollision();
	}
}
void HomerNavigationNode::maxDepthMoveDistanceCallback(const std_msgs::Float32::ConstPtr& msg)
{
	m_max_move_depth = msg->data;
	calcMaxMoveDist();
}

void HomerNavigationNode::laserDataCallback(const sensor_msgs::LaserScan::ConstPtr& msg)
{
	m_last_laser_time = ros::Time::now();
	m_max_move_sick = map_tools::get_max_move_distance ( map_tools::laser_ranges_to_points(msg->ranges, msg->angle_min, msg->angle_increment, msg->range_min, msg->range_max, m_transform_listener, msg->header.frame_id, "/base_link"), m_min_x, m_min_y); 
	calcMaxMoveDist();
}


void HomerNavigationNode::downlaserDataCallback(const sensor_msgs::LaserScan::ConstPtr& msg)
{
	m_max_move_down = map_tools::get_max_move_distance ( map_tools::laser_ranges_to_points(msg->ranges, msg->angle_min, msg->angle_increment, msg->range_min, msg->range_max, m_transform_listener, msg->header.frame_id, "/base_link"), m_min_x, m_min_y); 
	calcMaxMoveDist();
}

void HomerNavigationNode::startNavigationCallback(const homer_mapnav_msgs::StartNavigation::ConstPtr& msg)
{
	m_avoided_collision  = false;
	m_target_point 		 = msg->goal.position;
	m_target_orientation = tf::getYaw(msg->goal.orientation);
	m_desired_distance 	 = msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
	m_skip_final_turn 	 = msg->skip_final_turn;
	m_fast_path_planning = msg->fast_planning;
	m_new_target 		 = true;
	m_target_name        = "";

	ROS_INFO_STREAM("Navigating to target " << m_target_point.x << ", " << m_target_point.y
			<< "\nTarget orientation: " << m_target_orientation
			<< "Desired distance to target: " << m_desired_distance);

	m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
}

void HomerNavigationNode::moveBaseSimpleGoalCallback(const geometry_msgs::PoseStamped::ConstPtr& msg)
{
	if(msg->header.frame_id != "map")
	{
		tf::StampedTransform transform;
		if(m_transform_listener.waitForTransform("map", msg->header.frame_id, msg->header.stamp, ros::Duration(1.0)))
		{
			try
			{
				m_transform_listener.lookupTransform("map", msg->header.frame_id, msg->header.stamp, transform);
				tf::Vector3 targetPos(msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
				targetPos = transform * targetPos;
				m_target_point.x = targetPos.getX();
				m_target_point.y = targetPos.getY();
				m_target_orientation 	= tf::getYaw(transform*tf::Quaternion(msg->pose.orientation.x, msg->pose.orientation.y, msg->pose.orientation.z, msg->pose.orientation.w));
			}
			catch(tf::TransformException ex)
			{
				ROS_ERROR_STREAM(ex.what());
				return;
			}
		}
		else
		{
			try
			{
				m_transform_listener.lookupTransform("map", msg->header.frame_id, ros::Time(0), transform);
				tf::Vector3 targetPos(msg->pose.position.x, msg->pose.position.y, msg->pose.position.z);
				targetPos = transform * targetPos;
				m_target_point.x = targetPos.getX();
				m_target_point.y = targetPos.getY();
				m_target_orientation 	= tf::getYaw(transform*tf::Quaternion(msg->pose.orientation.x, msg->pose.orientation.y, msg->pose.orientation.z, msg->pose.orientation.w));
			}
			catch(tf::TransformException ex)
			{
				ROS_ERROR_STREAM(ex.what());
				return;
			}
		}
	}
	else
	{
		m_target_point 			= msg->pose.position;
		m_target_orientation 	= tf::getYaw(msg->pose.orientation);
	}
	m_avoided_collision 	= false;
	m_desired_distance 		= 0.1;
	m_skip_final_turn 		= false;
	m_fast_path_planning 	= false;
	m_new_target 		    = true;

	ROS_INFO_STREAM("Navigating to target via Move Base Simple x: " << m_target_point.x << ", y: " << m_target_point.y
			<< "\nTarget orientation: " << m_target_orientation
			<< " Desired distance to target: " << m_desired_distance
			<< "\nframe_id: " << msg->header.frame_id);

	m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
}

void HomerNavigationNode::navigateToPOICallback(const homer_mapnav_msgs::NavigateToPOI::ConstPtr &msg)
{
	homer_mapnav_msgs::GetPointsOfInterest srv;
	m_get_POIs_client.call(srv);
	std::vector<homer_mapnav_msgs::PointOfInterest>::iterator it;
	for(it = srv.response.poi_list.pois.begin(); it != srv.response.poi_list.pois.end(); ++it)
	{
		if(it->name == msg->poi_name)
		{
			m_avoided_collision  = false;	
			m_target_point 		 = it->pose.position;
			m_target_orientation = tf::getYaw(it->pose.orientation);
			m_desired_distance 	 = msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
			m_skip_final_turn 	 = msg->skip_final_turn;
			m_fast_path_planning = false;
			m_new_target 		 = true;
			m_target_name        = msg->poi_name;

			ROS_INFO_STREAM("Navigating to target " << m_target_point.x << ", " << m_target_point.y
					<< "\nTarget orientation: " << m_target_orientation
					<< "Desired distance to target: " << m_desired_distance);

			m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
			return;
		}
	}

	ROS_ERROR_STREAM("No point of interest with name '" << msg->poi_name << "' found in current poi list");
	sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::UNKNOWN);
}

void HomerNavigationNode::stopNavigationCallback(const std_msgs::Empty::ConstPtr& msg)
{
	ROS_INFO_STREAM("Stopping navigation." );
	m_MainMachine.setState( IDLE );
	m_avoided_collision = false;
	stopRobot();

	m_waypoints.clear();
	nav_msgs::Path empty_path_msg;
	empty_path_msg.poses = m_waypoints;
	m_path_pub.publish(empty_path_msg);
}

void HomerNavigationNode::unknownThresholdCallback(const std_msgs::Int8::ConstPtr &msg)
{
	m_explorer->setUnknownThreshold(static_cast<int>(msg->data));
}

int main(int argc, char **argv)
{
	ros::init(argc, argv, "homer_navigation");

	HomerNavigationNode node;

	ros::Rate rate(50);

	while(ros::ok())
	{
		ros::spinOnce();
		node.idleProcess();
		rate.sleep();
	}

	return 0;
}