
#include "bugsystem.h"
#include "packagelistjob.h"
#include "buglistjob.h"
#include "bugdetailsjob.h"
#include "bugcommand.h"

#include <kstaticdeleter.h>
#include <kdebug.h>
#include <klocale.h>
#include <kemailsettings.h>
#include <kstandarddirs.h>
#include <ksimpleconfig.h>

#include "packageimpl.h"
#include "bugimpl.h"
#include "bugdetailsimpl.h"
#include "mailsender.h"
#include "kbbprefs.h"

KStaticDeleter<BugSystem> bssd;

BugSystem *BugSystem::s_self = 0;

BugSystem *BugSystem::self()
{
    if ( !s_self )
        s_self = bssd.setObject( new BugSystem );

    return s_self;
}

BugSystem::BugSystem()
    : m_disconnected( false )
{
    m_commandsFile = new KSimpleConfig(locateLocal("appdata","commands"));
    loadCommands();
}

BugSystem::~BugSystem()
{
    saveCommands();
    delete m_commandsFile;
}

void BugSystem::setDisconnected( bool disconnected )
{
    m_disconnected = disconnected;
}

bool BugSystem::disconnected() const
{
    return m_disconnected;
}

void BugSystem::retrievePackageList()
{
    m_packages = m_cache.loadPackageList();

    if( !m_packages.isEmpty() ) {
        emit packageListAvailable( m_packages );
    } else {
        emit packageListCacheMiss();

        if ( !m_disconnected )
        {
            emit packageListLoading();

            PackageListJob *job = new PackageListJob;
            connect( job, SIGNAL( packageListAvailable( const Package::List & ) ),
                     this, SIGNAL( packageListAvailable( const Package::List & ) ) );
            connect( job, SIGNAL( packageListAvailable( const Package::List & ) ),
                     this, SLOT( setPackageList( const Package::List & ) ) );
            connect( job, SIGNAL( infoMessage( const QString & ) ),
                     this, SIGNAL( infoMessage( const QString & ) ) );
            connect( job, SIGNAL( infoPercent( unsigned long ) ),
                     this, SIGNAL( infoPercent( unsigned long ) ) );

            job->start();
        }
    }
}

void BugSystem::retrieveBugList( const Package &pkg )
{
    kdDebug() << "BugSystem::retrieveBugList(): " << pkg.name() << endl;

    if ( pkg.isNull() )
        return;

//    kdDebug() << "BugSystem::retrieveBugList() loading Cache" << endl;

    m_bugs[pkg] = m_cache.loadBugList( pkg, m_disconnected );

    // Since the GUI stops showing the splash widget after this signal,
    // we should not emit anything on a cache miss...
    if( !m_bugs[ pkg ].isEmpty() )
        emit bugListAvailable( pkg, m_bugs[pkg] );
    else
    {
        emit bugListCacheMiss( pkg );

        if ( !m_disconnected )
        {
            kdDebug() << "BugSystem::retrieveBugList() starting job" << endl;
            emit bugListLoading( pkg );

            BugListJob *job = new BugListJob;
            connect( job, SIGNAL( bugListAvailable( const Package &, const Bug::List & ) ),
                     this, SIGNAL( bugListAvailable( const Package &, const Bug::List & ) ) );
            connect( job, SIGNAL( bugListAvailable( const Package &, const Bug::List & ) ),
                     this, SLOT( setBugList( const Package &, const Bug::List & ) ) );
            connect( job, SIGNAL( infoMessage( const QString & ) ),
                     this, SIGNAL( infoMessage( const QString & ) ) );
            connect( job, SIGNAL( infoPercent( unsigned long ) ),
                     this, SIGNAL( infoPercent( unsigned long ) ) );

            job->start( pkg );
        }
    }
}

void BugSystem::retrieveBugDetails( const Bug &bug )
{
    if ( bug.isNull() )
        return;

    kdDebug() << "BugSystem::retrieveBugDetails(): " << bug.number() << endl;

    m_bugDetails[bug] = m_cache.loadBugDetails( bug );

    if ( !m_bugDetails[bug].isNull() ) {
//        kdDebug() << "Found in cache." << endl;
        emit bugDetailsAvailable( bug, m_bugDetails[bug] );
    } else {
//        kdDebug() << "Not found in cache." << endl;
        emit bugDetailsCacheMiss( bug );

        if ( !m_disconnected ) {
            emit bugDetailsLoading( bug );

            BugDetailsJob *job = new BugDetailsJob;
            connect( job, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ),
                     this, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ) );
            connect( job, SIGNAL( bugDetailsAvailable( const Bug &, const BugDetails & ) ),
                     this, SLOT( setBugDetails( const Bug &, const BugDetails & ) ) );
            connect( job, SIGNAL( infoMessage( const QString & ) ),
                     this, SIGNAL( infoMessage( const QString & ) ) );
            connect( job, SIGNAL( infoPercent( unsigned long ) ),
                     this, SIGNAL( infoPercent( unsigned long ) ) );

            job->start( bug );
        }
    }
}

void BugSystem::setPackageList( const Package::List &pkgs )
{
    m_packages = pkgs;

    m_cache.savePackageList( pkgs );
}

void BugSystem::setBugList( const Package &pkg, const Bug::List &bugs )
{
    m_bugs[ pkg ] = bugs;

    m_cache.saveBugList( pkg, bugs );
}

void BugSystem::setBugDetails( const Bug &bug, const BugDetails &details )
{
    m_bugDetails[ bug ] = details;

    m_cache.saveBugDetails( bug, details );
}

Package::List BugSystem::packageList() const
{
    return m_packages;
}

Package BugSystem::package( const QString &pkgname ) const
{
    Package::List::ConstIterator it;
    for( it = m_packages.begin(); it != m_packages.end(); ++it ) {
        if( pkgname == (*it).name() ) return (*it);
    }
    return Package();
}

Bug BugSystem::bug( const Package &pkg, const QString &number ) const
{
    Bug::List bugs = m_bugs[ pkg ];

    Bug::List::ConstIterator it;
    for( it = bugs.begin(); it != bugs.end(); ++it ) {
        if( number == (*it).number() ) return (*it);
    }
    return Bug();
}

void BugSystem::queueCommand( BugCommand *cmd )
{
    // m_commands[bug] is a QPtrList. Get or create, set to autodelete, then append command.
    m_commands[cmd->bug().number()].setAutoDelete( true );
    QPtrListIterator<BugCommand> cmdIt( m_commands[cmd->bug().number()] );
    for ( ; cmdIt.current(); ++cmdIt )
        if ( cmdIt.current()->type() == cmd->type() )
            return;
    m_commands[cmd->bug().number()].append( cmd );
    emit commandQueued( cmd );
}

QPtrList<BugCommand> BugSystem::queryCommands( const Bug &bug ) const
{
    CommandsMap::ConstIterator it = m_commands.find( bug.number() );
    if (it == m_commands.end()) return QPtrList<BugCommand>();
    else return *it;
}

bool BugSystem::hasCommandsFor( const Bug &bug ) const
{
    CommandsMap::ConstIterator it = m_commands.find( bug.number() );
    return it != m_commands.end();
}

void BugSystem::sendCommands()
{
    QString recipient = KBBPrefs::instance()->mOverrideRecipient;
    bool sendBCC = KBBPrefs::instance()->mSendBCC;

    KEMailSettings *emailSettings = new KEMailSettings;
    QString senderName = emailSettings->getSetting( KEMailSettings::RealName );
    QString senderEmail = emailSettings->getSetting( KEMailSettings::EmailAddress );
    QString smtpServer = emailSettings->getSetting( KEMailSettings::OutServer );
    delete emailSettings;

    MailSender::MailClient client = (MailSender::MailClient)KBBPrefs::instance()->mMailClient;

    // ### connect to signals
    MailSender *mailer = new MailSender( client, smtpServer );
    connect( mailer, SIGNAL( status( const QString & ) ),
             this, SIGNAL( infoMessage( const QString & ) ) );

    QString controlText;

    // For each bug that has commands.....
    CommandsMap::ConstIterator it;
    for(it = m_commands.begin(); it != m_commands.end(); ++it ) {
        Bug bug;
        Package pkg;
        // And for each command....
        QPtrListIterator<BugCommand> cmdIt( *it );
        for ( ; cmdIt.current() ; ++cmdIt ) {
            BugCommand* cmd = cmdIt.current();
            bug = cmd->bug();
            if (!cmd->package().isNull())
                pkg = cmd->package();
            if (!cmd->controlString().isNull()) {
                kdDebug() << "control@bugs.kde.org: " << cmd->controlString() << endl;
                controlText += cmd->controlString() + "\n";
            } else {
                kdDebug() << cmd->mailAddress() << ": " << cmd->mailText() << endl;
                // ### hm, should probably re-use the existing mailer instance and
                // implement message queueing for smtp
                MailSender *directMailer = mailer->clone();
                connect( directMailer, SIGNAL( status( const QString & ) ),
                         this, SIGNAL( infoMessage( const QString & ) ) );
                if (!directMailer->send( senderName, senderEmail, cmd->mailAddress(),
                                    cmd->bug().title().prepend( "Re: " ),
                                    cmd->mailText(),
                                         sendBCC, recipient )) {
                    delete mailer;
                    return;
                }
            }
        }
        if (!bug.isNull()) {
            m_commandsFile->deleteGroup( bug.number(), true ); // done, remove command
            m_cache.invalidateBugDetails( bug );
            if ( !pkg.isNull() )
                m_cache.invalidateBugList( pkg ); // the status of the bug comes from the buglist...
        }
    }

    if (!controlText.isEmpty()) {
        if ( !mailer->send( senderName, senderEmail, "control@bugs.kde.org",
                      i18n("Mail generated by KBugBuster"), controlText,
                      sendBCC, recipient ))
            return;

    }
    else
        delete mailer;

    m_commands.clear();
}

void BugSystem::clearCommands( const QString &bug )
{
    m_commands.remove( bug );
    m_commandsFile->deleteGroup( bug, true );

    emit commandCanceled( bug );
}

void BugSystem::clearCommands()
{
    // Shouldn't KConfigBase have deleteAll() ?
    CommandsMap::ConstIterator it;
    for(it = m_commands.begin(); it != m_commands.end(); ++it ) {
        m_commandsFile->deleteGroup( it.key(), true );
        emit commandCanceled( it.key() );
    }
    m_commands.clear();
}

bool BugSystem::commandsPending() const
{
    if ( m_commands.count() > 0 ) return true;
    else return false;
}

QStringList BugSystem::listCommands() const
{
    QStringList result;
    CommandsMap::ConstIterator it;
    for(it = m_commands.begin(); it != m_commands.end(); ++it ) {
        QPtrListIterator<BugCommand> cmdIt( *it );
        for ( ; cmdIt.current() ; ++cmdIt ) {
            BugCommand* cmd = cmdIt.current();
            if (!cmd->controlString().isNull())
                result.append( i18n("Control command : %1").arg(cmd->controlString()) );
            else
                result.append( i18n("Mail to %1").arg(cmd->mailAddress()) );
        }
    }
    return result;
}

void BugSystem::saveCommands() const
{
    CommandsMap::ConstIterator it;
    for(it = m_commands.begin(); it != m_commands.end(); ++it ) {
        m_commandsFile->setGroup( it.key() );
        QPtrListIterator<BugCommand> cmdIt( *it );
        for ( ; cmdIt.current() ; ++cmdIt ) {
            BugCommand* cmd = cmdIt.current();
            cmd->save( m_commandsFile );
        }
    }

    m_commandsFile->sync();
}

void BugSystem::loadCommands()
{
    m_commands.clear();

    QStringList bugs = m_commandsFile->groupList();
    QStringList::ConstIterator it;
    for( it = bugs.begin(); it != bugs.end(); ++it ) {
        m_commandsFile->setGroup( *it );
        QMap<QString, QString> entries = m_commandsFile->entryMap ( *it );
        QMap<QString, QString>::ConstIterator it;
        for( it = entries.begin(); it != entries.end(); ++it ) {
            QString type = it.key();
            BugCommand *cmd = BugCommand::load( m_commandsFile, type );
            if ( cmd ) {
                m_commands[cmd->bug().number()].setAutoDelete(true);
                m_commands[cmd->bug().number()].append(cmd);
            }
        }
    }
}


#include "bugsystem.moc"

/*
 * vim:sw=4:ts=4:et
 */
