/***************************************************************************
                          videodisplay.cpp  -  description
                             -------------------
    begin                : Sat Apr 5 2003
    copyright            : (C) 2003 by Martin Merck
    email                : merck@astro.uni-wuerzburg.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "amcerror.h"
#include "amclog.h"
#include "amcmotor.h"
#include "amcdefs.h"
#include "amcmirrorpanel.h"
#include "amcprogdlg.h"
#include "amcframegrabber.h"
#include "calibratepaneldialog.h"
#include "centerthread.h"
#include "magicmirror.h"
#include "threadcontroller.h"	// needed for the definition of DEFOCUS_THREAD_CTRL
#include "threadevent.h"			// needed for the definition of thread events
#include "videodisplay.h"
#include "videoframe.h"
#include <qlayout.h>
#include <qtimer.h>
#include <qslider.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qcheckbox.h>
#include <qmessagebox.h>
#include <qgroupbox.h>
#include <qlabel.h>
#include <kapp.h>
#include <math.h>
#include <unistd.h>

#define MAX(a,b) ((a>b) ? a : b )

extern KApplication*			g_theApp;
extern AMCLog*						g_pLog;
extern AMCFrameGrabber*		g_pFG;
extern MagicMirror*				g_theMirror;
extern double							g_dRefX, g_dRefY;

VideoDisplay::VideoDisplay(QWidget *parent, const char *name, bool modal )
		: VideoDisplayBase(parent, name, modal)
{
//	m_theFG = new AMCFrameGrabber();

  qDebug("Thread for VideoDisplay PID: %d - PPID: %d", getpid(), getppid() );

	Brightness->setValue( g_pFG->getBrightness() );
	Contrast->setValue( g_pFG->getContrast() );
	connect( Brightness, SIGNAL( valueChanged(int) ), g_pFG ,SLOT( setBrightness( int ) ) );
	connect( Contrast, SIGNAL( valueChanged(int) ), g_pFG ,SLOT( setContrast( int ) ) );

  qDebug("Creating Videoframe");
	m_videoFrame = new VideoFrame( this, "Video", g_pFG );
  m_videoFrame->setMinimumSize( QSize( MY_WIDTH, MY_HEIGHT ) );
  m_videoFrame->setMaximumSize( QSize( MY_WIDTH, MY_HEIGHT ) );
  m_videoFrame->setFrameShape( QFrame::NoFrame );
  qDebug("Adding Videoframe as widget");
	((QGridLayout*) this->layout())->addWidget( m_videoFrame, 0, 0 );
	connect( m_videoFrame, SIGNAL( gref(double, double ) ), this ,SLOT( setGRefInfoSlot( double, double ) ) );
	connect( m_videoFrame, SIGNAL( spot(double, double ) ), this ,SLOT( setSpotInfoSlot( double, double ) ) );
	connect( m_videoFrame, SIGNAL( framesGrabbed(int) ), (QObject*) GrabbedLCD ,SLOT( display( int ) ) );
	connect( m_videoFrame, SIGNAL( framesProcessed(int) ), (QObject*) ProcessedLCD ,SLOT( display( int ) ) );
	connect( thresholdSlider, SIGNAL( valueChanged(int) ), m_videoFrame ,SLOT( threshold(int) ) );
  thresholdSlider->setValue(15);
	connect( scaleBox, SIGNAL( toggled(bool) ), m_videoFrame ,SLOT( scaleImage(bool) ) );
	connect( scaleValSlider, SIGNAL( valueChanged(int) ), m_videoFrame ,SLOT( scaleValue(int) ) );
  scaleValSlider->setValue(200);
	connect( darksBox, SIGNAL( toggled(bool) ), m_videoFrame ,SLOT( substractDarks(bool) ) );
	connect( Take_Dark_Button, SIGNAL( clicked() ), m_videoFrame ,SLOT( takeDark() ) );
	connect( Clear_Darks_Button, SIGNAL( clicked() ), m_videoFrame ,SLOT( clearDarks() ) );

	// Constructing a timer. The destructor of the parent of the timer, in this case "this"
	// VideoDisplay will automatically destroy the timer so we do not need to delete it
	// ourselves.
  qDebug("Creating and starting timer");

	m_pTimer = new QTimer( this );
	connect( m_pTimer, SIGNAL( timeout() ), m_videoFrame, SLOT( timerDone() ) );
	m_pTimer->start( 1000 );        	// start 1 seconds timer

}

VideoDisplay::~VideoDisplay()
{
	disconnect( Brightness, SIGNAL( valueChanged(int) ), g_pFG ,SLOT( setBrightness( int ) ) );
	disconnect( Contrast, SIGNAL( valueChanged(int) ), g_pFG ,SLOT( setContrast( int ) ) );
	disconnect( thresholdSlider, SIGNAL( valueChanged(int) ), m_videoFrame ,SLOT( threshold(int) ) );
	disconnect( scaleBox, SIGNAL( toggled(bool) ), m_videoFrame ,SLOT( scaleImage(bool) ) );
	disconnect( scaleValSlider, SIGNAL( valueChanged(int) ), m_videoFrame ,SLOT( scaleValue(int) ) );
	disconnect( darksBox, SIGNAL( toggled(bool) ), m_videoFrame ,SLOT( substractDarks(bool) ) );
	disconnect( Take_Dark_Button, SIGNAL( clicked() ), m_videoFrame ,SLOT( takeDark() ) );
	disconnect( Clear_Darks_Button, SIGNAL( clicked() ), m_videoFrame ,SLOT( clearDarks() ) );
	disconnect( m_videoFrame, 0, 0, 0 );
	disconnect( m_pTimer, SIGNAL( timeout() ), m_videoFrame, SLOT( timerDone() ) );

	m_pTimer->stop();        	// stop 1 seconds timer
	delete m_pTimer;
	qDebug("Deleting VideoFrame");
	delete m_videoFrame;
	qDebug("Deleting VideoDisplay");
}

void VideoDisplay::storeSlot(){
	m_videoFrame->saveFrame();
}

/** No descriptions */
void VideoDisplay::setSpotInfoSlot( double p_dX, double p_dY )
{
	SpotX->setText( QString::number(p_dX) );
	SpotY->setText( QString::number(p_dY) );
}

/** No descriptions */
void VideoDisplay::setGRefInfoSlot( double p_dX, double p_dY )
{
	GRefX->setText( QString::number( p_dX ) );
	GRefY->setText( QString::number( p_dY ) );
}

/** Set the motor of the panel to use.
 */
void VideoDisplay::setMotor( AMCMotor* p_pMotor )
{
	m_pMotor = p_pMotor;
}

/** Set the motor of the panel to use.
 */
void VideoDisplay::setPanel( AMCMirrorPanel* p_pPanel )
{
	m_pPanel = p_pPanel;
	QString info;
	Panel_Info->setTitle( info.sprintf( "Panel: %d,%d", m_pPanel->i(), m_pPanel->j() ) );
	Device_Label->setText( info.sprintf( "Device: /dev/ttyS%d", m_pPanel->port()+3 ) );
	Box_Label->setText( info.sprintf( "Box: %02d", m_pPanel->box() ) );
	Driver_Label->setText( info.sprintf( "Driver: %d", m_pPanel->driver() ) );
	Laser_Check->setChecked( m_pPanel->isLaserOn() );

	RefX->setText( QString::number(	m_pPanel->getLaserX() ) );
	RefY->setText( QString::number( m_pPanel->getLaserY() ) );
	m_videoFrame->setRef( m_pPanel->getLaserX(), m_pPanel->getLaserY() );
	double dX, dY;
	dX = m_pPanel->getLaserX();
  m_pPanel->getCorrectedRef( dX, dY);
	m_videoFrame->setCorrRef( dX, dY );
	GRefX->setText( QString::number( g_dRefX ) );
	GRefY->setText( QString::number( g_dRefY ) );

  enableButtons( true );
}

/** Switch the laser on. */
void VideoDisplay::switchLaserSlot( bool p_zOn )
{
	try
	{
		m_pMotor->switchLaser( p_zOn );
		m_pPanel->setLaserOn( p_zOn );
	}
	catch( AMCError& e )
	{
		QMessageBox::information( this,
								  "AMCError",
  							  	  e.getErrorText(),
  							  	  QMessageBox::Ok | QMessageBox:: Default );
	}		
}

/** Center the motors. */
void VideoDisplay::centerSlot()
{
//	halt( true );
	enableButtons( false );
	g_theApp->processEvents();

	qDebug("Creating center thread");
	m_pCenterThread = new CenterThread( this );
	m_pCenterThread->setPanel( m_pPanel );
	qDebug("Starting center thread");
	m_pCenterThread->start();

/*	try
	{
		m_pMotor->centerMotors();
		m_pMotor->waitForMotors( m_pMotor->calcTimeout( 17000 )+2 );
	}
	catch( AMCError& e )
	{
		QMessageBox::information( this,
								  "AMCError",
  							  	  e.getErrorText(),
  							  	  QMessageBox::Ok | QMessageBox:: Default );
		enableButtons( true );
		halt( false );
		return;
	}
	m_pPanel->setX( 0 );
	m_pPanel->setY( 0 );
	enableButtons( true );
	halt( false );
*/
}

/** Goto the position given by X. */
void VideoDisplay::goXSlot()
{
  bool zOk;
	int iX = X_Goto_Value->text().toInt( &zOk );
	moveMotors( iX - m_pPanel->getX(), 0 );
}

/** Goto the position given by Y. */
void VideoDisplay::goYSlot()
{
  bool zOk;
	int iY = Y_Goto_Value->text().toInt( &zOk );
	moveMotors( 0, iY - m_pPanel->getY() );
}

/** Move motors relative by the given amount of steps. */
void VideoDisplay::moveMotors( int p_iX, int p_iY )
{
	if( (p_iX == 0) && (p_iY == 0) )
		return;
	
	enableButtons( false );
  halt( true );
	try
	{
		m_pMotor->moveMotors( p_iX, p_iY );
		m_pMotor->waitForMotors( m_pMotor->calcTimeout( MAX( abs(p_iX), abs(p_iY) ) ) );			
	
		int x = m_pPanel->getX() + p_iX;
		int y = m_pPanel->getY() + p_iY;
		m_pPanel->setX( x );
		m_pPanel->setY( y );
	}
	catch( AMCError& e )
	{
		QMessageBox::information( this,
								  "AMCError",
  							  	  e.formatError(),
  							  	  QMessageBox::Ok | QMessageBox:: Default );
	}
	enableButtons( true );

  halt( false );
	return;
	
}


/** No descriptions */
void VideoDisplay::enableButtons( bool p_zEnable ) const
{
	Laser_Check->setEnabled( p_zEnable );
	Calibrate_Button->setEnabled( p_zEnable );
	DefocusButton->setEnabled( p_zEnable );
	FocusButton->setEnabled( p_zEnable );
	DefocusAllButton->setEnabled( p_zEnable );
	
	X_Inc_100_Button->setEnabled( p_zEnable );
	X_Inc_10_Button->setEnabled( p_zEnable );
	X_Goto_Value->setEnabled( p_zEnable );
	X_Go_Button->setEnabled( p_zEnable );
	X_Dec_10_Button->setEnabled( p_zEnable );
	X_Dec_100_Button->setEnabled( p_zEnable );
	
	Y_Inc_100_Button->setEnabled( p_zEnable );
	Y_Inc_10_Button->setEnabled( p_zEnable );
	Y_Goto_Value->setEnabled( p_zEnable );
	Y_Go_Button->setEnabled( p_zEnable );
	Y_Dec_10_Button->setEnabled( p_zEnable );
	Y_Dec_100_Button->setEnabled( p_zEnable );

	if( p_zEnable )
	{
		int x = m_pPanel->getX();
		int y = m_pPanel->getY();
		
		QString str;
		X_Mot_Box->setTitle( str.sprintf("X=%d",x ) );
		Y_Mot_Box->setTitle( str.sprintf("Y=%d",y ) );
		X_Inc_100_Button->setEnabled( (x > 2400) ? false : true );
		X_Inc_10_Button->setEnabled( (x > 2490) ? false : true );
		X_Dec_10_Button->setEnabled( (x < -2490) ? false : true );
		X_Dec_100_Button->setEnabled( (x < -2400) ? false : true );
		
		Y_Inc_100_Button->setEnabled( (y > 2400) ? false : true );
		Y_Inc_10_Button->setEnabled( (y > 2490) ? false : true );
		Y_Dec_10_Button->setEnabled( (y < -2490) ? false : true );
		Y_Dec_100_Button->setEnabled( (y < -2400) ? false : true );

	}
	
	Center_Button->setEnabled( p_zEnable );
}


/** Save actual laser position as reference. */
void VideoDisplay::saveRefSlot()
{
	double x = SpotX->text().toDouble();
	double y = SpotY->text().toDouble();

	if( x != -1)
	{
		m_pPanel->setLaserX( x );
		m_pPanel->setLaserY( y );
		m_pPanel->setRefX( m_pPanel->getX() );
		m_pPanel->setRefY( m_pPanel->getY() );
		RefX->setText( QString::number( x ) );
		RefY->setText( QString::number( y ) );
		m_videoFrame->setRef( x, y );
	}
}

/** Save laser position as global reference. */
void VideoDisplay::saveGRefSlot()
{
	double x = SpotX->text().toDouble();
	double y = SpotY->text().toDouble();

	if( x != -1)
	{
		g_dRefX = x;
		g_dRefY = y;
		GRefX->setText( QString::number( x ) );
		GRefY->setText( QString::number( y ) );
	}
}

/** Adjust laser spot to the reference position of this panel */
void VideoDisplay::adjustRefSlot()
{
	double dRefX;
	double dRefY;
//	dRefX = m_pPanel->getLaserX();
//	dRefY = m_pPanel->getLaserY();
  m_pPanel->getCorrectedRef( dRefX, dRefY );

  qDebug("Reference: %5.1f %5.1f", dRefX, dRefY );
	if( ( dRefX < 0. ) && ( dRefY < 0. ) )
		return;
	double dDx, dDy;
	dDx = m_videoFrame->m_dCenterX - CAMERA_X;
	dDy = m_videoFrame->m_dCenterY - CAMERA_Y;
	dRefX += dDx;
	dRefY += dDy;
	int iStepX, iStepY;
	calcSteps( dRefX, dRefY, iStepX, iStepY );
	moveMotors( iStepX, iStepY );
}

/** Adjust laser spot to the global reference position */
void VideoDisplay::adjustGRefSlot()
{
	if( ( g_dRefX == -1. ) && ( g_dRefY == -1. ) )
		return;
	int iStepX, iStepY;
	calcSteps( g_dRefX, g_dRefY, iStepX, iStepY );
	moveMotors( iStepX, iStepY );
}

void VideoDisplay::calcSteps( double p_dRefX, double p_dRefY, int& p_iStepX, int& p_iStepY )
{
	p_iStepX = p_iStepY = 0;
	double dVSpotX = SpotX->text().toDouble();
	double dVSpotY = SpotY->text().toDouble();
	if( ( dVSpotX < 0. ) && ( dVSpotY < 0. ) )
		return;
	qDebug("Spot at x,y: %5.1f %5.1f", dVSpotX, dVSpotY );
	qDebug("Ref  at x,y: %5.1f %5.1f", p_dRefX, p_dRefY );


	double dX = p_dRefX - dVSpotX;
	double dY = p_dRefY - dVSpotY;

	m_pPanel->calcSteps( dX, dY, p_iStepX, p_iStepY );
	qDebug("Steps till reference: %4d %4d", p_iStepX, p_iStepY );	
}

/** No descriptions */
void VideoDisplay::defocusAllSlot()
{
	enableButtons( false );
  QString qsMsg("Defocusing all mirrors");
	AMCProgDlg* pDialog = new AMCProgDlg( DEFOCUS_THREAD_CTRL, this, qsMsg, true );
	pDialog->setText( qsMsg );
	pDialog->start();
	pDialog->exec();
	delete pDialog;

	try
	{
		// reselect the currently selected panel
		m_pMotor->unselectBox();
		int iBox = m_pPanel->box();
		m_pMotor->selectBox( iBox );
		int iDriver = m_pPanel->driver();
		m_pMotor->selectDriver( iDriver );
	}
	catch( AMCError& e )
	{
		QMessageBox::information( this,
								  "AMCError",
 						  	  e.formatError(),
 						  	  QMessageBox::Ok | QMessageBox:: Default );
	}
	enableButtons( true );
	return;
}

/** No descriptions */
void VideoDisplay::defocusSlot()
{
	int x = m_pPanel->getRefX();
	int y = m_pPanel->getRefY();

	if( x >= 0 )
		x -= 1500;
	else
		x += 1500;

	if( y >= 0 )
		y -= 1500;
	else
		y += 1500;

	x -= m_pPanel->getX();
	y -= m_pPanel->getY();
	moveMotors( x, y );

  return;
}

/** No descriptions */
void VideoDisplay::focusAllSlot()
{
	enableButtons( false );

  QString qsMsg("Focusing all mirrors");
	AMCProgDlg* pDialog = new AMCProgDlg( DEFOCUS_THREAD_CTRL, this, qsMsg, true );
	pDialog->setText( qsMsg );
	pDialog->start();
	pDialog->exec();
	delete pDialog;

	try
	{
		// reselect the currently selected panel
		m_pMotor->unselectBox();
		int iBox = m_pPanel->box();
		m_pMotor->selectBox( iBox );
		int iDriver = m_pPanel->driver();
		m_pMotor->selectDriver( iDriver );
	}
	catch( AMCError& e )
	{
		QMessageBox::information( this,
								  "AMCError",
 						  	  e.formatError(),
 						  	  QMessageBox::Ok | QMessageBox:: Default );
	}
	enableButtons( true );
	return;
}

/** No descriptions */
void VideoDisplay::focusSlot()
{
	int x = m_pPanel->getRefX();
	int y = m_pPanel->getRefY();

	x -= m_pPanel->getX();
	y -= m_pPanel->getY();
	moveMotors( x, y );

  return;
}

/** No descriptions */
void VideoDisplay::takeDarkSlot()
{
//	m_videoFrame->m_zTakeDark = true;
}

/** No descriptions */
void VideoDisplay::clearDarksSlot(){
	m_videoFrame->clearDarks();
}

void VideoDisplay::customEvent(QCustomEvent *e)
{
  if ( e->type() == THREAD_EVENT )
	{
		ThreadEvent* te = (ThreadEvent*)e;
	}

  if ( e->type() == THREAD_ERROR_EVENT )
	{
		ThreadErrorEvent* tee = (ThreadErrorEvent*)e;
		QMessageBox::information( this,
								  "AMCError",
  							  tee->getMessage(),
  							  QMessageBox::Ok | QMessageBox:: Default );
		m_pCenterThread->wait();		
		qDebug("Deleting center thread");
		delete m_pCenterThread;
		enableButtons( true );
		g_pLog->logError( tee->getMessage(), tee->getError() );
	}

  if ( e->type() == THREAD_END_EVENT )
	{
		m_pCenterThread->wait();		
		delete m_pCenterThread;
		enableButtons( true );
	}

}
/** No descriptions */
void VideoDisplay::calibrateSlot()
{
  qDebug("Stoping VideoFrame");
	halt( true );
  qDebug("Disabling buttons");
	enableButtons( false );
	CalibratePanelDialog* pDlg = new CalibratePanelDialog( this, "Calibrate Panel", g_pFG );
	pDlg->setPanel( m_pPanel );
	pDlg->setTSigmas( thresholdSlider->value() );
	int iRet = pDlg->exec();
	Laser_Check->setChecked( m_pPanel->isLaserOn() );
	delete pDlg;
  qDebug("Enabling buttons");
	enableButtons( true );
  qDebug("Restarting VideoFrame");
	halt( false );
}

/** No descriptions */
void VideoDisplay::accept()
{
	g_theMirror->saveMirrorPanels();
	VideoDisplayBase::accept();
}

/** No descriptions */
void VideoDisplay::halt( bool p_zHalt )
{
	if ( p_zHalt == true )
		m_pTimer->stop();
	else
		m_pTimer->start( 1000 );

	m_videoFrame->halt( p_zHalt );
}
