/***************************************************************************
                 ksdssdownloader.cpp  -  K Desktop Planetarium
                             -------------------
    begin                : Tue 05 Jan 2016 03:39:18 CST
    copyright            : (c) 2016 by Akarsh Simha
    email                : akarsh.simha@kdemail.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/* Qt Includes */
#include <QString>
#include <QImage>
#include <QImageWriter>
#include <QUrl>
#include <QMimeDatabase>
#include <QMimeType>
#include <QTemporaryFile>

/* Project Includes */
#include "ksdssdownloader.h"
#include "ksdssimage.h"
#include "skypoint.h"
#include "skyobject.h"
#include "deepskyobject.h"
#include "dms.h"
#include "Options.h"
#include "auxiliary/filedownloader.h"

KSDssDownloader::KSDssDownloader( QObject *parent ) : QObject( parent ) {
    connect(this, &KSDssDownloader::downloadCanceled, this, [&](){deleteLater();});
    m_VersionPreference << "poss2ukstu_blue" << "poss2ukstu_red" << "poss1_blue" << "poss1_red" << "quickv" << "poss2ukstu_ir";
    m_TempFile.open();
}

KSDssDownloader::KSDssDownloader(const SkyPoint * const p, const QString &destFileName, const std::function<void( bool )> &slotDownloadReady, QObject *parent ) : QObject( parent ) {
    // Initialize version preferences. FIXME: This must be made a
    // user-changeable option just in case someone likes red

    connect(this, &KSDssDownloader::downloadCanceled, this, [&](){deleteLater();});

    m_VersionPreference << "poss2ukstu_blue" << "poss2ukstu_red" << "poss1_blue" << "poss1_red" << "quickv" << "poss2ukstu_ir";
    m_TempFile.open();
    connect( this, &KSDssDownloader::downloadComplete, slotDownloadReady );
    startDownload( p, destFileName );
}


QString KSDssDownloader::getDSSURL( const SkyPoint * const p, const QString &version, struct KSDssImage::Metadata *md ) {
        const DeepSkyObject *dso = 0;
        double height, width;

        double dss_default_size = Options::defaultDSSImageSize();
        double dss_padding = Options::dSSPadding();

        Q_ASSERT( p );
        Q_ASSERT( dss_default_size > 0.0 && dss_padding >= 0.0 );

        dso = dynamic_cast<const DeepSkyObject *>( p );

        // Decide what to do about the height and width
        if( dso ) {
            // For deep-sky objects, use their height and width information
            double a, b, pa;
            a = dso->a();
            b = dso->a() * dso->e(); // Use a * e instead of b, since e() returns 1 whenever one of the dimensions is zero. This is important for circular objects
            Q_ASSERT( a >= b );
            pa = dso->pa() * dms::DegToRad;

            // We now want to convert a, b, and pa into an image
            // height and width -- i.e. a dRA and dDec.
            // DSS uses dDec for height and dRA for width. (i.e. "top" is north in the DSS images, AFAICT)
            // From some trigonometry, assuming we have a rectangular object (worst case), we need:
            width = a * sin( pa ) + b * fabs( cos( pa ) );
            height = a * fabs( cos( pa ) ) + b * sin( pa );
            // 'a' and 'b' are in arcminutes, so height and width are in arcminutes

            // Pad the RA and Dec, so that we show more of the sky than just the object.
            height += dss_padding;
            width += dss_padding;
        }
        else {
            // For a generic sky object, we don't know what to do. So
            // we just assume the default size.
            height = width = dss_default_size;
        }
        // There's no point in tiny DSS images that are smaller than dss_default_size
        if( height < dss_default_size )
            height = dss_default_size;
        if( width < dss_default_size )
            width = dss_default_size;

        return getDSSURL( p, width, height, version, md );
}

QString KSDssDownloader::getDSSURL( const SkyPoint * const p, float width, float height, const QString &version, struct KSDssImage::Metadata *md ) {
    Q_ASSERT( p );
    if( !p )
        return QString();
    if( width <= 0 )
        return QString();
    if( height <= 0 )
        height = width;
    QString URL = getDSSURL( p->ra0(), p->dec0(), width, height, "gif", version, md );
    if( md ) {
        const SkyObject *obj = 0;
        obj = dynamic_cast<const SkyObject *>( p );
        if( obj && obj->hasName() )
            md->object = obj->name();
    }
    return URL;
}

QString KSDssDownloader::getDSSURL( const dms &ra, const dms &dec, float width, float height, const QString & type_, const QString & version_, struct KSDssImage::Metadata *md ) {

    const double dss_default_size = Options::defaultDSSImageSize();

    QString version = version_.toLower();
    QString type = type_.toLower();
    QString URLprefix = QString( "http://archive.stsci.edu/cgi-bin/dss_search?v=%1&" ).arg(version);
    QString URLsuffix = QString( "&e=J2000&f=%1&c=none&fov=NONE" ).arg(type);

    char decsgn = ( dec.Degrees() < 0.0 ) ? '-' : '+';
    int dd = abs( dec.degree() );
    int dm = abs( dec.arcmin() );
    int ds = abs( dec.arcsec() );

    // Infinite and NaN sizes are replaced by the default size and tiny DSS images are resized to default size
    if( !qIsFinite( height ) || height <= 0.0 )
        height = dss_default_size;
    if( !qIsFinite( width ) || width <= 0.0)
        width = dss_default_size;

    // DSS accepts images that are no larger than 75 arcminutes
    if( height > 75.0 )
        height = 75.0;
    if( width > 75.0 )
        width = 75.0;

    QString RAString, DecString, SizeString;
    DecString = DecString.sprintf( "&d=%c%02d+%02d+%02d", decsgn, dd, dm, ds );
    RAString  = RAString.sprintf( "r=%02d+%02d+%02d", ra.hour(), ra.minute(), ra.second() );
    SizeString = SizeString.sprintf( "&h=%02.1f&w=%02.1f", height, width );

    if( md ) {
        md->src = KSDssImage::Metadata::DSS;
        md->width = width;
        md->height = height;
        md->ra0 = ra;
        md->dec0 = dec;
        md->version = version;
        md->format = ( type.contains( "fit" ) ? KSDssImage::Metadata::FITS : KSDssImage::Metadata::GIF );
        md->height = height;
        md->width = width;

        if( version.contains( "poss2" ) )
            md->gen = 2;
        else if( version.contains( "poss1" ) )
            md->gen = 1;
        else if( version.contains( "quickv" ) )
            md->gen = 4;
        else
            md->gen = -1;

        if( version.contains( "red" ) )
            md->band='R';
        else if( version.contains( "blue" ) )
            md->band='B';
        else if( version.contains( "ir" ) )
            md->band='I';
        else if( version.contains( "quickv" ) )
            md->band='V';
        else
            md->band='?';

        md->object = QString();
    }

    return ( URLprefix + RAString + DecString + SizeString + URLsuffix );
}

void KSDssDownloader::initiateSingleDownloadAttempt( QUrl srcUrl ) {
    qDebug() << "Temp file is at " << m_TempFile.fileName();
    QUrl fileUrl = QUrl::fromLocalFile( m_TempFile.fileName() );
    qDebug() << "Attempt #" << m_attempt << "downloading DSS Image. URL: " << srcUrl << " to " << fileUrl;
    //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt
    //connect ( m_DownloadJob, SIGNAL ( result (KJob *) ), SLOT ( downloadAttemptFinished() ) );

    downloadJob = new FileDownloader();

    downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"), i18n("Please wait while DSS image is being downloaded..."));
    connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled()));
    connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadAttemptFinished()));
    connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));

    downloadJob->get(srcUrl);
}

void KSDssDownloader::startDownload( const SkyPoint * const p, const QString &destFileName )
{
    QUrl srcUrl;
    m_FileName = destFileName;
    m_attempt = 0;
    srcUrl.setUrl( getDSSURL( p, m_VersionPreference[m_attempt], &m_AttemptData ) );
    initiateSingleDownloadAttempt( srcUrl );
}

void KSDssDownloader::startSingleDownload( const QUrl srcUrl, const QString &destFileName, KSDssImage::Metadata md ) {
    m_FileName = destFileName;
    QUrl fileUrl = QUrl::fromLocalFile( m_TempFile.fileName() );
    qDebug() << "Downloading DSS Image from URL: " << srcUrl << " to " << fileUrl;
    //m_DownloadJob = KIO::copy( srcUrl, fileUrl, KIO::Overwrite ) ; // FIXME: Can be done with pure Qt
    //connect ( m_DownloadJob, SIGNAL ( result (KJob *) ), SLOT ( singleDownloadFinished() ) );

    downloadJob = new FileDownloader();

    downloadJob->setProgressDialogEnabled(true, i18n("DSS Download"), i18n("Please wait while DSS image is being downloaded..."));
    connect(downloadJob, SIGNAL(canceled()), this, SIGNAL(downloadCanceled()));
    connect(downloadJob, SIGNAL(downloaded()), this, SLOT(singleDownloadFinished()));
    connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));

    m_AttemptData = md;

    downloadJob->get(srcUrl);
}

void KSDssDownloader::downloadError(const QString &errorString)
{
    qDebug() << "Error " << errorString << " downloading DSS images!" ;
    emit downloadComplete( false );
    downloadJob->deleteLater();
    return;
}

void KSDssDownloader::singleDownloadFinished()
{
    m_TempFile.open();
    m_TempFile.write(downloadJob->downloadedData());
    m_TempFile.close();
    downloadJob->deleteLater();

    // Check if we have a proper DSS image or the DSS server failed
    QMimeDatabase mdb;
    QMimeType mt = mdb.mimeTypeForFile( m_TempFile.fileName(), QMimeDatabase::MatchContent );
    if( mt.name().contains( "image", Qt::CaseInsensitive ) )
    {
        qDebug() << "DSS download was successful";
        emit downloadComplete( writeImageWithMetadata( m_TempFile.fileName(), m_FileName, m_AttemptData ) );
        return;
    }
    else
        emit downloadComplete( false );
}



void KSDssDownloader::downloadAttemptFinished()
{
    if( m_AttemptData.src == KSDssImage::Metadata::SDSS )
    {
        // FIXME: do SDSS-y things
        emit downloadComplete( false );
        deleteLater();
        downloadJob->deleteLater();
        return;
    }
    else
    {
        m_TempFile.open();
        m_TempFile.write(downloadJob->downloadedData());
        m_TempFile.close();
        downloadJob->deleteLater();

        // Check if we have a proper DSS image or the DSS server failed
        QMimeDatabase mdb;
        QMimeType mt = mdb.mimeTypeForFile( m_TempFile.fileName(), QMimeDatabase::MatchContent );
        if( mt.name().contains( "image", Qt::CaseInsensitive ) )
        {
            qDebug() << "DSS download was successful";
            emit downloadComplete( writeImageFile() );
            deleteLater();
            return;
        }

        // We must have failed, try the next attempt
        QUrl srcUrl;
        m_attempt++;
        if( m_attempt == m_VersionPreference.count() )
        {
            // Nothing downloaded... very strange. Fail.
            qDebug() << "Error downloading DSS images: All alternatives failed!";
            emit downloadComplete( false );
            deleteLater();
            return;
        }
        srcUrl.setUrl( getDSSURL( m_AttemptData.ra0, m_AttemptData.dec0, m_AttemptData.width, m_AttemptData.height,
                                  ( ( m_AttemptData.format == KSDssImage::Metadata::FITS ) ? "fits" : "gif" ),
                                  m_VersionPreference[ m_attempt ], &m_AttemptData ) );
        initiateSingleDownloadAttempt( srcUrl );
    }
}

bool KSDssDownloader::writeImageFile() {
    return writeImageWithMetadata( m_TempFile.fileName(), m_FileName, m_AttemptData );
}

bool KSDssDownloader::writeImageWithMetadata( const QString &srcFile, const QString &destFile, const KSDssImage::Metadata &md ) {
    // Write the temporary file into an image file with metadata
    QImage img( srcFile );
    QImageWriter writer( destFile, "png" );

    writer.setText( "Calibrated", "true" ); // This means that the image has RA/Dec size and orientation that is calibrated
    writer.setText( "PA", "0" ); // Position Angle is zero degrees for DSS images
    writer.setText( "Source", QString::number( KSDssImage::Metadata::DSS ) );
    writer.setText( "Format", QString::number( KSDssImage::Metadata::PNG ) );
    writer.setText( "Version", md.version );
    writer.setText( "Object", md.object );
    writer.setText( "RA", md.ra0.toHMSString( true ) );
    writer.setText( "Dec", md.dec0.toDMSString(true, true) );
    writer.setText( "Width", QString::number( md.width ) );
    writer.setText( "Height", QString::number( md.height ) );
    writer.setText( "Band", QString() + md.band );
    writer.setText( "Generation", QString::number( md.gen ) );
    writer.setText( "Author", "KStars KSDssDownloader" );
    return writer.write( img );
}
