#include "OccupancyMap.h"

#include "Math/Math.h"

#include <cmath>
#include <vector>
#include <fstream>
#include <sstream>
#include <QImage>

#include <Eigen/Geometry>

#include <ros/ros.h>
#include <tf/transform_listener.h>

#include "homer_mapnav_msgs/ModifyMap.h"
#include "tools/loadRosConfig.h"
#include "tools/tools.h"

//uncomment this to get extended information on the tracer
//#define TRACER_OUTPUT

using namespace std;

const float UNKNOWN_LIKELIHOOD = 0.3;

// Flags of current changes //
const char NO_CHANGE = 0;
const char OCCUPIED = 1;
const char FREE = 2;
//the safety border around occupied pixels which is left unchanged
const char SAFETY_BORDER = 3;
///////////////////////////////

//assumed laser measure count for loaded maps
const int LOADED_MEASURECOUNT = 10;


OccupancyMap::OccupancyMap()
{
  initMembers();
}

OccupancyMap::OccupancyMap(float *&occupancyProbability, geometry_msgs::Pose origin, float resolution, int pixelSize, Box2D<int> exploredRegion)
{
    initMembers();


    m_Origin = origin;
    m_Resolution = resolution;
    m_PixelSize = pixelSize;
    m_ByteSize = pixelSize * pixelSize;
    m_ExploredRegion = exploredRegion;
    m_ChangedRegion = exploredRegion;

    if ( m_OccupancyProbability )
    {
      delete[] m_OccupancyProbability;
    }
    m_OccupancyProbability = occupancyProbability;
    for(unsigned i = 0; i < m_ByteSize; i++)
    {
        if(m_OccupancyProbability[i] != 0.5)
        {
            m_MeasurementCount[i] = LOADED_MEASURECOUNT;
            m_OccupancyCount[i] = m_OccupancyProbability[i] * (float)LOADED_MEASURECOUNT;
        }
    }
}


OccupancyMap::OccupancyMap ( const OccupancyMap& occupancyMap )
{
  m_OccupancyProbability = 0;
  m_MeasurementCount = 0;
  m_OccupancyCount = 0;
  m_CurrentChanges = 0;
  m_InaccessibleCount = 0;
  m_HighSensitive = 0;
  m_LaserMaxRange = 0;
  m_LaserMinRange = 0;

  *this = occupancyMap;
}

OccupancyMap::~OccupancyMap()
{
  cleanUp();
}

void OccupancyMap::initMembers()
{
  float mapSize;
  loadConfigValue("/homer_mapping/size", mapSize);
  loadConfigValue("/homer_mapping/resolution", m_Resolution);

  //add one safety pixel
  m_PixelSize = mapSize / m_Resolution + 1;
  m_ByteSize = m_PixelSize * m_PixelSize;

  m_Origin.position.x = -m_PixelSize*m_Resolution/2;
  m_Origin.position.y = -m_PixelSize*m_Resolution/2;
  m_Origin.orientation.w = 1.0;
  m_Origin.orientation.x = 0.0;
  m_Origin.orientation.y = 0.0;
  m_Origin.orientation.z = 0.0;

  loadConfigValue("/homer_mapping/backside_checking", m_BacksideChecking);
  loadConfigValue("/homer_mapping/obstacle_borders", m_ObstacleBorders);
  loadConfigValue("/homer_mapping/measure_sampling_step", m_MeasureSamplingStep);
  loadConfigValue("/homer_mapping/laser_scanner/free_reading_distance", m_FreeReadingDistance);

  m_OccupancyProbability = new float[m_ByteSize];
  m_MeasurementCount = new unsigned short[m_ByteSize];
  m_OccupancyCount = new unsigned short[m_ByteSize];
  m_CurrentChanges = new unsigned char[m_ByteSize];
  m_InaccessibleCount = new unsigned char[m_ByteSize];
  m_HighSensitive = new unsigned short[m_ByteSize];
  for ( unsigned i=0; i<m_ByteSize; i++ )
  {
    m_OccupancyProbability[i]=UNKNOWN_LIKELIHOOD;
    m_OccupancyCount[i]=0;
    m_MeasurementCount[i]=0;
    m_CurrentChanges[i]=NO_CHANGE;
    m_InaccessibleCount[i]=0;
    m_HighSensitive[i] = 0;
  }

  m_ExploredRegion=Box2D<int> ( m_PixelSize/2.1, m_PixelSize/2.1, m_PixelSize/1.9, m_PixelSize/1.9 );
  maximizeChangedRegion();
}


OccupancyMap& OccupancyMap::operator= ( const OccupancyMap & occupancyMap )
{
  // free allocated memory
  cleanUp();

  m_Resolution = occupancyMap.m_Resolution;
  m_ExploredRegion =  occupancyMap.m_ExploredRegion;
  m_PixelSize = occupancyMap.m_PixelSize;
  m_ByteSize = occupancyMap.m_ByteSize;

  loadConfigValue("/homer_mapping/backside_checking", m_BacksideChecking);

  // re-allocate all arrays
  m_OccupancyProbability = new float[m_ByteSize];
  m_MeasurementCount = new unsigned short[m_ByteSize];
  m_OccupancyCount = new unsigned short[m_ByteSize];
  m_CurrentChanges = new unsigned char[m_ByteSize];
  m_InaccessibleCount = new unsigned char[m_ByteSize];
  m_HighSensitive = new unsigned short[m_ByteSize];

  // copy array data
  memcpy ( m_OccupancyProbability, occupancyMap.m_OccupancyProbability, m_ByteSize * sizeof ( *m_OccupancyProbability ) );
  memcpy ( m_MeasurementCount, occupancyMap.m_MeasurementCount, m_ByteSize * sizeof ( *m_MeasurementCount ) );
  memcpy ( m_OccupancyCount, occupancyMap.m_OccupancyCount, m_ByteSize * sizeof ( *m_OccupancyCount ) );
  memcpy ( m_CurrentChanges, occupancyMap.m_CurrentChanges, m_ByteSize * sizeof ( *m_CurrentChanges ) );
  memcpy ( m_InaccessibleCount, occupancyMap.m_InaccessibleCount, m_ByteSize * sizeof ( *m_InaccessibleCount ) );
  memcpy ( m_HighSensitive, occupancyMap.m_HighSensitive, m_ByteSize * sizeof ( *m_HighSensitive) );


  return *this;
}

int OccupancyMap::width() const
{
  return m_PixelSize;
}

int OccupancyMap::height() const
{
  return m_PixelSize;
}

float OccupancyMap::getOccupancyProbability ( Eigen::Vector2i p )
{
  unsigned offset = m_PixelSize * p.y() + p.x();
  if ( offset > unsigned ( m_ByteSize ) )
  {
    return UNKNOWN_LIKELIHOOD;
  }
  return m_OccupancyProbability[ offset ];
}

void OccupancyMap::resetHighSensitive()
{
	ROS_INFO_STREAM("High sensitive Areas reseted");
	m_reset_high = true;
}

void OccupancyMap::computeOccupancyProbabilities()
{
  for ( int y = m_ChangedRegion.minY(); y <= m_ChangedRegion.maxY(); y++ )
  {
    int yOffset = m_PixelSize * y;
    for ( int x = m_ChangedRegion.minX(); x <= m_ChangedRegion.maxX(); x++ )
    {
      int i = x + yOffset;
      if ( m_MeasurementCount[i] > 0 )
      {
        m_OccupancyProbability[i] = m_OccupancyCount[i] / static_cast<float>  	( m_MeasurementCount[i] );
		if (m_HighSensitive[i] == 1)
		{
		  if(m_reset_high == true)
		  {
		  	m_OccupancyCount[i] = 0;
		  	m_OccupancyProbability[i] = 0;
		  }
		  if(m_MeasurementCount[i] > 20 )
		  {
		  	m_MeasurementCount[i] = 10; 	
		  	m_OccupancyCount[i] = 10 * m_OccupancyProbability[i];
		  }
		  if(m_OccupancyProbability[i] > 0.3)
		  {
		    m_OccupancyProbability[i] =  1 ; 
		  }
		}      
      }
      else
      {
        m_OccupancyProbability[i] = UNKNOWN_LIKELIHOOD;
      }
    }
  }
  if(m_reset_high  == true)
  {
  	m_reset_high = false;
  }
}

void OccupancyMap::insertLaserData ( sensor_msgs::LaserScanConstPtr laserData )
{
  markRobotPositionFree();

  m_LaserMaxRange = laserData->range_max;
  m_LaserMinRange = laserData->range_min;
  tf::StampedTransform laserTransform;
  try
  {
    m_tfListener.lookupTransform("/base_link", laserData->header.frame_id, ros::Time(0), laserTransform);
  }
  catch (tf::TransformException ex) {
      ROS_ERROR_STREAM(ex.what());
  }

  m_LaserPos.x = laserTransform.getOrigin().getX();
  m_LaserPos.y = laserTransform.getOrigin().getY();

  std::vector<RangeMeasurement> ranges;
  ranges.reserve ( laserData->ranges.size() );

  bool errorFound=false;
  int lastValidIndex=-1;
  float lastValidRange=m_FreeReadingDistance;

  RangeMeasurement rangeMeasurement;
  rangeMeasurement.sensorPos = m_LaserPos;
  for ( unsigned int i = 0; i < laserData->ranges.size(); i++ )
  {
    if ( ( laserData->ranges[i] >= m_LaserMinRange ) && ( laserData->ranges[i] <= m_LaserMaxRange ) )
    {
      //if we're at the end of an errorneous segment, interpolate
      //between last valid point and current point
      if ( errorFound )
      {
        //smaller of the two ranges belonging to end points
        float range = Math::min ( lastValidRange, laserData->ranges[i] );
        range -= m_Resolution * 2;

        if ( range < m_FreeReadingDistance )
        {
          range = m_FreeReadingDistance;
        }
        else
          if ( range > m_LaserMaxRange*0.8 )
          {
            range = m_LaserMaxRange*0.8;
          }

        //choose smaller range
        for ( unsigned j=lastValidIndex+1; j<i; j++ )
        {
          rangeMeasurement.endPos =  map_tools::laser_range_to_point(range, j, laserData->angle_min, laserData->angle_increment, m_tfListener, laserData->header.frame_id, "/base_link");// laserConf->nativeLaserToRobot ( j, range ); //TODO use tf
          rangeMeasurement.free = true;
          ranges.push_back ( rangeMeasurement );
        }
      }
      rangeMeasurement.endPos = map_tools::laser_range_to_point(laserData->ranges[i], i, laserData->angle_min, laserData->angle_increment, m_tfListener, laserData->header.frame_id, "/base_link");
      rangeMeasurement.free = false;
      ranges.push_back ( rangeMeasurement );

      errorFound=false;
      lastValidIndex=i;
      lastValidRange=laserData->ranges[i];
    }
    else
    {
      errorFound=true;
    }
  }

  if ( errorFound )
  {
    for ( unsigned j=lastValidIndex+1; j<laserData->ranges.size(); j++ )
    {
      rangeMeasurement.endPos = map_tools::laser_range_to_point(m_FreeReadingDistance, j, laserData->angle_min, laserData->angle_increment, m_tfListener, laserData->header.frame_id, "/base_link"); //

      rangeMeasurement.free = true;
      ranges.push_back ( rangeMeasurement );
    }
  }

  insertRanges ( ranges );
}


void OccupancyMap::insertRanges ( vector<RangeMeasurement> ranges )
{
  clearChanges();

  Eigen::Vector2i lastEndPixel;


  //paint safety borders
  if ( m_ObstacleBorders )
  {
    for ( unsigned i=0; i<ranges.size(); i++ )
    {
      geometry_msgs::Point endPosWorld = map_tools::transformPoint(ranges[i].endPos, m_tfListener, "/base_link", "/map");
      Eigen::Vector2i endPixel = map_tools::toMapCoords(endPosWorld, m_Origin, m_Resolution);

      for ( int y=endPixel.y()-1; y <= endPixel.y() +1; y++ )
      {
        for ( int x=endPixel.x()-1; x <= endPixel.x() +1; x++ )
        {
          unsigned offset=x+m_PixelSize*y;
          if ( offset < unsigned ( m_ByteSize ) )
          {
            m_CurrentChanges[ offset ] = SAFETY_BORDER;
          }
        }
      }
    }
  }
  //paint safety ranges
  for ( unsigned i=0; i<ranges.size(); i++ )
  {
      geometry_msgs::Point startPosWorld = map_tools::transformPoint(ranges[i].endPos, m_tfListener, "/base_link", "/map");
      Eigen::Vector2i startPixel = map_tools::toMapCoords(startPosWorld, m_Origin, m_Resolution);
    geometry_msgs::Point endPos;
    endPos.x = ranges[i].endPos.x * 4;
    endPos.y = ranges[i].endPos.y * 4;

    geometry_msgs::Point endPosWorld = map_tools::transformPoint(endPos, m_tfListener, "/base_link", "/map");
    Eigen::Vector2i endPixel = map_tools::toMapCoords(endPosWorld, m_Origin, m_Resolution);


    if(endPixel.x() < 0) endPixel.x() = 0;
    if(endPixel.y() < 0) endPixel.y() = 0;
    if(endPixel.x() >= m_PixelSize) endPixel.x() = m_PixelSize - 1;
    if(endPixel.y() >= m_PixelSize) endPixel.y() = m_PixelSize - 1;

    drawLine ( m_CurrentChanges, startPixel, endPixel, SAFETY_BORDER );
  }

  //paint end pixels
  for ( unsigned i=0; i<ranges.size(); i++ )
  {
    if ( !ranges[i].free )
    {
      geometry_msgs::Point endPosWorld = map_tools::transformPoint(ranges[i].endPos, m_tfListener, "/base_link", "/map");
      Eigen::Vector2i endPixel = map_tools::toMapCoords(endPosWorld, m_Origin, m_Resolution);

      if ( endPixel != lastEndPixel )
      {
        unsigned offset = endPixel.x() + m_PixelSize * endPixel.y();
        if ( offset < m_ByteSize )
        {
          m_CurrentChanges[ offset ] = ::OCCUPIED;
        }
      }
      lastEndPixel=endPixel;
      }
  }

  //paint free ranges
  for ( unsigned i=0; i<ranges.size(); i++ )
  {
      geometry_msgs::Point sensorPosWorld = map_tools::transformPoint(ranges[i].sensorPos, m_tfListener, "/base_link", "/map");
      geometry_msgs::Point endPosWorld = map_tools::transformPoint(ranges[i].endPos, m_tfListener, "/base_link", "/map");
      Eigen::Vector2i sensorPixel = map_tools::toMapCoords(sensorPosWorld, m_Origin, m_Resolution);
    Eigen::Vector2i endPixel = map_tools::toMapCoords(endPosWorld, m_Origin, m_Resolution);

    m_ChangedRegion.enclose ( sensorPixel.x(), sensorPixel.y() );
    m_ChangedRegion.enclose ( endPixel.x(), endPixel.y() );

    if ( endPixel != lastEndPixel )
    {
        drawLine ( m_CurrentChanges, sensorPixel, endPixel, ::FREE );
    }

    lastEndPixel=endPixel;
  }

  m_ChangedRegion.clip ( Box2D<int> ( 0,0,m_PixelSize-1,m_PixelSize-1 ) );
  m_ExploredRegion.enclose ( m_ChangedRegion );
  applyChanges();
  computeOccupancyProbabilities();
}

double OccupancyMap::contrastFromProbability ( int8_t prob )
{
  // range from 0..126 (=127 values) and 128..255 (=128 values)
  double diff = ( ( double ) prob - UNKNOWN );
  double contrast;
  if ( prob <= UNKNOWN )
  {
    contrast = ( diff / UNKNOWN ); // 0..1
  }
  else
  {
    contrast = ( diff / ( UNKNOWN+1 ) );  // 0..1
  }
  return ( contrast * contrast );
}

double OccupancyMap::evaluateByContrast()
{
  double contrastSum = 0.0;
  unsigned int contrastCnt = 0;

  for ( int y = m_ExploredRegion.minY(); y <= m_ExploredRegion.maxY(); y++ )
  {
    for ( int x = m_ExploredRegion.minX(); x <= m_ExploredRegion.maxX(); x++ )
    {
      int i = x + y * m_PixelSize;
      if ( m_MeasurementCount [i] > 1 )
      {
        int prob = m_OccupancyProbability[i] * 100;
        if ( prob != NOT_SEEN_YET ) // ignore not yet seen cells
        {
          contrastSum += contrastFromProbability ( prob );
          contrastCnt++;
        }
      }
    }
  }
  if ( ( contrastCnt ) > 0 )
  {
    return ( ( contrastSum / contrastCnt ) * 100 );
  }
  return ( 0 );
}



vector<MeasurePoint> OccupancyMap::getMeasurePoints (sensor_msgs::LaserScanConstPtr laserData)
{
  vector<MeasurePoint> result;
  result.reserve ( laserData->ranges.size() );

  double minDist = m_MeasureSamplingStep;

  m_LaserMaxRange = laserData->range_max;
  m_LaserMinRange = laserData->range_min;

  Point2D lastHitPos;
  Point2D lastUsedHitPos;

  //extract points for measuring
  for ( unsigned int i=0; i < laserData->ranges.size(); i++ )
  {
    if ( laserData->ranges[i] <= m_LaserMaxRange && laserData->ranges[i] >= m_LaserMinRange )
    {
        geometry_msgs::Point hitPosMsg = map_tools::laser_range_to_point(laserData->ranges[i], i, laserData->angle_min, laserData->angle_increment, m_tfListener, laserData->header.frame_id, "/base_link"); //laserConf->nativeLaserToRobot ( i, laserData[i] ); //tf
        Point2D hitPos(hitPosMsg.x, hitPosMsg.y);

      if ( hitPos.distance ( lastUsedHitPos ) >= minDist )
      {
        MeasurePoint p;
        //preserve borders of segments
        if ( ( i!=0 ) &&
                ( lastUsedHitPos.distance ( lastHitPos ) > m_Resolution*0.5 ) &&
                ( hitPos.distance ( lastHitPos ) >= minDist*1.5 ) )
        {
          p.hitPos = lastHitPos;
          p.borderType = RightBorder;
          result.push_back ( p );
          p.hitPos = hitPos;
          p.borderType = LeftBorder;
          result.push_back ( p );
          lastUsedHitPos = hitPos;
        }
        else
        {
          //save current point
          p.hitPos = hitPos;
          p.borderType = NoBorder;
          result.push_back ( p );
          lastUsedHitPos = hitPos;
        }
      }
      lastHitPos = hitPos;
    }
  }

  //the first and last one are border pixels
  if ( result.size() > 0 )
  {
    result[0].borderType = LeftBorder;
    result[result.size()-1].borderType = RightBorder;
  }

  //calculate front check points
  for ( unsigned i=0; i < result.size(); i++ )
  {
    CVec2 diff;

    switch ( result[i].borderType )
    {
      case NoBorder:
        diff = result[i-1].hitPos - result[i+1].hitPos;
        break;
      case LeftBorder:
        diff = result[i].hitPos - result[i+1].hitPos;
        break;
      case RightBorder:
        diff = result[i-1].hitPos - result[i].hitPos;
        break;
    }

    CVec2 normal = diff.rotate ( Math::Pi * 0.5 );
    normal.normalize();
    normal *= m_Resolution * sqrt ( 2.0 ) * 10.0;

    result[i].frontPos = result[i].hitPos + normal;
  }

  return result;
}


float OccupancyMap::computeScore ( Pose robotPose, std::vector<MeasurePoint> measurePoints )
{
  // This is a very simple implementation, using only the end point of the beam.
  // For every beam the end cell is computed and tested if the cell is occupied.
  unsigned lastOffset=0;
  unsigned hitOffset=0;
  unsigned frontOffset=0;
  float fittingFactor = 0.0;

  float sinTheta = sin ( robotPose.theta() );
  float cosTheta = cos ( robotPose.theta() );

  for ( unsigned int i = 0; i < measurePoints.size(); i++ )
  {
    //fast variant:
    float x = cosTheta * measurePoints[i].hitPos.x() - sinTheta * measurePoints[i].hitPos.y() + robotPose.x();
    float y = sinTheta * measurePoints[i].hitPos.x() + cosTheta * measurePoints[i].hitPos.y() + robotPose.y();
    geometry_msgs::Point hitPos;
    hitPos.x = x;
    hitPos.y = y;

    Eigen::Vector2i hitPixel = map_tools::toMapCoords(hitPos, m_Origin, m_Resolution);
    hitOffset = hitPixel.x() + m_PixelSize*hitPixel.y();

    //avoid multiple measuring of same pixel or unknown pixel
    if ( ( hitOffset == lastOffset ) || ( hitOffset >= unsigned ( m_ByteSize ) ) || ( m_MeasurementCount[hitOffset] == 0 ) )
    {
      continue;
    }

    if ( m_BacksideChecking )
    {
      //avoid matching of back and front pixels of obstacles
      x = cosTheta * measurePoints[i].frontPos.x() - sinTheta * measurePoints[i].frontPos.y() + robotPose.x();
      y = sinTheta * measurePoints[i].frontPos.x() + cosTheta * measurePoints[i].frontPos.y() + robotPose.y();
      geometry_msgs::Point frontPos;
      frontPos.x = x;
      frontPos.y = y;

      Eigen::Vector2i frontPixel = map_tools::toMapCoords(frontPos, m_Origin, m_Resolution);
      frontOffset = frontPixel.x() + m_PixelSize*frontPixel.y();

      if ( ( frontOffset >= unsigned ( m_ByteSize ) ) || ( m_MeasurementCount[frontOffset] == 0 ) )
      {
        continue;
      }
    }

    lastOffset=hitOffset;
    //fittingFactor += m_SmoothOccupancyProbability[ offset ];
    fittingFactor += m_OccupancyProbability[ hitOffset ];
  }
  return fittingFactor;
}


template<class DataT>
void OccupancyMap::drawLine ( DataT* data, Eigen::Vector2i& startPixel, Eigen::Vector2i& endPixel, char value )
{

  //bresenham algorithm
  int xstart = startPixel.x();
  int ystart = startPixel.y();
  int xend = endPixel.x();
  int yend = endPixel.y();

  int x, y, t, dist, xerr, yerr, dx, dy, incx, incy;
  // compute distances
  dx = xend - xstart;
  dy = yend - ystart;

  // 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 = xstart;
  y = ystart;
  xerr = dx;
  yerr = dy;

  // compute cells
  for ( t = 0; t < dist; t++ )
  {
    int index = x + m_PixelSize * y;
    // set flag to free if no flag is set
    // (do not overwrite occupied cells)
    if(index < 0) continue;
    if ( data[index] == NO_CHANGE )
    {
      data[index] = value;
    }
/*    if ( data[index] == OCCUPIED || data[index] == SAFETY_BORDER )
    {
      return;
    }*/
    xerr += dx;
    yerr += dy;
    if ( xerr > dist )
    {
      xerr -= dist;
      x += incx;
    }
    if ( yerr > dist )
    {
      yerr -= dist;
      y += incy;
    }
  }
}


void OccupancyMap::applyChanges()
{
  for ( int y = m_ChangedRegion.minY(); y <= m_ChangedRegion.maxY(); y++ )
  {
    int yOffset = m_PixelSize * y;
    for ( int x = m_ChangedRegion.minX(); x <= m_ChangedRegion.maxX(); x++ )
    {
      int i = x + yOffset;
      if ( ( m_CurrentChanges[i] == ::FREE || m_CurrentChanges[i] == ::OCCUPIED ) && m_MeasurementCount[i] < SHRT_MAX )
      {
        m_MeasurementCount[i]++;
      }
      if ( m_CurrentChanges[i] == ::OCCUPIED && m_OccupancyCount[i] < USHRT_MAX )
      {
        m_OccupancyCount[i]++;
      }
    }
  }
}

void OccupancyMap::clearChanges()
{
  m_ChangedRegion.expand ( 2 );
  m_ChangedRegion.clip ( Box2D<int> ( 0,0,m_PixelSize-1,m_PixelSize-1 ) );
  for ( int y = m_ChangedRegion.minY(); y <= m_ChangedRegion.maxY(); y++ )
  {
    int yOffset = m_PixelSize * y;
    for ( int x = m_ChangedRegion.minX(); x <= m_ChangedRegion.maxX(); x++ )
    {
      int i = x + yOffset;
      m_CurrentChanges[i] = NO_CHANGE;
    }
  }
  m_ChangedRegion=Box2D<int> ( m_PixelSize - 1, m_PixelSize - 1, 0, 0 );
}

void OccupancyMap::incrementMeasurementCount ( Eigen::Vector2i p )
{
  unsigned index = p.x() + m_PixelSize * p.y();
  if ( index < m_ByteSize )
  {
    if ( m_CurrentChanges[index] == NO_CHANGE && m_MeasurementCount[index] < USHRT_MAX )
    {
      m_CurrentChanges[index] = ::FREE;
      m_MeasurementCount[index]++;
    }
  }
  else
  {
    ROS_ERROR( "Index out of bounds: x = %d, y = %d", p.x(), p.y() );
  }
}

void OccupancyMap::incrementOccupancyCount ( Eigen::Vector2i p )
{
  int index = p.x() + m_PixelSize * p.y();
  if ( ( m_CurrentChanges[index] == NO_CHANGE || m_CurrentChanges[index] == ::FREE ) && m_MeasurementCount[index] < USHRT_MAX )
  {
    m_CurrentChanges[index] = ::OCCUPIED;
    m_OccupancyCount[index]++;
  }
}

void OccupancyMap::scaleDownCounts ( int maxCount )
{
  clearChanges();
  if ( maxCount <= 0 )
  {
    ROS_WARN("WARNING: argument maxCount is choosen to small, resetting map.");
    memset ( m_MeasurementCount, 0, m_ByteSize );
    memset ( m_OccupancyCount, 0, m_ByteSize );
    memset ( m_InaccessibleCount, 0, m_ByteSize );
  }
  else
  {
    for ( unsigned i = 0; i < m_ByteSize; i++ )
    {
      int scalingFactor = m_MeasurementCount[i] / maxCount;
      if ( scalingFactor != 0 )
      {
        m_MeasurementCount[i] /= scalingFactor;
        m_OccupancyCount[i] /= scalingFactor;
        m_InaccessibleCount[i] /= scalingFactor;
      }
      if ( m_InaccessibleCount[i] > maxCount )
      {
        m_InaccessibleCount[i] = maxCount;
      }
    }
  }
  maximizeChangedRegion();
  applyChanges();
  computeOccupancyProbabilities();
}


void OccupancyMap::markRobotPositionFree()
{
  geometry_msgs::Point point;
  point.x = 0;
  point.y = 0;
  point.z = 0;
  geometry_msgs::Point endPosWorld = map_tools::transformPoint(point, m_tfListener, "/base_link", "/map");
  Eigen::Vector2i robotPixel = map_tools::toMapCoords(endPosWorld, m_Origin, m_Resolution);

  int width = 0.3 / m_Resolution;
  for ( int i = robotPixel.y() - width; i <= robotPixel.y() + width; i++ )
  {
    for ( int j = robotPixel.x() - width; j <= robotPixel.x() + width; j++ )
    {
      incrementMeasurementCount ( Eigen::Vector2i ( i, j ) );
    }
  }
  Box2D<int> robotBox ( robotPixel.x()-width, robotPixel.y()-width, robotPixel.x() +width, robotPixel.y() +width );
  m_ChangedRegion.enclose ( robotBox );
  m_ExploredRegion.enclose ( robotBox );
}


QImage OccupancyMap::getProbabilityQImage ( int trancparencyThreshold, bool showInaccessible ) const
{
  QImage retImage ( m_PixelSize, m_PixelSize, QImage::Format_RGB32 );
  for ( int y = 0; y < m_PixelSize; y++ )
  {
    for ( int x = 0; x < m_PixelSize; x++ )
    {
      int index = x + y * m_PixelSize;
      int value = UNKNOWN;
      if ( m_MeasurementCount[index] > 0 )
      {
        value = static_cast<int> ( ( 1.0 - m_OccupancyProbability[index] ) * 255 );
        if ( m_MeasurementCount[index] < trancparencyThreshold )
        {
          value = static_cast<int> ( ( 0.75 + 0.025 * m_MeasurementCount[index] ) * ( 1.0 - m_OccupancyProbability[index] ) * 255 );
        }
      }
      if ( showInaccessible && m_InaccessibleCount[index] >= 2 )
      {
        value = 0;
      }
      retImage.setPixel ( x, y, qRgb ( value, value, value ) );
    }
  }
  return retImage;
}

void OccupancyMap::getOccupancyProbabilityImage ( vector<int8_t>& data, int& width, int& height, float& resolution )
{
  width = m_PixelSize;
  height = m_PixelSize;
  resolution = m_Resolution;
  data.resize(m_PixelSize * m_PixelSize);
  std::fill(data.begin(), data.end(), (int8_t)NOT_SEEN_YET); //note: linker error without strange cast from int8_t to int8_t
  for ( int y = m_ExploredRegion.minY(); y <= m_ExploredRegion.maxY(); y++ )
  {
    int yOffset = m_PixelSize * y;
    for ( int x = m_ExploredRegion.minX(); x <= m_ExploredRegion.maxX(); x++ )
    {
      int i = x + yOffset;
      if ( m_MeasurementCount[i] < 1 )
      {
        continue;
      }
      // set inaccessible points to black
      if ( m_InaccessibleCount[i] >= 2 )
      {
        data[i] = 99;
        continue;
      }
      if(m_OccupancyProbability[i] == UNKNOWN_LIKELIHOOD)
      {
          data[i] = NOT_SEEN_YET;
      }
      else
      {
        data[i] = (int)(m_OccupancyProbability[i] * 99); //TODO maybe - 2 (or *0.99 or smth)
      }
    }
  }
}

void OccupancyMap::maximizeChangedRegion()
{
  m_ChangedRegion=m_ExploredRegion;
}

void OccupancyMap::applyMasking(const nav_msgs::OccupancyGrid::ConstPtr &msg)
{
    if(msg->data.size() != m_ByteSize)
    {
        ROS_ERROR_STREAM("Size Mismatch between SLAM map (" << m_ByteSize << ") and masking map (" << msg->data.size() << ")");
        return;
    }
    for(size_t y = 0; y < msg->info.height; y++)
    {
        int yOffset = msg->info.width * y;
        for(size_t x = 0; x < msg->info.width; x++)
        {
            int i = yOffset + x;

            switch(msg->data[i])
            {
            case homer_mapnav_msgs::ModifyMap::BLOCKED:
            case homer_mapnav_msgs::ModifyMap::OBSTACLE:
                //increase measure count of cells which were not yet visible to be able to modify unknown areas
                if(m_MeasurementCount[i] == 0)
                    m_MeasurementCount[i] = 10;

                m_OccupancyCount[i] = m_MeasurementCount[i];
                m_OccupancyProbability[i] = 1.0;
                m_ExploredRegion.enclose(x, y);
                break;
            case homer_mapnav_msgs::ModifyMap::FREE:
                //see comment above
                if(m_MeasurementCount[i] == 0)
                    m_MeasurementCount[i] = 10;

                m_OccupancyCount[i] = 0;
                m_OccupancyProbability[i] = 0.0;
                m_ExploredRegion.enclose(x, y);
                break;
            case homer_mapnav_msgs::ModifyMap::HIGH_SENSITIV:
            	m_HighSensitive[i] = 1;
                break;
            }
        }
    }
}

void OccupancyMap::cleanUp()
{
  if ( m_OccupancyProbability )
  {
    delete[] m_OccupancyProbability;
  }
  if ( m_MeasurementCount )
  {
    delete[] m_MeasurementCount;
  }
  if ( m_OccupancyCount )
  {
    delete[] m_OccupancyCount;
  }
  if ( m_CurrentChanges )
  {
    delete[] m_CurrentChanges;
  }
  if ( m_InaccessibleCount )
  {
    delete[] m_InaccessibleCount;
  }
  if ( m_HighSensitive ) 
  {
    delete[] m_HighSensitive;
  }
}