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


#include "qparticleviewer.h"
#include "moc_qparticleviewer.cpp"
#include <qimage.h>


using namespace GMapping;

QParticleViewer::QParticleViewer( QWidget * parent, const char * name , WFlags f, GridSlamProcessorThread* thread): QWidget(parent, name, f|WRepaintNoErase|WResizeNoErase){
  viewCenter=Point(0.,0.);
  setMinimumSize(500,500);
  mapscale=10.;
  m_pixmap=new QPixmap(500,500);
  m_pixmap->fill(Qt::white);
  gfs_thread=thread;
  tis=0;
  m_particleSize=0;
  m_refresh=false;
  bestMap=0;
  dragging=false;
  showPaths=0;
  showBestPath=1;
  count=0;
  writeToFile=0;
}

QParticleViewer::~QParticleViewer(){
  if (m_pixmap)
    delete m_pixmap;
}

void QParticleViewer::paintEvent ( QPaintEvent *paintevent ){
  if (! m_pixmap)
    return;
  bitBlt(this,0,0,m_pixmap,0,0,m_pixmap->width(),m_pixmap->height(),CopyROP);
}

void QParticleViewer::mousePressEvent ( QMouseEvent *event ){
  if (event->button()==LeftButton){
    dragging=true;
    draggingPos=event->pos();	
  }
}
void QParticleViewer::mouseMoveEvent ( QMouseEvent *event ){
  if (dragging){
    QPoint delta=event->pos()-draggingPos;
    draggingPos=event->pos();
    viewCenter.x-=delta.x()/mapscale;
    viewCenter.y+=delta.y()/mapscale;
    update();
  }
}

void QParticleViewer::mouseReleaseEvent ( QMouseEvent *event ){
  if (event->button()==LeftButton){
    dragging=false;
  }
}

void QParticleViewer::keyPressEvent ( QKeyEvent* e ){
  switch (e->key()){
  case Qt::Key_B: showBestPath=!showBestPath; break;
  case Qt::Key_P: showPaths=!showPaths; break;
  case Qt::Key_Plus: mapscale *=1.25;  break;
  case Qt::Key_Minus: mapscale/=1.25;  break;
  case Qt::Key_C: viewCenter=bestParticlePose; break;
  default:;
  }
}

		
void QParticleViewer::resizeEvent(QResizeEvent * sizeev){
  if (!m_pixmap)
    return;
  cerr << "QParticleViewer::resizeEvent" <<  sizeev->size().width()<< " " << sizeev->size().height() << endl;
  m_pixmap->resize(sizeev->size());
}

void QParticleViewer::drawParticleMove(const QParticleViewer::OrientedPointVector& oldPose, const QParticleViewer::OrientedPointVector& newPose){
  assert(oldPose.size()==newPose.size());
  QPainter painter(m_pixmap);
  painter.setPen(Qt::red);
  OrientedPointVector::const_iterator nit=newPose.begin();
  for(OrientedPointVector::const_iterator it=oldPose.begin(); it!=oldPose.end(); it++, nit++){
    IntPoint p0=map2pic(*it);
    IntPoint p1=map2pic(*nit);
    painter.drawLine( 
		     (int)(p0.x), (int)(p0.y), (int)(p1.x), (int)(p1.y)
		     );
  }
}

void QParticleViewer::drawFromFile(){
  if(! tis)
    return;
  if (tis->atEnd())
    return;	
  QTextIStream& is=*tis;
	
  string line=is.readLine();
  istringstream lineStream(line);
  string recordType;
  lineStream >> recordType;
  if (recordType=="LASER_READING"){
    //do nothing with the laser
    cout << "l" << flush;
  }
  if (recordType=="ODO_UPDATE"){
    //just move the particles
    if (m_particleSize)
      m_refresh=true;
    m_oldPose=m_newPose;
    m_newPose.clear();
    unsigned int size;
    lineStream >> size;
    if (!m_particleSize)
      m_particleSize=size;
    assert(m_particleSize==size);
    for (unsigned int i=0; i< size; i++){
      OrientedPoint p;
      double w;
      lineStream >> p.x;
      lineStream >> p.y;
      lineStream >> p.theta;
      lineStream >> w;
      m_newPose.push_back(p);
    }
    cout << "o" << flush;
  }
  if (recordType=="SM_UPDATE"){
    if (m_particleSize)
      m_refresh=true;
    m_oldPose=m_newPose;
    m_newPose.clear();
    unsigned int size;
    lineStream >> size;
    if (!m_particleSize)
      m_particleSize=size;
    assert(m_particleSize==size);
    for (unsigned int i=0; i< size; i++){
      OrientedPoint p;
      double w;
      lineStream >> p.x;
      lineStream >> p.y;
      lineStream >> p.theta;
      lineStream >> w;
      m_newPose.push_back(p);
    }
    cout << "u" << flush;
  }
  if (recordType=="RESAMPLE"){
    unsigned int size;
    lineStream >> size;
    if (!m_particleSize)
      m_particleSize=size;
    assert(m_particleSize==size);
    OrientedPointVector temp(size);
    for (unsigned int i=0; i< size; i++){
      unsigned int ind;
      lineStream >> ind;
      temp[i]=m_newPose[ind];
    }
    m_newPose=temp;
    cout << "r" << flush;
  }
  if (m_refresh){
    drawParticleMove(m_oldPose, m_newPose);
    m_refresh=false;
  }
}

void QParticleViewer::drawMap(const ScanMatcherMap& map){
  //cout << "Map received" << map.getMapSizeX() << " " << map.getMapSizeY() << endl;
  QPainter painter(m_pixmap);
  painter.setPen(Qt::black);
  m_pixmap->fill(QColor(200,200,255));
  unsigned int count=0;
	
  Point wmin=Point(pic2map(IntPoint(-m_pixmap->width()/2,m_pixmap->height()/2)));
  Point wmax=Point(pic2map(IntPoint(m_pixmap->width()/2,-m_pixmap->height()/2)));
  IntPoint imin=map.world2map(wmin);
  IntPoint imax=map.world2map(wmax);
  /*	cout << __PRETTY_FUNCTION__ << endl;
	cout << " viewCenter=" << viewCenter.x << "," << viewCenter.y <<   endl;	
	cout << " wmin=" << wmin.x << "," << wmin.y <<  " wmax=" << wmax.x << "," << wmax.y << endl;	
	cout << " imin=" << imin.x << "," << imin.y <<  " imax=" << imax.x << "," << imax.y << endl;
	cout << " mapSize=" << map.getMapSizeX() << "," << map.getMapSizeY() << endl;*/
  for(int x=0; x<m_pixmap->width(); x++)
    for(int y=0; y<m_pixmap->height(); y++){
      //IntPoint ip=IntPoint(x,y)+imin;
      //Point p=map.map2world(ip);
      Point p=pic2map(IntPoint(x-m_pixmap->width()/2,
			       y-m_pixmap->height()/2));

      //if (map.storage().isInside(map.world2map(p))){
      double v=map.cell(p);
      if (v>=0){
	int grayValue=255-(int)(255.*v);
	painter.setPen(QColor(grayValue, grayValue, grayValue));
	painter.drawPoint(x,y);
	count++;
      }
    }
}


void QParticleViewer::drawFromMemory(){
  if (! gfs_thread)
    return;
  m_pixmap->fill(Qt::white);
  GridSlamProcessorThread::EventDeque events=gfs_thread->getEvents();
  for (GridSlamProcessorThread::EventDeque::const_iterator it=events.begin(); it!=events.end();it++){
    GridSlamProcessorThread::MapEvent* mapEvent= dynamic_cast<GridSlamProcessorThread::MapEvent*>(*it);
    if (mapEvent){
      //cout << "Map: bestIdx=" << mapEvent->index <<endl;
      if (bestMap)
	delete bestMap;
      else {
				
      }
      bestMap=mapEvent->pmap;
      mapEvent->pmap=0;
      bestParticlePose=mapEvent->pose;
      delete mapEvent;
    }else{
      GridSlamProcessorThread::DoneEvent* doneEvent= dynamic_cast<GridSlamProcessorThread::DoneEvent*>(*it);
      if (doneEvent){
	gfs_thread->stop();
	delete doneEvent;
      } else
	history.push_back(*it);
    }	
			
  }
  if (bestMap)
    drawMap(*bestMap);
	
  unsigned int particleSize=0;
  std::vector<OrientedPoint> oldPose, newPose;
  vector<unsigned int> indexes;
	
  GridSlamProcessorThread::EventDeque::reverse_iterator it=history.rbegin();
  while (!particleSize && it!=history.rend()){
    GridSlamProcessorThread::ParticleMoveEvent* move= dynamic_cast<GridSlamProcessorThread::ParticleMoveEvent*>(*it);
    GridSlamProcessorThread::ResampleEvent* resample= dynamic_cast<GridSlamProcessorThread::ResampleEvent*>(*it);
    if (move)
      particleSize=move->hypotheses.size();
    if (resample)
      particleSize=resample->indexes.size();
    it++;
  }
	
  //check for the best index
  double wmax=-1e2000;
  unsigned int bestIdx=0;
  bool emitted=false;
  for (unsigned int i=0; i<particleSize; i++){
    unsigned int currentIndex=i;
    bool done=false;
    for(GridSlamProcessorThread::EventDeque::reverse_iterator it=history.rbegin(); it!=history.rend()&& !done; it++){
      GridSlamProcessorThread::ParticleMoveEvent* move= dynamic_cast<GridSlamProcessorThread::ParticleMoveEvent*>(*it);
      if (move && move->scanmatched){
	double cw=move->weightSums[currentIndex];
	if (cw>wmax){
	  wmax=cw;
	  bestIdx=currentIndex;
	} 
	done=true;
	if (! emitted){
	  emit neffChanged(move->neff/particleSize);
	  emitted=true;
	}
      }
      GridSlamProcessorThread::ResampleEvent* resample= dynamic_cast<GridSlamProcessorThread::ResampleEvent*>(*it);
      if (resample){
	currentIndex=resample->indexes[currentIndex];
      }
    }
  }
  //cout << "bestIdx=" << bestIdx << endl;
  QPainter painter(m_pixmap);
	
  for (unsigned int i=0; i<particleSize+1; i++){
    painter.setPen(Qt::yellow);
    unsigned int currentIndex=i;
    if (i==particleSize && showBestPath){
      currentIndex=bestIdx;
      painter.setPen(Qt::red);
    }
    bool first=true;
    OrientedPoint pnew(0,0,0);
    for(GridSlamProcessorThread::EventDeque::reverse_iterator it=history.rbegin(); it!=history.rend(); it++){
      GridSlamProcessorThread::ParticleMoveEvent* move= dynamic_cast<GridSlamProcessorThread::ParticleMoveEvent*>(*it);
      if (move){
	OrientedPoint pold=move->hypotheses[currentIndex];
	IntPoint p0=map2pic(pold)+IntPoint(m_pixmap->width()/2,m_pixmap->height()/2);
	IntPoint p1=map2pic(pnew)+IntPoint(m_pixmap->width()/2,m_pixmap->height()/2);;
	if (first){
	  painter.drawPoint(p0.x, p0.y);
	} else {
	  painter.drawLine(p0.x, p0.y, p1.x, p1.y);
	}
	first=false;
	if (!(showPaths || showBestPath&&i==particleSize))
	  break;
	pnew=pold;
      }
      GridSlamProcessorThread::ResampleEvent* resample= dynamic_cast<GridSlamProcessorThread::ResampleEvent*>(*it);
      if (resample && ! first){
	currentIndex=resample->indexes[currentIndex];
      }
    }
  }
  if (writeToFile && bestMap){
    if (! (count%writeToFile) ){
      char name[100];
      sprintf(name,"dump-%05d.png", count/writeToFile);
      cout << " Writing " << name <<" ..." << flush;
      QImage image=m_pixmap->convertToImage();
      bool rv=image.save(name,"PNG");
      if (rv)
	cout << " Done";
      else
	cout << " ERROR";
      cout << endl;
    }
    count++;
  }
}

void QParticleViewer::timerEvent(QTimerEvent * te) {
  if (te->timerId()==timer) {
    if ( tis)
      drawFromFile();
    else{
      drawFromMemory();
      update();
    }
  }
}


void QParticleViewer::start(int period){
  timer=startTimer(period);
}

void QParticleViewer::refreshParameters(){
  //scanmatcher
  matchingParameters.maxrange=gfs_thread->getlaserMaxRange();
  matchingParameters.urange=gfs_thread->getusableRange();
  matchingParameters.ssigma=gfs_thread->getgaussianSigma();
  matchingParameters.sreg=gfs_thread->getregScore();
  matchingParameters.scrit=gfs_thread->getcritScore();
  matchingParameters.ksize=gfs_thread->getkernelSize();
  matchingParameters.lstep=gfs_thread->getoptLinearDelta();
  matchingParameters.astep=gfs_thread->getoptAngularDelta();
  matchingParameters.iterations=gfs_thread->getoptRecursiveIterations();

  //start
  startParameters.srr=gfs_thread->getsrr();
  startParameters.stt=gfs_thread->getstt();
  startParameters.str=gfs_thread->getstr();
  startParameters.srt=gfs_thread->getsrt();
	
  startParameters.xmin=gfs_thread->getxmin();
  startParameters.ymin=gfs_thread->getymin();
  startParameters.xmax=gfs_thread->getxmax();
  startParameters.ymax=gfs_thread->getymax();
  startParameters.delta=gfs_thread->getdelta();
	
  startParameters.particles=gfs_thread->getParticles().size();
  startParameters.resampleThreshold=gfs_thread->getresampleThreshold();
  startParameters.outFileName=0;
}

void QParticleViewer::start(){
  gfs_thread->setMatchingParameters(
				    matchingParameters.urange, 
				    matchingParameters.maxrange, 
				    matchingParameters.ssigma, 
				    matchingParameters.ksize, 
				    matchingParameters.lstep, 
				    matchingParameters.astep, 
				    matchingParameters.iterations, 
				    startParameters.lsigma,
				    startParameters.lgain,
				    startParameters.lskip);
  gfs_thread->setMotionModelParameters(
				       startParameters.srr,
				       startParameters.srt,
				       startParameters.srt,
				       startParameters.stt);
  gfs_thread->setUpdateDistances(
				 startParameters.linearUpdate,
				 startParameters.angularUpdate,
				 startParameters.resampleThreshold
				 );
  ((GridSlamProcessor*)(gfs_thread))->init(
					   startParameters.particles,
					   startParameters.xmin, 
					   startParameters.ymin, 
					   startParameters.xmax, 
					   startParameters.ymax, 
					   startParameters.delta, 
					   startParameters.initialPose);
  gfs_thread->start();
}

void QParticleViewer::setMatchingParameters(const QParticleViewer::MatchingParameters& mp){
  matchingParameters=mp;
}

void QParticleViewer::setStartParameters(const QParticleViewer::StartParameters& sp){
  startParameters=sp;
}

void QParticleViewer::stop(){
  gfs_thread->stop();
}

void QParticleViewer::loadFile(const char * fn){
  gfs_thread->loadFiles(fn);
  /*	
    startParameters.initialPose=
    gfs_thread->boundingBox(
    startParameters.xmin, 
    startParameters.ymin, 
    startParameters.xmax,
    startParameters.ymax);
  */	
}
