#include <vector>
#include <iostream>
#include <sstream>
#include <cmath>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <ros/package.h>

#include "homer_navigation/homer_navigation_node.h"

//messages
#include <std_msgs/Empty.h>
#include <homer_mapnav_msgs/TargetUnreachable.h>
#include <homer_mapnav_msgs/GetPointsOfInterest.h>
#include <homer_mapnav_msgs/ModifyMap.h>
#include <nav_msgs/Path.h>
#include <std_msgs/Int8.h>
#include <tools/tools.h>
#include <tools/loadRosConfig.h>

//nav_libs
#include "Explorer/Explorer.h"
#include "SpeedControl/SpeedControl.h"

using namespace std;


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

	srand(time(NULL));// Initialize random numbers

    //subscribers
    map_sub_ 				= nh.subscribe<nav_msgs::OccupancyGrid>("/map", 1, &HomerNavigationNode::mapCallback, this);
    pose_sub_				= nh.subscribe<geometry_msgs::PoseStamped>("/pose", 1, &HomerNavigationNode::poseCallback, this);
    laser_data_sub_			= nh.subscribe<sensor_msgs::LaserScan>("/scan", 1, &HomerNavigationNode::laserDataCallback, this);
    laser_back_data_sub_    = nh.subscribe<sensor_msgs::LaserScan>("/back_scan", 1, &HomerNavigationNode::backLaserCallback, this);
    start_navigation_sub_ 	= nh.subscribe<homer_mapnav_msgs::StartNavigation>("/homer_navigation/start_navigation", 1, &HomerNavigationNode::startNavigationCallback, this);
    stop_navigation_sub_ 	= nh.subscribe<homer_mapnav_msgs::StopNavigation>("/homer_navigation/stop_navigation", 1, &HomerNavigationNode::stopNavigationCallback, this);
    navigate_to_poi_sub_	= nh.subscribe<homer_mapnav_msgs::NavigateToPOI>("/homer_navigation/navigate_to_POI", 1, &HomerNavigationNode::navigateToPOICallback, this);
    unknown_threshold_sub_ 	= nh.subscribe<std_msgs::Int8>("/homer_navigation/unknown_threshold", 1, &HomerNavigationNode::unknownThresholdCallback, this);
    refresh_param_sub_ 		= nh.subscribe<std_msgs::Empty>("/homer_navigation/refresh_params", 1, &HomerNavigationNode::refreshParamsCallback, this);

	cmd_vel_pub_			= nh.advertise<geometry_msgs::Twist>("/robot_platform/cmd_vel", 1);
    target_reached_pub_ 	= nh.advertise<std_msgs::Empty>("/homer_navigation/target_reached", 1);
    target_unreachable_pub_ = nh.advertise<homer_mapnav_msgs::TargetUnreachable>("/homer_navigation/target_unreachable", 1);
    path_pub_ 				= nh.advertise<nav_msgs::Path>("/homer_navigation/path", 1);
    get_POIs_client_ 		= nh.serviceClient<homer_mapnav_msgs::GetPointsOfInterest>("/map_manager/get_pois");


	m_move_base_simple_goal_sub_ = nh.subscribe<geometry_msgs::PoseStamped>("/move_base_simple/goal", 1, &HomerNavigationNode::moveBaseSimpleGoalCallback, this); // for RVIZ usage



    m_MapTypeMachine.setName( "HomerNavigation Node" );
	ADD_MACHINE_STATE( m_MapTypeMachine, SLAM_MAP );
	ADD_MACHINE_STATE( m_MapTypeMachine, NAVIGATION_MAP );
	m_MapTypeMachine.setState( SLAM_MAP );

    m_MainMachine.setName( "HomerNavigation Main" );
	ADD_MACHINE_STATE( m_MainMachine, IDLE );
	ADD_MACHINE_STATE( m_MainMachine, AWAITING_EXPLORATION_MAP );
	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 );
    ADD_MACHINE_STATE( m_MainMachine, TARGET_REACHED );
	
	m_avoided_collision = false; 
	 
    init();
    m_act_speed = 0;
    m_act_angle = 0; 
}

void HomerNavigationNode::loadParameters()
{
    SpeedControl::loadDimensions();
    
	//Explorer constructor 
    loadConfigValue("/homer_navigation/safe_path_weight", 				m_SafePathWeight);
    loadConfigValue("/homer_mapping/resolution", 						resolution_);
    loadConfigValue("/homer_navigation/allowed_obstacle_distance/min", 	m_AllowedObstacleDistance.first);
    loadConfigValue("/homer_navigation/allowed_obstacle_distance/max", 	m_AllowedObstacleDistance.second);
    m_AllowedObstacleDistance.first /= resolution_;
    m_AllowedObstacleDistance.second/= resolution_;
    loadConfigValue("/homer_navigation/safe_obstacle_distance/min", 	m_SafeObstacleDistance.first);
    loadConfigValue("/homer_navigation/safe_obstacle_distance/max", 	m_SafeObstacleDistance.second);
    m_SafeObstacleDistance.first /= resolution_;
    m_SafeObstacleDistance.second /= resolution_;
    loadConfigValue("/homer_navigation/frontier_safeness_factor", 		m_FrontierSafenessFactor);
	
	//Explorer     
    loadConfigValue("/homer_navigation/waypoint_sampling_threshold", 	waypoint_sampling_threshold_);    
    
    //check path 
    loadConfigValue("/homer_navigation/check_path", 					check_path_);
    loadConfigValue("/homer_navigation/check_path_max_errors", 			check_path_max_errors_);
    loadConfigValue("/homer_navigation/check_path_max_distance", 		check_path_max_distance_);
    
    //collision 
    loadConfigValue("/homer_navigation/collision_distance", 			collision_distance_);
    loadConfigValue("/homer_navigation/collision_distance_near_target", collision_distance_near_target_);

	//moveRobot config values
	loadConfigValue("/homer_navigation/backward_distance", 							 backward_distance_);
	loadConfigValue("/homer_navigation/max_trans_vel", 								 m_MaxTransVel);
    loadConfigValue("/homer_navigation/max_rot_vel", 								 m_MaxRotVel);
    loadConfigValue("/homer_navigation/speed_control/last_speedfactor_count", 		 m_SpeedFactorMeanFilterSize);
    loadConfigValue("/homer_navigation/speed_control/min_move_speedfactor", 		 m_MinMoveSpeedFactor);
    loadConfigValue("/homer_navigation/speed_control/min_turn_speedfactor_moving", 	 m_MinTurnSpeedFactorMoving);
    loadConfigValue("/homer_navigation/speed_control/min_turn_speedfactor_standing", m_MinTurnSpeedFactorStanding);
    loadConfigValue("/homer_navigation/turn_threshold_angle", 						 turn_threshold_angle_);

	//cmd_vel config values
    loadConfigValue("/homer_navigation/use_cmd_vel", 					m_use_cmd_vel_);
    loadConfigValue("/homer_navigation/min_turn_angle", 				m_min_turn_angle);
    loadConfigValue("/homer_navigation/max_turn_speed", 				m_max_turn_speed);
    loadConfigValue("/homer_navigation/max_move_speed", 				m_max_move_speed);
    loadConfigValue("/homer_navigation/max_drive_angle",				m_max_drive_angle);

}


void HomerNavigationNode::init()
{
    last_map_timestamp_ = ros::Time(0);

	loadParameters();

    robot_pose_.position.x = 0.0;
    robot_pose_.position.y = 0.0;
    robot_pose_.position.z = 0.0;
    robot_pose_.orientation = tf::createQuaternionMsgFromYaw(0.0);

    last_laser_time_ = ros::Time::now();

    explorer_ = new Explorer ( m_AllowedObstacleDistance.first, m_AllowedObstacleDistance.second,
								m_SafeObstacleDistance.first, m_SafeObstacleDistance.second,
								m_SafePathWeight, m_FrontierSafenessFactor );

    m_MainMachine.setState ( IDLE );
}

HomerNavigationNode::~HomerNavigationNode()
{
    if ( explorer_ )
	{
        delete explorer_;
	}
    if(last_map_data_)
    {
        delete last_map_data_;
    }
}

void HomerNavigationNode::sendStopRobot()
{
    m_act_speed = 0;
    m_act_angle = 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;
	cmd_vel_pub_.publish(cmd_vel_msg);
}

void HomerNavigationNode::idleProcess()
{
	if ( m_MainMachine.state() == FOLLOWING_PATH )
	{
	    if ( (ros::Time::now() - last_laser_time_) > ros::Duration(2.0) )
	    {
	        ROS_ERROR_STREAM( "Laser data timeout!\n");
			sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::LASER_TIMEOUT);
		}
	}
}


void HomerNavigationNode::calculatePath()
{
    float desired_distance_2 = desired_distance_ - 0.05;
    if ( desired_distance_2 < resolution_ )
	{
      desired_distance_2 = resolution_;
	}
    explorer_->setStart ( map_tools::toMapCoords( robot_pose_.position,  origin_, resolution_) );

    if ( map_tools::distance( map_tools::fromMapCoords( target_approx_, origin_, resolution_ ), target_point_ ) <= desired_distance_ )
	{
      ROS_INFO_STREAM( "There is a way to the target circle. Planning direct path." );
      explorer_->setTarget ( map_tools::toMapCoords( target_point_, origin_, resolution_ ), desired_distance_2 / resolution_ );
	}
	else
	{
      ROS_INFO_STREAM( "The target area is not reachable. Trying to go as close as possible." );
      explorer_->setTarget ( target_approx_, 0 );
	}

	bool success;
    pixel_path_ = explorer_->getPath( success );
    if ( !success )
	{
        ROS_WARN_STREAM("No path found for navigation, reporting as unreachable." );
        sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
	}
	else
	{
		ROS_INFO_STREAM("homer_navigation::calculatePath - Path Size: " << pixel_path_.size());
		std::vector<Eigen::Vector2i> waypoint_pixels = explorer_->sampleWaypointsFromPath ( pixel_path_, waypoint_sampling_threshold_ );
		waypoints_.clear();
		for(std::vector<Eigen::Vector2i>::iterator it = waypoint_pixels.begin(); it != waypoint_pixels.end(); ++it)
		{
		    geometry_msgs::PoseStamped poseStamped;
		    poseStamped.pose.position = map_tools::fromMapCoords(*it, origin_, 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;
		    waypoints_.push_back(poseStamped);
		}

		ROS_INFO_STREAM("homer_navigation::calculatePath - Path Size: " << waypoints_.size());

		sendPathData();
		//make sure no timeout occurs after long calculation time
		last_laser_time_ = ros::Time::now();
	}
}


void HomerNavigationNode::startExploration()
{
    ROS_INFO_STREAM( "Starting exploration." );
/*
	m_Explorer->resetExploration();
	m_Explorer->setOccupancyMap ( gridSize, gridSize, mapData, m_ExploredRegion );
	m_Explorer->setFrontierSafenessFactor( m_FrontierSafenessFactor );
	m_Explorer->setStart ( m_CoordinateConverter->worldToMap ( m_RobotPose ) );

	bool success;
	m_PixelPath= m_Explorer->getExplorationTransformPath( success );

	if ( success ) {
		m_Target = m_CoordinateConverter->mapToWorld( m_PixelPath.back() );
		m_TargetApprox = m_PixelPath.back();
	}

	vector<Pixel> waypointPixels = m_Explorer->sampleWaypointsFromPath ( m_PixelPath, m_WaypointSamplingThreshold );
	m_Waypoints = m_CoordinateConverter->mapToWorld ( waypointPixels );
*/

    if ( pixel_path_.size() == 0 )
	{
        ROS_WARN_STREAM("No path found for exploration, sending NoTargetM." );
        //sendMessage ( new NoTargetM() );
		m_MainMachine.setState( IDLE );
	}
	else
	{
		m_MainMachine.setState ( FOLLOWING_PATH );
		sendPathData();
	}
  //make sure no timeout occurs after long calculation time
    last_laser_time_ = ros::Time::now();
}


void HomerNavigationNode::startNavigation()
{
    ROS_INFO_STREAM("Starting navigation to " << target_point_.x << "," << target_point_.y);

    if ( distanceTo(target_point_) < desired_distance_ )
	{
        ROS_INFO_STREAM( "Will not (re-)plan path: Target position already reached." );
		targetPositionReached();
		return;
	}
    ROS_INFO_STREAM( "Distance to target still too large (" << distanceTo( target_point_ ) << "m; requested: " << desired_distance_ << "m)" );

    ROS_DEBUG_STREAM("homer_navigation_node::startNavigation: target point " << target_point_.x << " " << target_point_.y);
    Eigen::Vector2i new_target = map_tools::toMapCoords(target_point_, origin_, resolution_);
    ROS_DEBUG_STREAM("homer_navigation_node::startNavigation: target cell " << new_target);
    Eigen::Vector2i new_target_approx;

	switch ( m_MapTypeMachine.state() )
	{
		case SLAM_MAP:
        {
            ROS_INFO_STREAM( "Resetting occupancy map." );

            if(fast_path_planning_)
            {
                maskMap();
            }
            explorer_->setOccupancyMap(width_, width_, origin_, &(*last_map_data_)[0]);
            explorer_->setStart(map_tools::toMapCoords(robot_pose_.position, origin_, resolution_));
            new_target_approx = explorer_->getNearestAccessibleTarget(new_target);
			break;
        }
		case NAVIGATION_MAP:
            ROS_INFO_STREAM( "Updating obstacles in externally assigned navigation map." );
            explorer_->updateObstacles( width_, width_, origin_, &(*last_map_data_)[0] );
            explorer_->setStart(map_tools::toMapCoords(robot_pose_.position, origin_, resolution_));
            new_target_approx = new_target;
			break;
	}

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

    bool new_approx_is_better = ( map_tools::distance( robot_pose_.position, target_point_ ) - map_tools::distance( new_target_approx_world, target_point_ ) ) > 0.2;
    bool new_approx_reaches_target = ( map_tools::distance(new_target_approx_world, target_point_ ) < desired_distance_ );
    m_path_reaches_target = new_approx_reaches_target;
    if ( !new_approx_is_better && !new_approx_reaches_target )
	{
      ROS_WARN_STREAM( "No better way to target found, turning and then reporting as unreachable."
          <<     endl << "Distance to target: " << distanceTo( target_point_ ) << "m; requested: " << desired_distance_ << "m" );
	  m_MainMachine.setState( FINAL_TURN );
	}
	else
	{
      target_approx_ = new_target_approx;
      m_MainMachine.setState ( FOLLOWING_PATH );
      calculatePath();
	}
  }
  
void HomerNavigationNode::sendPathData()
{
    geometry_msgs::PoseStamped pose_stamped;
    pose_stamped.pose = robot_pose_;
    pose_stamped.header.frame_id = "/map";
	pose_stamped.header.stamp = ros::Time::now();
	
    nav_msgs::Path msg;
    msg.poses = waypoints_;
    if(waypoints_.size() > 0)
        msg.poses.insert(msg.poses.begin(), pose_stamped);
    path_pub_.publish(msg);
 }

void HomerNavigationNode::sendTargetReachedMsg() {
	sendStopRobot();
    m_MainMachine.setState( IDLE );
    std_msgs::Empty reached_msg;
    target_reached_pub_.publish(reached_msg);
    waypoints_.clear();
    nav_msgs::Path empty_path_msg;
    empty_path_msg.poses = waypoints_;
    path_pub_.publish(empty_path_msg);
    ROS_INFO_STREAM("TargetReachedMsg");
}

void HomerNavigationNode::sendTargetUnreachableMsg( int8_t reason ) {
	sendStopRobot();
	m_MainMachine.setState( IDLE );
    homer_mapnav_msgs::TargetUnreachable unreachable_msg;
    unreachable_msg.reason = reason;
    target_unreachable_pub_.publish(unreachable_msg);
    waypoints_.clear();
    nav_msgs::Path empty_path_msg;
    empty_path_msg.poses = waypoints_;
    path_pub_.publish(empty_path_msg);
    ROS_INFO_STREAM("TargetUnreachableMsg");
}

void HomerNavigationNode::targetPositionReached()
{
  //we're as close s we can get, target reached
    ROS_INFO_STREAM( "Target position reached. Distance to target: " << distanceTo( target_point_ ) << "m. Desired distance:" << desired_distance_ << "m" );
	sendStopRobot();
   // usleep( 30000 );
    waypoints_.clear();
    sendPathData();
	m_MainMachine.setState( FINAL_TURN );
    ROS_INFO_STREAM("Turning to look-at point");
}


void HomerNavigationNode::checkPath()
{
    invalid_path_count_=0;
    for ( unsigned i=0; i<pixel_path_.size(); i++ )
	{
        geometry_msgs::Point p = map_tools::fromMapCoords(pixel_path_.at(i), origin_, resolution_);
        if ( distanceTo(p) > check_path_max_distance_ )
		{
			continue;
		}
        if (map_tools::findValue( last_map_data_, width_, height_, pixel_path_[i].x(), pixel_path_[i].y(), 90, m_AllowedObstacleDistance.first ) )
		{
            invalid_path_count_++;
            ROS_WARN_STREAM("Obstacle detected in current path " << invalid_path_count_ << " of " << check_path_max_errors_ << " times.");

            if ( invalid_path_count_ >= check_path_max_errors_ )
            {
                ROS_WARN_STREAM( "Replanning path." );
                currentPathFinished();
			}
			return;
		}
	}
}

void HomerNavigationNode::handleCollision ()
{
	if ( m_MainMachine.state()== FOLLOWING_PATH )
	{
        sendStopRobot();
        if( distanceTo( target_point_) < collision_distance_near_target_ )
        {
            ROS_INFO_STREAM("Collision detected near target. Switch to final turn.");
            targetPositionReached();
        }
        else
        {
            m_MainMachine.setState( AVOIDING_COLLISION );
            ROS_WARN_STREAM( "Collision detected while following path!" );
        }
    }
}

float HomerNavigationNode::calcSpeedFactor()
{
    float speedFactor = SpeedControl::getSpeedFactor(laser_points_, m_MinMoveSpeedFactor, 1.0 );

	m_LastSpeedFactors.push_back( speedFactor );
	if ( m_LastSpeedFactors.size() > m_SpeedFactorMeanFilterSize ) { m_LastSpeedFactors.pop_front(); }

    float speedFactorMean = mean( m_LastSpeedFactors );

	return speedFactorMean;
}


void HomerNavigationNode::performNextMove()
{
    float maxMoveDistance = SpeedControl::getMaxMoveDistance ( laser_points_ );
	float speedFactor = calcSpeedFactor();

	switch ( m_MainMachine.state() )
	{
		case FOLLOWING_PATH:
		{
            if ( distanceTo( target_point_ ) < desired_distance_ )
			{
                ROS_INFO_STREAM( "Desired distance to target was reached." );
				targetPositionReached();
				return;
            }

			float waypointRadiusLaser = 0.125 * maxMoveDistance;

            Eigen::Vector2i waypointPixel = map_tools::toMapCoords(waypoints_[0].pose.position, origin_, resolution_);
            float obstacleDistanceMap = explorer_->getObstacleTransform()->getValue( waypointPixel.x(), waypointPixel.y() );
            float waypointRadiusMap = 0.125 * obstacleDistanceMap * resolution_;

			float waypointRadius = waypointRadiusLaser + waypointRadiusMap;
            if ( ( waypointRadius < resolution_ ) || ( waypoints_.size() == 1 ) )
			{
                waypointRadius = resolution_;
			}

            //if we have accidentaly skipped waypoints, recalculate path
			float minDistance=FLT_MAX;
			unsigned nearestWaypoint=0;
            for ( unsigned i=0; i<waypoints_.size(); i++ )
			{
                if ( distanceTo( waypoints_[i].pose.position ) < minDistance ) {
					nearestWaypoint = i;
                    minDistance = distanceTo( waypoints_[i].pose.position );
				}
			}
			if ( nearestWaypoint != 0 )
			{
                ROS_WARN_STREAM("Waypoints skipped. Recalculating path!");
				calculatePath();
				if ( m_MainMachine.state() != FOLLOWING_PATH ) { break; }
			}

            ROS_DEBUG_STREAM("NextMove DEBUG VALUES: maxMoveDistance="<< maxMoveDistance <<"\n"
                            << "\tWaypointRadiusLaser=" << waypointRadiusLaser << "\n\t"
                            << "obstacleDistanceMap=" << obstacleDistanceMap << "\n\t"
                            << "");

            //check if current waypoint has been reached
            if ( (waypoints_.size() != 0) && ( distanceTo( waypoints_[0].pose.position ) < waypointRadius ) )
            {
                waypoints_.erase ( waypoints_.begin() );
                ROS_DEBUG_STREAM("homer_navigation::performNextMove(): Current waypoint has been reached! Still " << waypoints_.size() << " to reach!");
            }

			sendPathData();

			ROS_ERROR_STREAM("waypointRadius: " << waypointRadius);
            //last wayoint reached
            if ( waypoints_.size() == 0 )
			{
                ROS_INFO_STREAM("Last waypoint reached");
				currentPathFinished();
				return;
            }

            geometry_msgs::Point currentWaypoint = waypoints_[0].pose.position;

			double distanceToWaypoint = distanceTo ( currentWaypoint );
			double angleToWaypoint = angleToPointDeg ( currentWaypoint );

            ROS_DEBUG_STREAM("homer_navigation::performNextMove(): Distance to waypoint: "<<distanceToWaypoint<<" Angle to waypoint: "<< (angleToWaypoint) << " Waypoint: " << currentWaypoint << "Robot Pose: " << robot_pose_.position << " Robot orientation " << tf::getYaw(robot_pose_.orientation)) ;

			ostringstream stream;
			stream.precision(2);

			if (!m_use_cmd_vel_) 
			{
				ROS_ERROR_STREAM("Not supported");
			}
				//move to next waypoint if heading in right direction
			else // else use cmd_vel
			{
				if (angleToWaypoint < -180)
				{
					angleToWaypoint += 360;
				}	

				//linear speed calculation
				double speed = distanceToWaypoint; 
				if (speed < 0 )
				{
					speed = max(speed,-m_max_move_speed);
				}
				else
				{
					speed = min(speed,m_max_move_speed);
				}
				if(m_avoided_collision)
				{
					if( std::abs(angleToWaypoint) < 10)
					{
						m_avoided_collision = false;
					}
					else
					{
						speed = 0;
					}
				}
				//linear speed calculation end
				//angular speed calculation
				double angle = angleToWaypoint*3.14/180.0;
				if (abs(angle) < m_min_turn_angle)
				{
					angle = 0.0;
				}
				else
				{
					if (abs(angle) > m_max_drive_angle)
					{
						speed = 0.0;
					}
					if (angle < 0 )
					{
						angle = max(angle,-m_max_turn_speed);
					}
					else
					{
						angle = min(angle,m_max_turn_speed);
					}
					if (distanceToWaypoint < 1.0)
					{
					angle *= distanceToWaypoint;
					}
					// min speed for angles because under 0.35 the machine is really really slow
					if (speed < 0.07)
					{
						if ( angle < 0 ) 
						{
							angle = min(angle,-0.45);
						}
						else
						{
							angle = max(angle,0.45);
						}
					}
				}
				//angular speed calculation end			

				m_act_speed = speed;
    			m_act_angle = angle; 
				geometry_msgs::Twist cmd_vel_msg;
				cmd_vel_msg.linear.x = speed;			
				cmd_vel_msg.angular.z = angle;
				cmd_vel_pub_.publish(cmd_vel_msg);

				stream << "Driving & turning" << endl;
				stream << "linear: " << speed << " angular: " << angle << endl;
				stream << "distanceToWaypoint:" << distanceToWaypoint << "angleToWaypoint: " << angleToWaypoint << endl;
			}
			ROS_INFO_STREAM( stream.str() );
			break;
		}

		case AVOIDING_COLLISION:
		{
            if( distanceTo( target_point_) < collision_distance_near_target_ )
            {
                ROS_INFO_STREAM("Collision detected near target. Switch to final turn.");
                targetPositionReached();
            } 
			else if ( maxMoveDistance <= collision_distance_ )
			{
				ostringstream stream;
                stream << "Maximum driving distance too short (" << maxMoveDistance << "m)! Moving back.";
                ROS_WARN_STREAM( stream.str() );
				if (!m_use_cmd_vel_)
				{
					ROS_ERROR_STREAM("Not supported");
				}
				else // cmd_vel
				{
                    geometry_msgs::Twist cmd_vel_msg;
                    if(HomerNavigationNode::obstacleBackwardDistance() > 0.4)
                    {
                        cmd_vel_msg.linear.x = -0.3;
                    }
                    else
                    {
                        cmd_vel_msg.angular.z = -0.45;
                    }
                    cmd_vel_pub_.publish(cmd_vel_msg);
				}
			} 
			else
			{
				m_avoided_collision = true;
                ROS_WARN_STREAM( "Collision avoided. Updating path." );
                currentPathFinished();
			}
			break;
		}
		case FINAL_TURN:
		{
			if ( 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);
        		}
				break;
			}

			double turnAngle = minTurnAngle( tf::getYaw(robot_pose_.orientation), target_orientation_ );
			ROS_INFO_STREAM("homer_navigation::PerformNextMove:: Final Turn. Robot orientation: " << rad2Deg(tf::getYaw(robot_pose_.orientation)) << ". Target orientation: " << rad2Deg(target_orientation_) );
			ROS_DEBUG_STREAM ( "homer_navigation::PerformNextMove:: turnAngle: " << rad2Deg(turnAngle));
			if (m_use_cmd_vel_ ) // final turn with cmd_vel
			{
				if (turnAngle< 0 )
				{
					turnAngle= max(turnAngle,-m_max_turn_speed);
				}
				else
				{
					turnAngle = min(turnAngle,m_max_turn_speed);
				}
				
				if (abs(turnAngle) < m_min_turn_angle) 
				{
					ROS_INFO_STREAM(":::::::TARGET REACHED BECAUSE lower "<<m_min_turn_angle);
					if( m_path_reaches_target	)
		            {
		    			sendTargetReachedMsg();
		    		}
		    		else
		    		{
		    			sendTargetUnreachableMsg(homer_mapnav_msgs::TargetUnreachable::NO_PATH_FOUND);
		    		}
				}
				else
				{
					geometry_msgs::Twist cmd_vel_msg;
					cmd_vel_msg.angular.z = turnAngle;
					cmd_vel_pub_.publish(cmd_vel_msg);				
				}
			}
			break;
		}

		case AWAITING_EXPLORATION_MAP:
		case AWAITING_PATHPLANNING_MAP:
        {
      //make sure that the robot doesn't move
            ROS_INFO_STREAM("Awaiting pathplanning map");
			sendStopRobot();
			break;
        }
        case IDLE:
			break;
    }
}

void HomerNavigationNode::currentPathFinished() // also used for replanning
{
    ROS_INFO_STREAM( "Current path was finished, initiating recalculation.");
	waypoints_.clear();
	sendStopRobot();
	m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );

}

// returns angle to target point in degrees(!)
int HomerNavigationNode::angleToPointDeg ( geometry_msgs::Point target )
{
    double cx = robot_pose_.position.x;
    double cy = robot_pose_.position.y;
    int targetAngle = rad2Deg( atan2 ( target.y - cy, target.x - cx ) );
    int currentAngle = rad2Deg( tf::getYaw(robot_pose_.orientation) );

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


double HomerNavigationNode::distanceTo ( geometry_msgs::Point target )
{
    double cx = robot_pose_.position.x;
    double cy = robot_pose_.position.y;
    double distance_to_target_2 = ( cx - target.x ) * ( cx - target.x ) + ( cy - target.y ) * ( cy - target.y );
    return sqrt ( distance_to_target_2 );
}

float HomerNavigationNode::obstacleBackwardDistance()
{
    float min_y = -0.27; 
    float max_y =  0.27;

    float back_distance = 4;

    for(float depth = -0.3 ; depth > -1.0; depth -= 0.01) 
    {
        for(float y = min_y ; y <= max_y ; y += 0.1) 
        {
            geometry_msgs::Point base_link_point;
            base_link_point.x = depth;
            base_link_point.y = y;
            geometry_msgs::Point map_point = map_tools::transformPoint(base_link_point, transform_listener_ ,"/base_link", "/map");
            int i = map_tools::map_index(map_point, origin_ ,width_,resolution_);
            if(last_map_data_->at(i) > 90)
            {
              if(back_distance > HomerNavigationNode::distanceTo(map_point))
              {
                  back_distance = HomerNavigationNode::distanceTo(map_point);
              }
            }
        }
        if(back_distance != 4)
        {
            break;
        }
    }
    ROS_ERROR_STREAM("Back Distance: "<< back_distance);
    return back_distance;
}

void HomerNavigationNode::actualizeStatusInfo()
{
	ostringstream stream;
	stream << m_MapTypeMachine.stateString() << '\n'
           << m_MainMachine.stateString();
    ROS_DEBUG_STREAM( stream.str() );
}

void HomerNavigationNode::maskMap()
{
    //generate bounding box
    ROS_INFO_STREAM("Calculating Bounding box for fast planning");
    Eigen::Vector2i pose_pixel =  map_tools::toMapCoords( robot_pose_.position,  origin_, resolution_);
    Eigen::Vector2i target_pixel =  map_tools::toMapCoords( target_point_,  origin_, 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(), origin_, resolution_));
    ROS_INFO_STREAM("max in m: "<<map_tools::fromMapCoords(safe_planning_box.max(), origin_, resolution_));
    for(size_t x = 0; x < width_; x++)
  	{
        for(size_t y = 0; y < width_; y++)
        {
            if(!safe_planning_box.contains(Eigen::Vector2i(x, y)))
            {
                last_map_data_->at(y * 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;

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

template<class ContainerT>
double HomerNavigationNode::mean ( const ContainerT& values )
{
  typename ContainerT::const_iterator it;
  it = values.begin();
  double sum = 0;
  while ( it != values.end() )
  {
    sum += *it;
    it++;
  }
  return sum / double ( values.size() );
}

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(msg->header.stamp != last_map_timestamp_)
    {
        last_map_timestamp_ = msg->header.stamp;
        last_map_data_ = new std::vector<int8_t>(msg->data);
        origin_ = msg->info.origin;
        width_ = msg->info.width;
        height_ = msg->info.height;
        resolution_ = msg->info.resolution;
        
        Eigen::Vector2i map_point;
		if(m_laser_points_map.size() > 0)
		{
			for(int i = 0; i < m_laser_points_map.size(); i++)
			{
				geometry_msgs::Point& point = m_laser_points_map[i];
				map_point = map_tools::toMapCoords(point, origin_, resolution_);
				int k = map_point.y() * width_ + map_point.x();
				if(k < 0 || k > width_*height_)
				{
					continue;
				}
				last_map_data_->at(k) = homer_mapnav_msgs::ModifyMap::BLOCKED;
			}
		}
		if(m_back_laser.size() > 0 )
		{
			for(int i = 0; i < m_back_laser.size(); i++)
			{
				geometry_msgs::Point& point = m_back_laser[i];
				map_point = map_tools::toMapCoords(point, origin_, resolution_);
				int k = map_point.y() * width_ + map_point.x();
				if(k < 0 || k > width_*height_)
				{
					continue;
				}
				last_map_data_->at(k) = homer_mapnav_msgs::ModifyMap::BLOCKED;
			}
		}
		
        switch ( m_MainMachine.state() )
        {
            case AWAITING_EXPLORATION_MAP:
                if ( m_MapTypeMachine.state() ==  SLAM_MAP )
                {
                    startExploration();
                }
                break;

            case AWAITING_PATHPLANNING_MAP:
                startNavigation();
                break;
            case FOLLOWING_PATH:
            {
                if ( check_path_)
                {
                    checkPath();
                }
                
                break;
			}
            default:
                break;
        }
    }
}

void HomerNavigationNode::poseCallback(const geometry_msgs::PoseStamped::ConstPtr& msg)
{
    robot_pose_ = msg->pose;
	performNextMove();
}

void HomerNavigationNode::laserDataCallback(const sensor_msgs::LaserScan::ConstPtr& msg)
{
    float frontal_obstacle_distance;
    bool collision_detected;
    
    last_laser_time_ = ros::Time::now();
    laser_points_ =  map_tools::laser_ranges_to_points(msg->ranges, msg->angle_min, msg->angle_increment, msg->range_min, msg->range_max, transform_listener_, msg->header.frame_id, "/base_link");                                       
    m_laser_points_map =  map_tools::laser_ranges_to_points( msg->ranges, msg->angle_min, msg->angle_increment, msg->range_min, msg->range_max, transform_listener_, msg->header.frame_id, "/map");                

    if(m_act_speed == 0) 
    {
    	return;
    }
    frontal_obstacle_distance = SpeedControl::getMaxMoveDistance ( laser_points_ );
    collision_detected = frontal_obstacle_distance <= collision_distance_;
    if(collision_detected)
    {
    	handleCollision();
    }
}

void HomerNavigationNode::backLaserCallback(const sensor_msgs::LaserScan::ConstPtr& msg)
{
	m_back_laser = map_tools::laser_ranges_to_points( msg->ranges, msg->angle_min, msg->angle_increment, msg->range_min, msg->range_max, transform_listener_, msg->header.frame_id, "/map");                
}

void HomerNavigationNode::startNavigationCallback(const homer_mapnav_msgs::StartNavigation::ConstPtr& msg)
{
    ROS_INFO_STREAM("Start navigating to (" << msg->goal.position.x << ", " << msg->goal.position.y << ")");
    if (m_MainMachine.state() != IDLE) {
        ROS_WARN_STREAM( "Aborting current operation and starting navigation.\n");
    }

    m_MapTypeMachine.setState(SLAM_MAP);

    target_point_ = msg->goal.position;
    target_orientation_ = tf::getYaw(msg->goal.orientation);
    desired_distance_ = msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
	skip_final_turn_ = msg->skip_final_turn;
    fast_path_planning_ = msg->fast_planning;

    m_LastSpeedFactors.clear();

    ROS_INFO_STREAM("Navigating to target " << target_point_.x << ", " << target_point_.y
                    << "\nTarget orientation: " << target_orientation_
                    << "Desired distance to target: " << desired_distance_);

    m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
}


void HomerNavigationNode::moveBaseSimpleGoalCallback(const geometry_msgs::PoseStamped::ConstPtr& msg)
{
	m_avoided_collision 	= false;
    target_point_ 			= msg->pose.position;
    target_orientation_ 	= tf::getYaw(msg->pose.orientation);
	desired_distance_ 		= 0.1;// msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
	skip_final_turn_ 		= false;
    fast_path_planning_ 	= false;

    ROS_INFO_STREAM("Navigating to target via Move Base Simple x: " << target_point_.x << ", y: " << target_point_.y
                    << "\nTarget orientation: " << target_orientation_
                    << "Desired distance to target: " << desired_distance_ );

    m_MainMachine.setState( AWAITING_PATHPLANNING_MAP );
}

void HomerNavigationNode::navigateToPOICallback(const homer_mapnav_msgs::NavigateToPOI::ConstPtr &msg)
{
	m_avoided_collision = false;
    std::string name = msg->poi_name;
    homer_mapnav_msgs::GetPointsOfInterest srv;
    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 == name)
        {
            ROS_INFO_STREAM("Start navigating to (" << it->pose.position.x << ", " << it->pose.position.y << ")");
            if (m_MainMachine.state() != IDLE) {
                ROS_WARN_STREAM( "Aborting current operation and starting navigation.\n");
            }

            m_MapTypeMachine.setState(SLAM_MAP);

			sendStopRobot();

            target_point_ = it->pose.position;
            target_orientation_ = tf::getYaw(it->pose.orientation);
            desired_distance_ = msg->distance_to_target < 0.1 ? 0.1 : msg->distance_to_target;
			skip_final_turn_ = msg->skip_final_turn;


            m_LastSpeedFactors.clear();

            ROS_INFO_STREAM("Navigating to target " << target_point_.x << ", " << target_point_.y
                            << "\nTarget orientation: " << target_orientation_
                            << "Desired distance to target: " << 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");
}

void HomerNavigationNode::stopNavigationCallback(const homer_mapnav_msgs::StopNavigation::ConstPtr& msg)
{
    ROS_INFO_STREAM("Stopping navigation." );
    // stop exploring
    m_MainMachine.setState( IDLE );
    m_avoided_collision = false;
	sendStopRobot();

    waypoints_.clear();
    nav_msgs::Path empty_path_msg;
    empty_path_msg.poses = waypoints_;
    path_pub_.publish(empty_path_msg);
}

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

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

  HomerNavigationNode node;
  
  ros::Rate rate(12);

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

  return 0;
}