/***************************************************************************
                          altvstime.cpp  -  description
                             -------------------
    begin                : wed nov 17 08:05:11 CET 2002
    copyright            : (C) 2002-2003 by Pablo de Vicente
    email                : vicente@oan.es
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 <tqlayout.h>
#include <tqpainter.h>
#include <tdelocale.h>
#include <klineedit.h>
#include <tdelistbox.h>
#include <kpushbutton.h>
#include <kdialogbase.h>

#include "altvstime.h"
#include "altvstimeui.h"
#include "dms.h"
#include "dmsbox.h"
#include "kstars.h"
#include "kstarsdata.h"
#include "skypoint.h"
#include "skyobject.h"
#include "skyobjectname.h"
#include "ksnumbers.h"
#include "objectnamelist.h"
#include "simclock.h"
#include "finddialog.h"
#include "locationdialog.h"

#include "kstarsdatetime.h"
#include "libtdeedu/extdate/extdatetimeedit.h"

AltVsTime::AltVsTime( TQWidget* parent)  :
	KDialogBase( KDialogBase::Plain, i18n( "Altitude vs. Time" ), Close, Close, parent )
{
	ks = (KStars*) parent;

	TQFrame *page = plainPage();

	setMainWidget(page);
	topLayout = new TQVBoxLayout( page, 0, spacingHint() );

	View = new AVTPlotWidget( -12.0, 12.0, -90.0, 90.0, page );
	View->setMinimumSize( 400, 400 );
	View->setSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding );
	View->setXAxisType( KStarsPlotWidget::TIME );
	View->setYAxisType( KStarsPlotWidget::ANGLE );
	View->setShowGrid( false );
	View->setXAxisLabel( i18n( "Local Time" ) );
	View->setXAxisLabel2( i18n( "Local Sidereal Time" ) );
	View->setYAxisLabel( i18n( "the angle of an object above (or below) the horizon", "Altitude" ) );

	avtUI = new AltVsTimeUI( page );
	avtUI->raBox->setDegType( false );
	avtUI->decBox->setDegType( true );

	//POST-3.2
	//Doesn't make sense to manually adjust long/lat unless we can modify TZ also
	avtUI->longBox->setReadOnly( true );
	avtUI->latBox->setReadOnly( true );

	topLayout->addWidget( View );
	topLayout->addWidget( avtUI );

	geo = ks->geo();

	DayOffset = 0;
	showCurrentDate();
	if ( getDate().time().hour() > 12 ) DayOffset = 1;

	avtUI->longBox->show( geo->lng() );
	avtUI->latBox->show( geo->lat() );

	computeSunRiseSetTimes();

	setLSTLimits();
	View->updateTickmarks();

	connect( avtUI->browseButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotBrowseObject() ) );
	connect( avtUI->cityButton,   TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotChooseCity() ) );
	connect( avtUI->updateButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotUpdateDateLoc() ) );
	connect( avtUI->clearButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotClear() ) );
	connect( avtUI->addButton,   TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotAddSource() ) );
	connect( avtUI->nameBox, TQT_SIGNAL( returnPressed() ), this, TQT_SLOT( slotAddSource() ) );
	connect( avtUI->raBox,   TQT_SIGNAL( returnPressed() ), this, TQT_SLOT( slotAddSource() ) );
	connect( avtUI->decBox,  TQT_SIGNAL( returnPressed() ), this, TQT_SLOT( slotAddSource() ) );
	connect( avtUI->clearFieldsButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotClearBoxes() ) );
	connect( avtUI->longBox, TQT_SIGNAL( returnPressed() ), this, TQT_SLOT( slotAdvanceFocus() ) );
	connect( avtUI->latBox,  TQT_SIGNAL( returnPressed() ), this, TQT_SLOT( slotAdvanceFocus() ) );
	connect( avtUI->PlotList, TQT_SIGNAL( highlighted(int) ), this, TQT_SLOT( slotHighlight() ) );

	pList.setAutoDelete(FALSE);
	deleteList.setAutoDelete(TRUE); //needed for skypoints which may be created in this class

	//ther edit boxes should not pass on the return key!
	avtUI->nameBox->setTrapReturnKey( true );
	avtUI->raBox->setTrapReturnKey( true );
	avtUI->decBox->setTrapReturnKey( true );

	setMouseTracking( true );
}

AltVsTime::~AltVsTime()
{
    // no need to delete child widgets, TQt does it all for us
}

void AltVsTime::slotAddSource(void) {
	bool objFound( false );

	//First, attempt to find the object name in the list of known objects
	if ( ! avtUI->nameBox->text().isEmpty() ) {
		ObjectNameList &ObjNames = ks->data()->ObjNames;
		TQString text = avtUI->nameBox->text().lower();

		for( SkyObjectName *name = ObjNames.first( text ); name; name = ObjNames.next() ) {
			if ( name->text().lower() == text ) {
				//object found
				SkyObject *o = name->skyObject();
				processObject( o );

				objFound = true;
				break;
			}
		}

		if ( !objFound ) kdDebug() << "No object named " << avtUI->nameBox->text() << " found." << endl;
	}

	//Next, if the name, RA, and Dec fields are all filled, construct a new skyobject
	//with these parameters
	if ( !objFound && ! avtUI->nameBox->text().isEmpty() && ! avtUI->raBox->text().isEmpty() && ! avtUI->decBox->text().isEmpty() ) {
		bool ok( true );
		dms newRA( 0.0 ), newDec( 0.0 );
		newRA = avtUI->raBox->createDms( false, &ok );
		if ( ok ) newDec = avtUI->decBox->createDms( true, &ok );

		//If the epochName is blank (or any non-double), we assume J2000
		//Otherwise, precess to J2000.
		KStarsDateTime dt;
		dt.setFromEpoch( getEpoch( avtUI->epochName->text() ) );
		long double jd = dt.djd();
		if ( jd != J2000 ) {
			SkyPoint ptest( newRA, newDec );
			ptest.precessFromAnyEpoch( jd, J2000 );
			newRA.setH( ptest.ra()->Hours() );
			newDec.setD( ptest.dec()->Degrees() );
		}

		//make sure the coords do not already exist from another object
		bool found(false);
		for ( SkyPoint *p = pList.first(); p; p = pList.next() ) {
			//within an arcsecond?
			if ( fabs( newRA.Degrees() - p->ra()->Degrees() ) < 0.0003 && fabs( newDec.Degrees() - p->dec()->Degrees() ) < 0.0003 ) {
				found = true;
				break;
			}
		}
		if ( found ) {
			kdDebug() << "This point is already displayed; I will not duplicate it." << endl;
			ok = false;
		}

		if ( ok ) {
			SkyObject *obj = new SkyObject( 8, newRA, newDec, 1.0, avtUI->nameBox->text() );
			deleteList.append( obj ); //this object will be deleted when window is destroyed
			processObject( obj );
		}

	//If the Ra and Dec boxes are filled, but the name field is empty,
	//move input focus to nameBox`
	} else if ( avtUI->nameBox->text().isEmpty() && ! avtUI->raBox->text().isEmpty() && ! avtUI->decBox->text().isEmpty() ) {
		avtUI->nameBox->TQWidget::setFocus();

	//nameBox is empty, and one of the ra or dec fields is empty.  Move input focus to empty coord box
	} else if ( avtUI->raBox->text().isEmpty() ) {
		avtUI->raBox->TQWidget::setFocus();
	} else if ( avtUI->decBox->text().isEmpty() ) {
		avtUI->decBox->TQWidget::setFocus();
	}

	View->repaint(false);
}

//Use find dialog to choose an object
void AltVsTime::slotBrowseObject(void) {
	FindDialog fd(ks);
	if ( fd.exec() == TQDialog::Accepted ) {
		SkyObject *o = fd.currentItem()->objName()->skyObject();
		processObject( o );
	}

	View->repaint();
}

void AltVsTime::processObject( SkyObject *o, bool forceAdd ) {
	KSNumbers *num = new KSNumbers( getDate().djd() );
	KSNumbers *oldNum = 0;

	//If the object is in the solar system, recompute its position for the given epochLabel
	if ( o && o->isSolarSystem() ) {
		oldNum = new KSNumbers( ks->data()->ut().djd() );
		o->updateCoords( num, true, geo->lat(), ks->LST() );
	}

	//precess coords to target epoch
	o->updateCoords( num );

	//If this point is not in list already, add it to list
	bool found(false);
	for ( SkyPoint *p = pList.first(); p; p = pList.next() ) {
		if ( o->ra()->Degrees() == p->ra()->Degrees() && o->dec()->Degrees() == p->dec()->Degrees() ) {
			found = true;
			break;
		}
	}
	if ( found && !forceAdd ) kdDebug() << "This point is already displayed; I will not duplicate it." << endl;
	else {
		pList.append( (SkyPoint*)o );

		//make sure existing curves are thin and red
		for ( int i=0; i < View->objectCount(); ++i ) {
			KPlotObject *obj = View->object( i );
			if ( obj->size() == 2 ) {
				obj->setColor( "red" );
				obj->setSize( 1 );
			}
		}

		//add new curve with width=2, and color=white
		KPlotObject *po = new KPlotObject( "", "white", KPlotObject::CURVE, 2, KPlotObject::SOLID );
		for ( double h=-12.0; h<=12.0; h+=0.5 ) {
			po->addPoint( new DPoint( h, findAltitude( o, h ) ) );
		}
		View->addObject( po );

		avtUI->PlotList->insertItem( o->translatedName() );
		avtUI->PlotList->setCurrentItem( avtUI->PlotList->count() - 1 );
		avtUI->raBox->showInHours(o->ra() );
		avtUI->decBox->showInDegrees(o->dec() );
		avtUI->nameBox->setText(o->translatedName() );

		//Set epochName to epoch shown in date tab
		avtUI->epochName->setText( TQString().setNum( getDate().epoch() ) );
	}
	kdDebug() << "Currently, there are " << View->objectCount() << " objects displayed." << endl;

	//restore original position
	if ( o->isSolarSystem() ) {
		o->updateCoords( oldNum, true, ks->geo()->lat(), ks->LST() );
                delete oldNum;
	}
        o->EquatorialToHorizontal( ks->LST(), ks->geo()->lat() );
	delete num;
}

double AltVsTime::findAltitude( SkyPoint *p, double hour ) {
	hour += 24.0*(double)DayOffset;

	//getDate converts the user-entered local time to UT
	KStarsDateTime ut = getDate().addSecs( hour*3600.0 );

	dms LST = geo->GSTtoLST( ut.gst() );
	p->EquatorialToHorizontal( &LST, geo->lat() );
	return p->alt()->Degrees();
}

void AltVsTime::slotHighlight(void) {
	int iPlotList = avtUI->PlotList->currentItem();

	//highlight the curve of the selected object
	for ( int i=0; i<View->objectCount(); ++i ) {
		KPlotObject *obj = View->object( i );

		if ( i == iPlotList ) {
			obj->setSize( 2 );
			obj->setColor( "white" );
		} else {
			obj->setSize( 1 );
			obj->setColor( "red" );
		}
	}

	View->update();

	int index = 0;
	for ( SkyPoint *p = pList.first(); p; p = pList.next() ) {
		if ( index == iPlotList ) {
			avtUI->raBox->showInHours(p->ra() );
			avtUI->decBox->showInDegrees(p->dec() );
			avtUI->nameBox->setText(avtUI->PlotList->currentText() );
		}
		++index;
	}
}

//move input focus to the next logical widget
void AltVsTime::slotAdvanceFocus(void) {
	if ( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name() == TQString( "nameBox" ) ) avtUI->addButton->setFocus();
	if ( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name() == TQString( "raBox" ) ) avtUI->decBox->setFocus();
	if ( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name() == TQString( "decbox" ) ) avtUI->addButton->setFocus();
	if ( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name() == TQString( "longBox" ) ) avtUI->latBox->setFocus();
	if ( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()))->name() == TQString( "latBox" ) ) avtUI->updateButton->setFocus();
}

void AltVsTime::slotClear(void) {
	if ( pList.count() ) pList.clear();
	if ( deleteList.count() ) deleteList.clear();
	avtUI->PlotList->clear();
	avtUI->nameBox->clear();
	avtUI->raBox->clear();
	avtUI->decBox->clear();
        avtUI->epochName->clear();
	View->clearObjectList();
	View->repaint();
}

void AltVsTime::slotClearBoxes(void) {
	avtUI->nameBox->clear();
	avtUI->raBox->clear() ;
	avtUI->decBox->clear();
	avtUI->epochName->clear();
}

void AltVsTime::computeSunRiseSetTimes() {
	//Determine the time of sunset and sunrise for the desired date and location
	//expressed as doubles, the fraction of a full day.
	KStarsDateTime today = getDate();

	SkyObject *oSun = (SkyObject*) ks->data()->PCat->planetSun();
	double sunRise = -1.0 * oSun->riseSetTime( today.djd() + 1.0, geo, true ).secsTo(TQTime()) / 86400.0;
	double sunSet = -1.0 * oSun->riseSetTime( today.djd(), geo, false ).secsTo(TQTime()) / 86400.0;

	//check to see if Sun is circumpolar
	//requires temporary repositioning of Sun to target date
	KSNumbers *num = new KSNumbers( today.djd() );
	KSNumbers *oldNum = new KSNumbers( ks->data()->ut().djd() );
	dms LST = geo->GSTtoLST( getDate().gst() );
	oSun->updateCoords( num, true, geo->lat(), &LST );
	if ( oSun->checkCircumpolar( geo->lat() ) ) {
		if ( oSun->alt()->Degrees() > 0.0 ) {
			//Circumpolar, signal it this way:
			sunRise = 0.0;
			sunSet = 1.0;
		} else {
			//never rises, signal it this way:
			sunRise = 0.0;
			sunSet = -1.0;
		}
	}

	//Notify the View about new sun rise/set times:
	View->setSunRiseSetTimes( sunRise, sunSet );

	//Restore Sun coordinates:
	oSun->updateCoords( oldNum, true, ks->geo()->lat(), ks->LST() );
        oSun->EquatorialToHorizontal( ks->LST(), ks->geo()->lat() );

	delete num;
	delete oldNum;
}

void AltVsTime::slotUpdateDateLoc(void) {
	KStarsDateTime today = getDate();
	KSNumbers *num = new KSNumbers( today.djd() );
	KSNumbers *oldNum = 0;
	dms LST = geo->GSTtoLST( today.gst() );

	//First determine time of sunset and sunrise
	computeSunRiseSetTimes();

	for ( unsigned int i = 0; i < avtUI->PlotList->count(); ++i ) {
		TQString oName = avtUI->PlotList->text( i ).lower();
		ObjectNameList &ObjNames = ks->data()->ObjNames;
		bool objFound(false);

		for( SkyObjectName *name = ObjNames.first( oName ); name; name = ObjNames.next() ) {
			if ( name->text().lower() == oName ) {
				//object found
				SkyObject *o = name->skyObject();

				//If the object is in the solar system, recompute its position for the given date
				if ( o->isSolarSystem() ) {
					oldNum = new KSNumbers( ks->data()->ut().djd() );
					o->updateCoords( num, true, geo->lat(), &LST );
				}

				//precess coords to target epoch
				o->updateCoords( num );

				//update pList entry
				pList.replace( i, (SkyPoint*)o );

				KPlotObject *po = new KPlotObject( "", "white", KPlotObject::CURVE, 1, KPlotObject::SOLID );
				for ( double h=-12.0; h<=12.0; h+=0.5 ) {
					po->addPoint( new DPoint( h, findAltitude( o, h ) ) );
				}
				View->replaceObject( i, po );

				//restore original position
				if ( o->isSolarSystem() ) {
					o->updateCoords( oldNum, true, ks->data()->geo()->lat(), ks->LST() );
                                        delete oldNum;
					oldNum = 0;
				}
                                o->EquatorialToHorizontal( ks->LST(), ks->data()->geo()->lat() );

				objFound = true;
				break;
			}
		}

		if ( ! objFound ) {  //assume unfound object is a custom object
			pList.at(i)->updateCoords( num ); //precess to desired epoch

			KPlotObject *po = new KPlotObject( "", "white", KPlotObject::CURVE, 1, KPlotObject::SOLID );
			for ( double h=-12.0; h<=12.0; h+=0.5 ) {
				po->addPoint( new DPoint( h, findAltitude( pList.at(i), h ) ) );
			}
			View->replaceObject( i, po );
		}
	}

	if ( getDate().time().hour() > 12 ) DayOffset = 1;
	else DayOffset = 0;

	setLSTLimits();
	slotHighlight();
	View->repaint();

	delete num;
}

void AltVsTime::slotChooseCity(void) {
	LocationDialog ld(ks);
	if ( ld.exec() == TQDialog::Accepted ) {
		GeoLocation *newGeo = ld.selectedCity();
		if ( newGeo ) {
			geo = newGeo;
			avtUI->latBox->showInDegrees( geo->lat() );
			avtUI->longBox->showInDegrees( geo->lng() );
		}
	}
}

int AltVsTime::currentPlotListItem() const {
	return avtUI->PlotList->currentItem();
}

void AltVsTime::setLSTLimits(void) {
	//UT at noon on target date
	KStarsDateTime ut = getDate().addSecs( ((double)DayOffset + 0.5)*86400. );

	dms lst = geo->GSTtoLST( ut.gst() );
	View->setSecondaryLimits( lst.Hours(), lst.Hours() + 24.0, -90.0, 90.0 );
}

void AltVsTime::showCurrentDate (void)
{
	KStarsDateTime dt = KStarsDateTime::currentDateTime();
	if ( dt.time() > TQTime( 12, 0, 0 ) ) dt = dt.addDays( 1 );
	avtUI->dateBox->setDate( dt.date() );
}

KStarsDateTime AltVsTime::getDate (void)
{
	//convert midnight local time to UT:
	KStarsDateTime dt( avtUI->dateBox->date(), TQTime() );
	dt = geo->LTtoUT( dt );
	return dt;
}

double AltVsTime::getEpoch (TQString eName)
{
	//If Epoch field not a double, assume J2000
	bool ok(false);
	double epoch = eName.toDouble(&ok);

	if ( !ok ) {
		kdDebug() << "Invalid Epoch.  Assuming 2000.0." << endl;
		return 2000.0;
	}

	return epoch;
}

AVTPlotWidget::AVTPlotWidget( double x1, double x2, double y1, double y2, TQWidget *parent, const char* name )
	: KStarsPlotWidget( x1, x2, y1, y2, parent, name )
{
	//Default SunRise/SunSet values
	SunRise = 0.25;
	SunSet = 0.75;
}

void AVTPlotWidget::mousePressEvent( TQMouseEvent *e ) {
	mouseMoveEvent( e );
}

void AVTPlotWidget::mouseMoveEvent( TQMouseEvent *e ) {
	TQRect checkRect( leftPadding(), topPadding(), PixRect.width(), PixRect.height() );
	int Xcursor = e->x();
	int Ycursor = e->y();

	if ( ! checkRect.contains( e->x(), e->y() ) ) {
		if ( e->x() < checkRect.left() )   Xcursor = checkRect.left();
		if ( e->x() > checkRect.right() )  Xcursor = checkRect.right();
		if ( e->y() < checkRect.top() )    Ycursor = checkRect.top();
		if ( e->y() > checkRect.bottom() ) Ycursor = checkRect.bottom();
	}

	Xcursor -= leftPadding();
	Ycursor -= topPadding();

	TQPixmap buffer2( *buffer );
	TQPainter p;
	p.begin( &buffer2 );
	p.translate( leftPadding(), topPadding() );
	p.setPen( TQPen( "grey", 1, SolidLine ) );
	p.drawLine( Xcursor, 0, Xcursor, PixRect.height() );
	p.drawLine( 0, Ycursor, PixRect.width(), Ycursor );
	p.end();
	bitBlt( this, 0, 0, &buffer2 );
}

void AVTPlotWidget::paintEvent( TQPaintEvent */*e*/ ) {
	TQPainter p;

	p.begin( buffer );
	p.fillRect( 0, 0, width(), height(), bgColor() );

	p.translate( leftPadding(), topPadding() );

	int pW = PixRect.width();
	int pH = PixRect.height();

	//draw daytime sky if the Sun rises for the current date/location
	//(when Sun does not rise, SunSet = -1.0)
	if ( SunSet != -1.0 ) {
		//If Sun does not set, then just fill the daytime sky color
		if ( SunSet == 1.0 ) {
			p.fillRect( 0, 0, pW, int(0.5*pH), TQColor( 0, 100, 200 ) );
		} else {
			//Display centered on midnight, so need to modulate dawn/dusk by 0.5
			int dawn = int(pW*(0.5 + SunRise));
			int dusk = int(pW*(SunSet - 0.5));
			p.fillRect( 0, 0, dusk, int(0.5*pH), TQColor( 0, 100, 200 ) );
			p.fillRect( dawn, 0, pW - dawn, int(0.5*pH), TQColor( 0, 100, 200 ) );

			//draw twilight gradients
			unsigned short int W = 40;
			for ( unsigned short int i=0; i<W; ++i ) {
				p.setPen( TQColor( 0, int(100*i/W), 200*i/W ) );
				p.drawLine( dusk - (i-20), 0, dusk - (i-20), pH );
				p.drawLine( dawn + (i-20), 0, dawn + (i-20), pH );
			}
		}
	}

	//draw ground
	p.fillRect( 0, int(0.5*pH), pW, int(0.5*pH), TQColor( "#002200" ) );

	drawBox( &p );
	drawObjects( &p );

	//Add vertical line indicating "now"
	TQTime t = TQTime::currentTime();
	double x = 12.0 + t.hour() + t.minute()/60.0 + t.second()/3600.0;
	while ( x > 24.0 ) x -= 24.0;
	int ix = int(x*pW/24.0); //convert to screen pixel coords
	p.setPen( TQPen( "white", 2, DotLine ) );
	p.drawLine( ix, 0, ix, pH );

	p.end();

	bitBlt( this, 0, 0, buffer );
}

#include "altvstime.moc"
