/***************************************************************************
 *   Copyright (C) 2004 by Robert Hogan                                    *
 *   robert@roberthogan.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.                                   *
 *                                                                         *
 *   This program 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.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#include "klamav.h"
#include "update.h"
#include "klamavconfig.h"

#include <kstaticdeleter.h>

#include <klocale.h>
#include <kio/netaccess.h>


#include <kaction.h>

#include <qcheckbox.h>
#include <kbuttonbox.h>
#include <kurlrequester.h>
#include <kurlcompletion.h>
#include <kcombobox.h>
#include <qlayout.h>
#include <kmessagebox.h>
#include <klineedit.h>
#include <ktempfile.h>
#include <ksystemtray.h>
#include <ktar.h>
#include <kprogress.h>
#include <kprocio.h>
#include <knotifyclient.h>
#include <dom/html_misc.h>
#include <kapplication.h>
#include <dcopclient.h>
#include <kuser.h>

#include <qtimer.h>

#include "version.h"

#include <stdlib.h>

const char *mirrors[] = {
    "heanet",
    "internap",
    "surfnet",
    "umn",
    "nchc",
    "ufpr",
    "unc",
    "jaist",
    "voxel",
    "citkit",
    "optusnet",
    "ovh",
    "mesh",
    "easynews",
    "switch",
    "kent",
    "puzzle",
    0
};

KlamavUpdate::KlamavUpdate(QWidget *parent, const char *name)
    : QWidget(parent, name),filelist(new KHTMLPart(this))
{

    filelist->hide();

}


KlamavUpdate::~KlamavUpdate()
{
    kdDebug() << "deleting" << endl;
    delete filelist;
}



void KlamavUpdate::downloadComponent(QString component, QString version, QString extension)
{

    upgradeinprogress = true;

    QString location = getenv("HOME");

    location += "/.klamav";
    QDir klamavdir(location);
    if (!klamavdir.exists() && !klamavdir.mkdir(location))
        location = getenv("HOME");

    QString currentclamav = location + QString("/%1-%2").arg(component).arg(version);
    QDir clamavdir(currentclamav);

    QString configure;
/*    if (component == "klamav"){
        configure = QString("%1/%2-%3/%4-%5/configure").arg(location).arg(component).arg(version).arg(component).arg(version);

    }else{*/
        configure = QString("%1/%2-%3/configure").arg(location).arg(component).arg(version);
//     }
    QFile configurefile(configure);

    bool download = true;

    if (clamavdir.exists() && configurefile.exists()){
        int result = KMessageBox::questionYesNo(this, i18n( "You seem to have downloaded %1-%2 already (in %3/%4-%5). Would you like to skip re-downloading it and just try to compile it?").arg(component).arg(version).arg(location).arg(component).arg(version),i18n( "Compile %1" ).arg(component));

        switch (result) {
            case 2 : 
                download = true;break;
            case 3 : 
                download = false;
        }
    }

    while (download){

        startProgressDialog( i18n( "Downloading %1-%2..." ).arg(component).arg(version) );
 
        QString tmpFile;
        if ( !KIO::NetAccess::download( QString("http://%1.dl.sourceforge.net/sourceforge/%2/%3-%4.%5").arg(getMirror()).arg(component).arg(component).arg(version).arg(extension), tmpFile, 0L ) ){
            KMessageBox::information (this,i18n("Couldn't download %1.").arg(component));
            delete timer;
            timer=0;
            delete progressDialog;
            progressDialog = 0;
            upgradeinprogress = false;
            return;
        }
    
        ////kdDebug() << "Theme is in temp file: " << tmpFile << endl;
    
        if ((progressDialog) && (progressDialog->wasCancelled())){
            updateCanceled();
            return;
        }
    
        
    
        progressDialog->setLabel( i18n( "Unpacking %1-%2 to %3/%4-%5" ).arg(component).arg(version).arg(location).arg(component).arg(version));
    
        // unpack the tarball
        KTar tar( tmpFile );
        if (!(tar.open( IO_ReadOnly ))){
            int result = KMessageBox::questionYesNo(this, i18n( "The mirror I attempted to download from has not updated yet. Should I try another?"));
            //kdDebug() << result << endl;
            switch (result) {
                case KMessageBox::Yes : 
                    download = true; break;
                case KMessageBox::No : 
                    download = false;
                default:
                    download = false;
            }
        }else{
            download = false;
        } 
        tar.directory()->copyTo( location );
        tar.close();
        // remove the temp file
        KIO::NetAccess::removeTempFile( tmpFile );
    
        delete timer;
        timer=0;
        delete progressDialog;
        progressDialog = 0;
    }

    if ((progressDialog) && (progressDialog->wasCancelled())){
        updateCanceled();
        delete progressDialog;
        progressDialog = 0;
        return;
    }

    //KTar does not honour executable permissions

    if (clamavdir.exists() && configurefile.exists()){

        chmod((const char *)configure,0700);
//         if (component == "klamav"){
//             QString dazukoconfigure = i18n("%1/%2-%3/dazuko/configure").arg(location).arg(component).arg(version);
// 
//             chmod((const char *)dazukoconfigure,0700);
//         }

        int result = KMessageBox::warningContinueCancel(this, i18n( "%1-%2 is ready for compiling and installation. Would you like the wizard to ask you for the root password so it can compile and install it for you? (If not, you can compile it yourself later at %3/%4-%5)").arg(component).arg(version).arg(location).arg(component).arg(version),i18n( "Install %1-%2" ).arg(component).arg(version),i18n( "Use the Wizard" ));
        
        switch (result) {
            case 2 : 
                KMessageBox::information (this,i18n("Installation of %1 Cancelled.").arg(component));
                upgradeinprogress = false;
                break;
            case 5 : 

                KProcess* arkollonproc = new KShellProcess();
                //make sure we catch arkollon in the .klamav directory
// 	        QString curpath = (QString) getenv("PATH");
//                 QString homedir = (QString) getenv("HOME");
// 	        arkollonproc->setEnvironment("PATH",homedir+"/.klamav/bin:" + curpath);

                KMessageBox::information (this,i18n( "<p><b>If this the first time you've compiled software then here are a few useful tips:</b><br>"
                    "1. Any error messages in the log file with the words 'KDE', 'Qt','curl' or 'X' in them mean that you need to install "
                    "the appropriate development libraries.<br>"
                    "2. Any package provided by your distribution with 'lib' or 'devel' in the name is a development library, e.g. qt-devel, libkde.<br>"
                    "3. If you encounter errors installing Dazuko, just deselect it at installation time and try the instructions at www.dazuko.org.<br>"
                    "4. Some distributions provide a dazuko package, see if yours does.</p>" ),"Compilation Tips","compiler");
                QString command = QString("klamarkollon %1/%2-%3").arg(location).arg(component).arg(version);
                *arkollonproc << command;
                if (component == "klamav")
                connect( arkollonproc, SIGNAL(processExited(KProcess *)), SLOT(klamavInstallationExited(KProcess *)) );
                else
                    connect( arkollonproc, SIGNAL(processExited(KProcess *)), SLOT(clamavInstallationExited(KProcess *)) );
                arkollonproc->start();
        }
    }
    
}

void KlamavUpdate::checkForNewKlamAV()
{

    //kdDebug() << "Checking for new KlamAV" << endl;

    checkingDirectly = false;

    highestsofarnumeric = 0;
    klamav_url = "http://prdownloads.sourceforge.net/klamav/";
    getLatestVersionFromSFHack(klamav_url);

}

void KlamavUpdate::checkForNewKlamAVDirectly()
{

    //kdDebug() << "Checking for new KlamAV" << endl;

    checkingDirectly = true;

    highestsofarnumeric = 0;
    klamav_url = "http://prdownloads.sourceforge.net/klamav/";
    startProgressDialog( i18n( "Checking for new version of KlamAV..." ) );

    getLatestVersionFromSF(klamav_url);

}


void KlamavUpdate::checkForNewClamAVDirectly()
{

    //kdDebug() << "Checking for new ClamAV" << endl;

    checkingDirectly = true;

    highestsofarnumeric = 0;
    clamav_url = "http://prdownloads.sourceforge.net/clamav/";

    startProgressDialog( i18n( "Checking for new version of ClamAV..." ) );
    
    getLatestVersionFromSF(clamav_url);

}

void KlamavUpdate::completedSearchForUpdates(const QString &component, const QString &extension)
{

    double currentversion;

    if (component == "KlamAV")
        currentversion = VERSION_KLAMAV;
    else{
	QString currentClamAVVersion = KlamavConfig::clamAVVersion();
        if (currentClamAVVersion.mid(currentClamAVVersion.length() - 2,1) == ".")
            currentversion = currentClamAVVersion.replace(currentClamAVVersion.length() - 2,1,"").toDouble();
        else
            currentversion = currentClamAVVersion.toDouble();
    }

    ////kdDebug() << "in completed search for updates" << endl;
    ////kdDebug() << currentversion << endl;
    ////kdDebug() << highestsofarnumeric << endl;

    if (highestsofarnumeric > currentversion){
        int result = KMessageBox::warningContinueCancel(this, i18n( "It looks like your version of %1 is out of date! %2-%3 is the most recent version available. Would you like KlamAV to download and compile it for you?").arg(component).arg(component).arg(highestsofarraw),i18n( "Download and Install %1-%2" ).arg(component).arg(highestsofarraw),i18n( "Download and Install %1-%2" ).arg(component).arg(highestsofarraw));
    
        switch (result) {
            case 2 : 
                break;
            case 5 : 
                downloadComponent(component.lower(), highestsofarraw, extension);
        }
    }else if (checkingDirectly){
        KMessageBox::information (this,i18n("Your installation of %1 is already up-to-date!").arg(component));
    }
    checkingDirectly = false;
    emit toggleUpgradeButtons(true);
//     if (filelist)
//         filelist->deleteLater();

}



void KlamavUpdate::startProgressDialog( const QString & text )
{
    //if ( progressDialog )
    //    delete progressDialog;

    progressDialog = new KProgressDialog( this, "progress_dialog", QString::null, text, false );

    progressDialog->setAllowCancel( true );
    progressDialog->showCancelButton( true );
    progressDialog->setPlainCaption( i18n( "Please Wait" ) );

    progressDialog->progressBar()->setTotalSteps( 0 );
    progressDialog->progressBar()->setPercentageVisible( false );

    progressDialog->setMinimumDuration( 500 );
    progressDialog->show();

    timer = new QTimer( this );
    connect( timer, SIGNAL( timeout() ), this, SLOT( slotProg() ) );

    timer->start( 200, FALSE );
}

void KlamavUpdate::slotProg()
{

    if (progressDialog)
        progressDialog->progressBar()->setProgress(progressDialog->progressBar()->progress() + 4 );
}

void KlamavUpdate::clamavInstallationExited(KProcess* arkollonproc)
{

    arkollonproc = 0;
    delete arkollonproc;
    upgradeinprogress = false;
    emit getCurrentVersionOfClamAV( );

}

void KlamavUpdate::klamavInstallationExited(KProcess* arkollonproc)
{

    arkollonproc = 0;
    delete arkollonproc;
    upgradeinprogress = false;

    KMessageBox::information (this,i18n( "If the installation of KlamAV completed successfully you should restart KlamAV for the new version to take effect." ));

}




void KlamavUpdate::getLatestVersionFromSF(KURL url)
{

    ////kdDebug() << "getting latest version" << endl;
    ////kdDebug() << url << endl;
    emit toggleUpgradeButtons(false);

    connect( filelist, SIGNAL( completed() ), this,
		SLOT( parseSFPage() ) );

    filelist->openURL(url);
    if (progressDialog)
        connect( progressDialog, SIGNAL( cancelClicked() ), this,
		SLOT( updateCanceled() ) );

}

void KlamavUpdate::getLatestVersionFromSFHack(KURL url)
{

    ////kdDebug() << "getting latest version" << endl;
    ////kdDebug() << url << endl;
    emit toggleUpgradeButtons(false);

    connect( filelist, SIGNAL( completed() ), this,
		SLOT( parseSFPageHack() ) );

    filelist->openURL(url);

}



void KlamavUpdate::updateCanceled()
{

    ////kdDebug() << "cancelled" << endl;
    filelist->closeURL();
    emit toggleUpgradeButtons(true);
}

void KlamavUpdate::parseSFPage()
{


    ////kdDebug() << "parsing sf page" << endl;


    const DOM::HTMLCollection links = filelist->htmlDocument().links();
    ////kdDebug() << links.length() << endl;

    if (links.length() == 0){
            KMessageBox::information (this,i18n( "Could not contact update server!" ));
            updateCanceled();
            delete progressDialog;
            progressDialog = 0;
            return;
    }
    
    disconnect( filelist, SIGNAL( completed() ), this,
	SLOT( parseSFPage() ) );

    for (unsigned int j=0; j != links.length(); j++ ){
        const DOM::Node linkNode = links.item( j );
        getVersionFromLink( linkNode );
    }

    delete progressDialog;
    progressDialog = 0;
    
    ////kdDebug() << highestsofarfilename << endl;
    if (highestsofarfilename.contains("klamav"))
        completedSearchForUpdates("KlamAV", "tar.bz2");
    else if (highestsofarfilename.contains("clamav"))
        completedSearchForUpdates("ClamAV", "tar.gz");


}

void KlamavUpdate::parseSFPageHack()
{


    ////kdDebug() << "parsing sf page" << endl;


    const DOM::HTMLCollection links = filelist->htmlDocument().links();
    ////kdDebug() << links.length() << endl;

    if (links.length() == 0){
            return;
    }
    
    disconnect( filelist, SIGNAL( completed() ), this,
	SLOT( parseSFPageHack() ) );

    for (unsigned int j=0; j != links.length(); j++ ){
        const DOM::Node linkNode = links.item( j );
        getVersionFromLink( linkNode );
    }

    //delete progressDialog;
    //progressDialog = 0;

    ////kdDebug() << highestsofarfilename << endl;
    if (highestsofarfilename.contains("klamav"))
        completedSearchForUpdates("KlamAV", "tar.bz2");
    else if (highestsofarfilename.contains("clamav"))
        completedSearchForUpdates("ClamAV", "tar.gz");


}


void KlamavUpdate::getVersionFromLink( const DOM::Node &n )
{
        double numericversion;
        QString tmpversion;

        if ( n.isNull() || n.nodeType() != DOM::Node::ELEMENT_NODE )
            return;
        
        DOM::Element elem = static_cast<DOM::Element>( n );
        
        KURL href ( elem.getAttribute( "href" ).string() );
        
        ////kdDebug() << href.filename() << endl;

        QString name = href.filename();
        QString version = name.replace(".tar.bz2","").replace(".tar.gz","");
        version = version.replace("clamav-","").replace("klamav-","");

        tmpversion = version;

        if ((tmpversion.mid(tmpversion.length() - 2,1) == ".") && (tmpversion.length() > 3))
            numericversion = tmpversion.replace(tmpversion.length() - 2,1,"").toDouble();
        else
            numericversion = tmpversion.toDouble();

        ////kdDebug() << version << endl;
        ////kdDebug() << numericversion << endl;
        if (numericversion > highestsofarnumeric){
            highestsofarnumeric = numericversion;
            highestsofarraw = version;
            highestsofarfilename = href.filename();
        }


}

QString KlamavUpdate::getMirror()
{
    int r = 1+(int) (16.0*rand()/(RAND_MAX+1.0));
    static int previousmirror = 0;

    while (r == previousmirror)
        r=1+(int) (16.0*rand()/(RAND_MAX+1.0));

    previousmirror = r;

    ////kdDebug() << r << endl;
    return mirrors[r];
}

#include "update.moc"
