#include <QtGui/QFileDialog>
#include <QtGui/QMessageBox>
#include "image.h"
#include "image_op.h"
#include "rgba.h"
#include "calibrateWidget.hh"
#include "crossWidget.hh"
#include "mandoWizard.hh"
#include "selectRectWidget.hh"
#include "tools.hh"
#include "videoWidget.hh"
#include "image_funcs.h"

#define CALIBDELAY 900

using namespace mimas;
using namespace std;

MandoWizard::MandoWizard( QWidget *parent, Qt::WFlags f ):
  QDialog( parent, f ), videoTimer(0), calibrateWidget(NULL), calibTimer(0),
  segmentationTimer(0), pointerTimer(0)
{
  ui.setupUi( this );

  // Finalise GUI.
  videoWidget = new VideoWidget;
  ui.videoDisplay->setWidget( videoWidget );
  searchPatternWidget = new VideoWidget;
  ui.searchPatternDisplay->setWidget( searchPatternWidget );
  projectedPatternWidget = new VideoWidget;
  ui.projectedPatternDisplay->setWidget( projectedPatternWidget );
  calibFrameWidget = new CrossWidget;
  ui.calibFrameDisplay->setWidget( calibFrameWidget );
  referenceImageWidget = new SelectRectWidget;
  ui.referenceImageDisplay->setWidget( referenceImageWidget );
  segmentedWidget = new CrossWidget;
  ui.segmentedDisplay->setWidget( segmentedWidget );

  // Connect signals and slots.
  connect( ui.brightnessSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.hueSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.colourSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.contrastSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.shutterSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.gainSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.balanceSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setCameraParameters()) );
  connect( ui.searchPatternButton, SIGNAL(clicked()),
           this, SLOT(loadSearchPattern()) );
  connect( ui.projectedPatternButton, SIGNAL(clicked()),
           this, SLOT(loadProjectedPattern()) );
  connect( ui.previousButton, SIGNAL(clicked()),
           this, SLOT(previousPage()) );
  connect( ui.nextButton, SIGNAL(clicked()),
           this, SLOT(nextPage()) );
  connect( ui.cancelButton, SIGNAL(clicked()),
           this, SLOT(reject()) );
  connect( ui.finishButton, SIGNAL(clicked()),
           this, SLOT(accept()) );
  connect( ui.reconnectButton, SIGNAL(clicked()),
           this, SLOT(reconnectCamera()) );
  connect( ui.calibrateButton, SIGNAL(clicked()),
           this, SLOT(calibrate()) );
  connect( ui.calibrateSlider, SIGNAL(valueChanged(int)),
           this, SLOT(displayCalibFrame(int)) );
  connect( ui.differenceCheckBox, SIGNAL(toggled(bool)),
           this, SLOT(displayDifference(bool)) );
  connect( ui.grabButton, SIGNAL(clicked()),
           this, SLOT(grabReferenceImage()) );
  connect( referenceImageWidget, SIGNAL(rectDefined(bool)),
           this, SLOT(updateEnabled()) );
  connect( ui.thresholdSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setThreshold(int)) );
  connect( ui.sigmaSpinBox, SIGNAL(valueChanged(double)),
           this, SLOT(setSigma(double)) );
  connect( ui.rangeSlider, SIGNAL(valueChanged(int)),
           this, SLOT(setRange(int)) );

  // Load default patterns.
  searchPatternWidget->setImage
    ( qImageToMimasImage
      ( QImage( ":/images/searchPattern.png" ) ) );
  projectedPatternWidget->setImage
    ( qImageToMimasImage
      ( QImage( ":/images/projectedPattern.png" ) ) );

  showPage( 0 );
}

void MandoWizard::previousPage()
{
  showPage( ui.stackedWidget->currentIndex() - 1 );
}

void MandoWizard::nextPage()
{
  showPage( ui.stackedWidget->currentIndex() + 1 );
}

void MandoWizard::showPage( int index )
{
  // Check index.
  assert( index >= 0 );
  assert( index < ui.stackedWidget->count() );

  // Update title.
  switch ( index ) {
  case 0:
    ui.titleLabel->setText( "Adjust camera position" );
    break;
  case 1:
    ui.titleLabel->setText( "Select calibration patterns" );
    break;
  case 2:
    ui.titleLabel->setText( "Perform calibration" );
    break;
  case 3:
    ui.titleLabel->setText( "Capture image and select reference image for "
                            "colour segmentation and select" );
    break;
  case 4:
    ui.titleLabel->setText( "Set parameters for pointer recognition" );
    break;
  case 5:
    ui.titleLabel->setText( "Set parameters for mouse-clicks" );
    break;
  default:
    assert( false );
  };

  // Show page.
  ui.stackedWidget->setCurrentIndex( index );

  // Enable/disable controls.
  updateEnabled();

  // Set up video display.
  setupVideo();
}

void MandoWizard::setupVideo(void)
{
  int index = ui.stackedWidget->currentIndex();
  if ( index == 0 ) {
    if ( videoTimer == 0 )
      videoTimer = startTimer( 0 );
  } else
    if ( videoTimer != 0 ) {
      killTimer( videoTimer );
      videoTimer = 0;
    };
  if ( index == 4 ) {
    if ( segmentationTimer == 0 )
      segmentationTimer = startTimer( 0 );
  } else
    if ( segmentationTimer != 0 ) {
      killTimer( segmentationTimer );
      segmentationTimer = 0;
    };
  if ( index == 5 ) {
    if ( pointerTimer == 0 )
      pointerTimer = startTimer( 0 );
  } else
    if ( pointerTimer != 0 ) {
      killTimer( pointerTimer );
      pointerTimer = 0;
    };
}

void MandoWizard::updateEnabled(void)
{
  // Enable/disable controls.
  int index = ui.stackedWidget->currentIndex();
  ui.previousButton->setEnabled( index > 0 );
  ui.finishButton->setEnabled( index == 5 );
  ui.brightnessSlider->setEnabled( (bool)v4lInput );
  ui.hueSlider->setEnabled( (bool)v4lInput );
  ui.colourSlider->setEnabled( (bool)v4lInput );
  ui.contrastSlider->setEnabled( (bool)v4lInput );
  ui.shutterSlider->setEnabled( (bool)dc1394Input );
  ui.gainSlider->setEnabled( (bool)dc1394Input );
  ui.balanceSlider->setEnabled( (bool)dc1394Input );
  
  switch ( index ) {
  case 0:
    ui.nextButton->setEnabled( (bool)input );
    break;
  case 1:
    ui.nextButton->setEnabled
      ( searchPatternWidget->getImage().initialised() &&
        projectedPatternWidget->getImage().initialised() );
    break;
  case 2: {
    bool calibFinished;
    if ( cameraProjectorCalibration )
      calibFinished = cameraProjectorCalibration->getNumCalibFrames() >= 5;
    else
      calibFinished = false;
    ui.nextButton->setEnabled( calibFinished );
    ui.selectImageLabel->setEnabled( calibFinished );
    ui.calibrateSlider->setEnabled( calibFinished );
    ui.differenceCheckBox->setEnabled( calibFinished );
    break;}
  case 3:
    ui.nextButton->setEnabled( referenceImageWidget->isRectDefined() );
    if ( !referenceImageWidget->isRectDefined() )
      pointerRecognition.reset();
    break;
  case 4:
    ui.nextButton->setEnabled( true );
    break;
  case 5:
    ui.nextButton->setEnabled( false );
    break;
  default:
    assert( false );
  };
}

void MandoWizard::loadSearchPattern()
{
  image< rgba< unsigned char > > img;
  if ( loadImage( "Load search pattern", img ) ) {
    searchPatternWidget->setImage( img );
    cameraProjectorCalibration.reset();
    pointerRecognition.reset();
    updateEnabled();
  };
}

void MandoWizard::loadProjectedPattern()
{
  image< rgba< unsigned char > > img;
  if ( loadImage( "Load projected pattern", img ) ) {
    projectedPatternWidget->setImage( img );
    cameraProjectorCalibration.reset();
    pointerRecognition.reset();
    updateEnabled();
  };
}

void MandoWizard::reconnectCamera()
{
  input.reset();
  v4lInput.reset();
  dc1394Input.reset();
  cameraProjectorCalibration.reset();
  pointerRecognition.reset();
  referenceImageWidget->clearSelection();
  assert( ui.stackedWidget->currentIndex() == 0 );
  setupVideo();
}

void MandoWizard::displayCalibFrame( int index )
{
  displayCalibResult( index, ui.differenceCheckBox->isChecked() );
}

void MandoWizard::displayDifference( bool diff )
{
  displayCalibResult( ui.calibrateSlider->value(), diff );
}

void MandoWizard::grabReferenceImage()
{
  referenceImageWidget->setImage( grabColourFrame() );
  updateEnabled();
}

void MandoWizard::setThreshold( int _value )
{
  assert( pointerRecognition );
  assert( ui.thresholdSlider->minimum() == 0 );
  pointerRecognition->setThreshold
    ( 0.25 * _value / ui.thresholdSlider->maximum() );
}

void MandoWizard::setRange( int _value )
{
  assert( pointerRecognition );
  pointerRecognition->setTrackingRange( _value );
}

void MandoWizard::setSigma( double _value )
{
  pointerRecognition->setSigma( _value );
}

void MandoWizard::displayCalibResult( int index, bool diff )
{
  assert( cameraProjectorCalibration );
  assert( index >= 0 &&
          index < cameraProjectorCalibration->getNumCalibFrames() + 1 );
  if ( index == 0 ) {
    calibFrameWidget->setImage
      ( cameraProjectorCalibration->getBackgroundFrame() );
    calibFrameWidget->clearCross();
  } else {
    if ( diff )
      calibFrameWidget->setImage
        ( normalise( image< int >( cameraProjectorCalibration->
                                   getCalibFrame( index - 1 ) ) -
                     image< int >( cameraProjectorCalibration->
                                   getBackgroundFrame() ), 0, 255 ) );
    else
      calibFrameWidget->setImage
        ( cameraProjectorCalibration->getCalibFrame( index - 1 ) );
    calibFrameWidget->setCross
      ( cameraProjectorCalibration->getCalibPointPair( index - 1 ).second );
  };
}

bool MandoWizard::loadImage( const char *title,
                             image< rgba< unsigned char > > &img )
{
  bool retVal = false;
  try {
    QString s = QFileDialog::getOpenFileName( this, title,
                                              ".",
                                              "Images (*.png *.jpg);;"
                                              "All Files (*)" );
    if ( s != QString::null ) {
      img = qImageToMimasImage( QImage( s ) );
      MMERROR( img.initialised(), mimasexception, ,
               "Error loading file \"" << (const char *)s.toLatin1()
               << "\"." );
      retVal = true;
    };
  } catch ( exception &e ) {
    QMessageBox::critical( this, "Error loading image", e.what() );
  };
  return retVal;
}

void MandoWizard::timerEvent( QTimerEvent *e )
{
  if ( e->timerId() == videoTimer ) {
    try {
      videoWidget->setImage( grabColourFrame( false ) );
      assert( ui.stackedWidget->currentIndex() == 0 );
   } catch ( exception &e ) {
      assert( videoTimer != 0 );
      killTimer( videoTimer );
      videoTimer = 0;
      videoWidget->setImage( image< rgba< unsigned char > >() );
      input.reset();
      v4lInput.reset();
      dc1394Input.reset();
      ui.errorLabel->setText( e.what() );
      updateEnabled();
    };
  } else if ( e->timerId() == calibTimer ) {
    assert( calibrateWidget != NULL );
    killTimer( calibTimer );
    calibTimer = 0;
    if ( !calibrateWidget->isVisible() ) {
      delete calibrateWidget;
      calibrateWidget = NULL;
      cameraProjectorCalibration.reset();
      pointerRecognition.reset();
    } else {
      switch ( calibState ) {
      case Init:
        calibrateWidget->setPattern( 0 );
        calibState = First;
        break;
      case First:
        cameraProjectorCalibration->setBackgroundFrame( grabFrame() );
        calibrateWidget->setPattern( 1 );
        calibState = Second;
        break;
      case Second:
        cameraProjectorCalibration->addCalibFrame
          ( grabFrame(), calibrateWidget->getPos( 1 ) );
        calibrateWidget->setPattern( 2 );
        calibState = Third;
        break;
      case Third:
        cameraProjectorCalibration->addCalibFrame
          ( grabFrame(), calibrateWidget->getPos( 2 ) );
        calibrateWidget->setPattern( 3 );
        calibState = Fourth;
        break;
      case Fourth:
        cameraProjectorCalibration->addCalibFrame
          ( grabFrame(), calibrateWidget->getPos( 3 ) );
        calibrateWidget->setPattern( 4 );
        calibState = Fifth;
        break;
      case Fifth:
        cameraProjectorCalibration->addCalibFrame
          ( grabFrame(), calibrateWidget->getPos( 4 ) );
        calibrateWidget->setPattern( 5 );
        calibState = Closing;
        break;
      case Closing:
        cameraProjectorCalibration->addCalibFrame
          ( grabFrame(), calibrateWidget->getPos( 5 ) );
        calibrateWidget->setPattern( 0 );
        calibState = Finished;
        break;
      default:
        calibState = Init;
        displayCalibFrame( ui.calibrateSlider->value() );
        // Get size of screen.
        // This is only done at this point, because directly after
        // initialisation of calibrateWidget, calibrateWidget->width() and
        // ..->height() will not have the correct valuies.
        screenW = calibrateWidget->width();
        screenH = calibrateWidget->height();
        delete calibrateWidget;
        calibrateWidget = NULL;
        updateEnabled();
      };
      if ( calibState != Init )
        calibTimer=startTimer( CALIBDELAY );
    };
  } else if ( e->timerId() == segmentationTimer ) {
   if ( !pointerRecognition ) {
      assert( cameraProjectorCalibration );
      pointerRecognition =
        PointerRecognitionPtr
        ( new PointerRecognition( cameraProjectorCalibration,
                                  referenceImageWidget->
                                  selectedImage() ) );
      setThreshold( ui.thresholdSlider->value() );
      setSigma( ui.sigmaSpinBox->value() );
      setRange( ui.rangeSlider->value() );
      pointerRecognition->setClip( 0, 0, screenW, screenH );
    };
    Vector camPos( 3 ), pos( 3 );
    image< rgba< unsigned char > > frame( grabColourFrame() );
    if ( pointerRecognition->findPointer( frame, camPos, pos ) )
      segmentedWidget->setCross( camPos );
    else
      segmentedWidget->clearCross();
    segmentedWidget->setImage
      ( pointerRecognition->getMinX(),
        pointerRecognition->getMinY(),
        frame.getWidth(),
        frame.getHeight(),
        pointerRecognition->getSegmentedImage() );
  } else if ( e->timerId() == pointerTimer ) {
    Vector camPos( 3 ), pos( 3 );
    image< rgba< unsigned char > > frame( grabColourFrame() );
    if ( pointerRecognition->findPointer( frame, camPos, pos ) ) {
      // Display *d = XOpenDisplay( NULL );
      // XTestFakeMotionEvent( d, DefaultScreen( d ),
      // (int)pos[0], (int)pos[1], 0 );
      // XCloseDisplay( d );
      ui.mouseXLCD->setSegmentStyle( QLCDNumber::Flat );
      ui.mouseYLCD->setSegmentStyle( QLCDNumber::Flat );
      ui.mouseXLCD->display( (int)pos[0] );
      ui.mouseYLCD->display( (int)pos[1] );
    } else {
      ui.mouseXLCD->setSegmentStyle( QLCDNumber::Outline );
      ui.mouseYLCD->setSegmentStyle( QLCDNumber::Outline );
    };
  } else
    QDialog::timerEvent( e );
}

MandoWizard::VideoPtr MandoWizard::camera(void)
  throw (mimas::mimasexception)
{
  if ( !input ) {
    if ( ui.cameraStack->currentIndex() == 0 ) {
      __u16 norm;
      switch ( ui.modeBox->currentIndex() ) {
      case 0:
        norm = VIDEO_MODE_PAL;
        break;
      case 1:
        norm = VIDEO_MODE_NTSC;
        break;
      case 2:
        norm = VIDEO_MODE_SECAM;
        break;
      case 3:
        norm = VIDEO_MODE_AUTO;
        break;
      default:
        norm = VIDEO_MODE_PAL;
      };
      v4lInput =
        V4LInputPtr( new V4LInput( (const char *)ui.v4lDeviceEdit->
                                   text().toLatin1(),
                                   ui.v4lChannelSpinBox->value(),
                                   -1, -1, norm ) );
      input = v4lInput;
      setCameraParameters();
      ui.errorLabel->setText( "" );
      updateEnabled();
    } else {
      dc1394Input =
        DC1394InputPtr( new DC1394Input( (const char *)ui.dc1394DeviceEdit->
                                         text().toLatin1(),
                                         ui.dc1394NodeSpinBox->value(),
                                         ui.dc1394ChannelSpinBox->value() ) );
      input = dc1394Input;
      dc1394_feature_info shutter = dc1394Input->
        get_feature( FEATURE_SHUTTER );
      ui.shutterSlider->setMinimum( shutter.min );
      ui.shutterSlider->setMaximum( shutter.max );
      ui.shutterSlider->setValue( shutter.value );
      dc1394_feature_info gain = dc1394Input->
        get_feature( FEATURE_GAIN );
      ui.gainSlider->setMinimum( gain.min );
      ui.gainSlider->setMaximum( gain.max );
      ui.gainSlider->setValue( gain.value );
      dc1394_feature_info balance = dc1394Input->
        get_feature( FEATURE_WHITE_BALANCE );
      ui.balanceSlider->setMinimum( balance.min );
      ui.balanceSlider->setMaximum( balance.max );
      ui.balanceSlider->setValue( ( balance.min + balance.max ) / 2 );
      setCameraParameters();
      ui.errorLabel->setText( "" );
      updateEnabled();
    };
  };
  return input;
}

void MandoWizard::setCameraParameters(void)
{
  if ( v4lInput )
    v4lInput->setSensivity( ui.brightnessSlider->value(),
                            ui.hueSlider->value(),
                            ui.colourSlider->value(),
                            ui.contrastSlider->value() );
  if ( dc1394Input ) {
    dc1394Input->set_feature_value( FEATURE_SHUTTER,
                                    ui.shutterSlider->value() );
    dc1394Input->set_feature_value( FEATURE_GAIN,
                                    ui.gainSlider->value() );
    dc1394Input->set_feature_value( FEATURE_WHITE_BALANCE,
                                    ui.balanceSlider->value() );
  };
}

image< unsigned char > MandoWizard::grabFrame(void)
  throw (mimasexception)
{
  return image< unsigned char >( grabColourFrame() );
}

image< rgba< unsigned char > > MandoWizard::grabColourFrame( bool twice )
  throw (mimasexception)
{
  image< rgba< unsigned char > > retVal;
  // discard old frame.
  if ( twice ) (*camera()) >> retVal;
  (*camera()) >> retVal;
  return retVal;
}

void MandoWizard::calibrate()
{
  if ( calibrateWidget != NULL ) {
    delete calibrateWidget;
    calibrateWidget = NULL;
  };
  assert( projectedPatternWidget->getImage().initialised() );
  calibrateWidget = new CalibrateWidget
    ( image< unsigned char >( projectedPatternWidget->getImage() ),
      this );

  assert( searchPatternWidget->getImage().initialised() );
  cameraProjectorCalibration =
    CameraProjectorCalibrationPtr
    ( new CameraProjectorCalibration( searchPatternWidget->getImage() ) );
  pointerRecognition.reset();

  calibrateWidget->setWindowFlags( Qt::Dialog );
  // calibrateWidget->setWindowModality( Qt::WindowModal );
  calibrateWidget->showFullScreen();

  updateEnabled();

  if ( calibTimer != 0 ) {
    killTimer( calibTimer );
    calibTimer = 0;
  };
  calibState = Init;
  calibTimer = startTimer( CALIBDELAY );
}

void MandoWizard::startDrag()
{
  ui.statusLabel->setText( "<b>button pressed</b>" );
}

void MandoWizard::stopDrag()
{
  ui.statusLabel->setText( "button released" );
}