/***************************************************************************
 *   Copyright (C) 2005 by Max Howell <max.howell@methylblue.com>          *
 *   Copyright (C) 2005 by Ian Monroe <ian@monroe.nu>                      *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/

#define DEBUG_PREFIX "StatusBar"

#include "amarok.h"
#include "debug.h"
#include "squeezedtextlabel.h"
#include "statusBarBase.h"
#include "threadmanager.h"
#include "enginecontroller.h"

#include <tdeio/job.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <kstdguiitem.h>

#include <tqapplication.h>
#include <tqdatetime.h>      //writeLogFile()
#include <tqfile.h>          //writeLogFile()
#include <tqpushbutton.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqmessagebox.h>
#include <tqobjectlist.h> //polish()
#include <tqpainter.h>
#include <tqpalette.h>
#include <tqprogressbar.h>
#include <tqstyle.h>   //class CloseButton
#include <tqtimer.h>
#include <tqtoolbutton.h>
#include <tqtooltip.h> //TQToolTip::palette()
#include <tqvbox.h>

//segregated classes
#include "popupMessage.h"
#include "progressBar.h"


namespace KDE {


namespace SingleShotPool
{
    static void startTimer( int timeout, TQObject *receiver, const char *slot )
    {
        TQTimer *timer = static_cast<TQTimer*>( receiver->child( slot ) );
        if( !timer ) {
            timer = new TQTimer( receiver, slot );
            receiver->connect( timer, TQT_SIGNAL(timeout()), slot );
        }

        timer->start( timeout, true );
    }

    static inline bool isActive( TQObject *parent, const char *slot )
    {
        TQTimer *timer = static_cast<TQTimer*>( parent->child( slot ) );

        return timer && timer->isA( TQTIMER_OBJECT_NAME_STRING ) && timer->isActive();
    }
}


//TODO allow for uncertain progress periods


StatusBar::StatusBar( TQWidget *parent, const char *name )
        : TQWidget( parent, name )
        , m_logCounter( -1 )
{
    TQBoxLayout *mainlayout = new TQHBoxLayout( this, 2, /*spacing*/5 );

    //we need extra spacing due to the way we paint the surrounding boxes
    TQBoxLayout *layout = new TQHBoxLayout( mainlayout, /*spacing*/5 );

    TQHBox *statusBarTextBox = new TQHBox( this, "statusBarTextBox" );
    m_mainTextLabel = new KDE::SqueezedTextLabel( statusBarTextBox, "mainTextLabel" );
    TQToolButton *shortLongButton = new TQToolButton( statusBarTextBox, "shortLongButton" );
    shortLongButton->hide();

    TQHBox *mainProgressBarBox = new TQHBox( this, "progressBox" );
    TQToolButton *b1 = new TQToolButton( mainProgressBarBox, "cancelButton" );
    m_mainProgressBar  = new TQProgressBar( mainProgressBarBox, "mainProgressBar" );
    TQToolButton *b2 = new TQToolButton( mainProgressBarBox, "showAllProgressDetails" );
    mainProgressBarBox->setSpacing( 2 );
    mainProgressBarBox->hide();

    layout->add( statusBarTextBox );
    layout->add( mainProgressBarBox );
    layout->setStretchFactor( statusBarTextBox, 3 );
    layout->setStretchFactor( mainProgressBarBox, 1 );

    m_otherWidgetLayout = new TQHBoxLayout( mainlayout, /*spacing*/5 );

    mainlayout->setStretchFactor( layout, 6 );
    mainlayout->setStretchFactor( m_otherWidgetLayout, 4 );

    shortLongButton->setIconSet( SmallIconSet( "edit_add" ) );
    TQToolTip::add( shortLongButton, i18n( "Show details" ) );
    connect( shortLongButton, TQT_SIGNAL(clicked()), TQT_SLOT(showShortLongDetails()) );

    b1->setIconSet( SmallIconSet( "cancel" ) );
    b2->setIconSet( SmallIconSet( "2uparrow") );
    b2->setToggleButton( true );
    TQToolTip::add( b1, i18n( "Abort all background-operations" ) );
    TQToolTip::add( b2, i18n( "Show progress detail" ) );
    connect( b1, TQT_SIGNAL(clicked()), TQT_SLOT(abortAllProgressOperations()) );
    connect( b2, TQT_SIGNAL(toggled( bool )), TQT_SLOT(toggleProgressWindow( bool )) );

    m_popupProgress = new OverlayWidget( this, mainProgressBarBox, "popupProgress" );
    m_popupProgress->setMargin( 1 );
    m_popupProgress->setFrameStyle( TQFrame::Panel | TQFrame::Raised );
    m_popupProgress->setFrameShape( TQFrame::StyledPanel );
    m_popupProgress->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
   (new TQGridLayout( m_popupProgress, 1 /*rows*/, 3 /*cols*/, 6, 3 ))->setAutoAdd( true );
}

void
StatusBar::addWidget( TQWidget *widget )
{
    m_otherWidgetLayout->add( widget );
}


/// reimplemented functions

void
StatusBar::polish()
{
    TQWidget::polish();

    int h = 0;
    TQObjectList *list = queryList( TQWIDGET_OBJECT_NAME_STRING, 0, false, false );

    for( TQObject * o = list->first(); o; o = list->next() ) {
        int _h = TQT_TQWIDGET( o ) ->minimumSizeHint().height();
        if ( _h > h )
            h = _h;

//         debug() << o->className() << ", " << o->name() << ": " << _h << ": " << TQT_TQWIDGET(o)->minimumHeight() << endl;

        if ( o->inherits( TQLABEL_OBJECT_NAME_STRING ) )
            static_cast<TQLabel*>(TQT_TQWIDGET(o))->setIndent( 4 );
    }

    h -= 4; // it's too big usually

    for ( TQObject * o = list->first(); o; o = list->next() )
        TQT_TQWIDGET(o)->setFixedHeight( h );

    delete list;
}

void
StatusBar::paintEvent( TQPaintEvent* )
{
    TQObjectList *list = queryList( TQWIDGET_OBJECT_NAME_STRING, 0, false, false );
    TQPainter p( this );

    for( TQObject * o = list->first(); o; o = list->next() ) {
        TQWidget *w = TQT_TQWIDGET( o );

        if ( !w->isVisible() )
            continue;

        style().tqdrawPrimitive(
                TQStyle::PE_StatusBarSection,
                &p,
                TQRect( w->x() - 1, w->y() - 1, w->width() + 2, w->height() + 2 ),
                colorGroup(),
                TQStyle::Style_Default,
                TQStyleOption( w ) );
    }

    delete list;
}

bool
StatusBar::event( TQEvent *e )
{
    if ( e->type() == TQEvent::LayoutHint )
        update();

    return TQWidget::event( e );
}


/// Messaging system

void
StatusBar::setMainText( const TQString &text )
{
    SHOULD_BE_GUI

    m_mainText = text;

    // it may not be appropriate for us to set the mainText yet
    resetMainText();
}

void
StatusBar::shortMessage( const TQString &text, bool longShort )
{
    SHOULD_BE_GUI

    m_mainTextLabel->setText( text );
    m_mainTextLabel->setPalette( TQToolTip::palette() );

    SingleShotPool::startTimer( longShort ? 8000 : 5000, TQT_TQOBJECT(this), TQT_SLOT(resetMainText()) );

    writeLogFile( text );
}

void
StatusBar::resetMainText()
{
//     if( sender() )
//         debug() << sender()->name() << endl;

    // don't reset if we are showing a shortMessage
    if( SingleShotPool::isActive( TQT_TQOBJECT(this), TQT_SLOT(resetMainText()) ) )
        return;

    m_mainTextLabel->unsetPalette();
    shortLongButton()->hide();

    if( allDone() )
        m_mainTextLabel->setText( m_mainText );

    else {
        ProgressBar *bar = 0;
        uint count = 0;
        foreachType( ProgressMap, m_progressMap )
            if( !(*it)->m_done ) {
                bar = *it;
                count++;
            }

        if( count == 1 )
            m_mainTextLabel->setText( bar->description() + i18n("...") );
        else
            m_mainTextLabel->setText( i18n("Multiple background-tasks running") );
    }
}

void
StatusBar::shortLongMessage( const TQString &_short, const TQString &_long, int type )
{
    SHOULD_BE_GUI

    m_shortLongType = type;

    if( !_short.isEmpty() )
        shortMessage( _short, true );

    if ( !_long.isEmpty() ) {
        m_shortLongText = _long;
        shortLongButton()->show();
        writeLogFile( _long );
    }
}

void
StatusBar::longMessage( const TQString &text, int type )
{
    SHOULD_BE_GUI

    if( text.isEmpty() )
        return;

    PopupMessage *message;
    message = new PopupMessage( this, m_mainTextLabel );
    connect( message, TQT_SIGNAL(destroyed(TQObject *)), this, TQT_SLOT(popupDeleted(TQObject *)) );
    message->setText( text );

    TQString image;

    switch( type )
    {
        case Information:
        case Question:
            image = TDEGlobal::iconLoader()->iconPath( "messagebox_info", -TDEIcon::SizeHuge );
            break;

        case Sorry:
        case Warning:
            image = TDEGlobal::iconLoader()->iconPath( "messagebox_warning", -TDEIcon::SizeHuge );
            break;

        case Error:
            image = TDEGlobal::iconLoader()->iconPath( "messagebox_critical", -TDEIcon::SizeHuge );
            // don't hide error messages.
//             message->setTimeout( 0 );
            break;
    }

    if( !image.isEmpty() )
        message->setImage( image );

    if ( !m_messageQueue.isEmpty() )
         message->stackUnder( m_messageQueue.last() );

    message->display();

    raise();

    m_messageQueue += message;

    writeLogFile( text );
}

void
StatusBar::popupDeleted( TQObject *obj )
{
    m_messageQueue.remove( TQT_TQWIDGET( obj ) );
}

void
StatusBar::longMessageThreadSafe( const TQString &text, int /*type*/ )
{
    TQCustomEvent * e = new TQCustomEvent( 1000 );
    e->setData( new TQString( text ) );
    TQApplication::postEvent( this, e );
}

void
StatusBar::customEvent( TQCustomEvent *e )
{
    if(e->type() == 1000 ){ 
      TQString *s = static_cast<TQString*>( e->data() );
      longMessage( *s );
      delete s;
    }else if(e->type() == 2000 ){
      EngineController::instance()->unplayableNotification();
    }
}


/// application wide progress monitor

inline bool
StatusBar::allDone()
{
    for( ProgressMap::Iterator it = m_progressMap.begin(), end = m_progressMap.end(); it != end; ++it )
        if( (*it)->m_done == false )
            return false;

    return true;
}

ProgressBar&
StatusBar::newProgressOperation( TQObject *owner )
{
    SHOULD_BE_GUI

    if ( m_progressMap.contains( owner ) )
        return *m_progressMap[owner];

    if( allDone() )
        // if we're allDone then we need to remove the old progressBars before
        // we start anything new or the total progress will not be accurate
        pruneProgressBars();
    else
        toggleProgressWindowButton()->show();
    TQLabel *label = new TQLabel( m_popupProgress );
    m_progressMap.insert( owner, new ProgressBar( m_popupProgress, label ) );

    m_popupProgress->reposition();

    connect( owner, TQT_SIGNAL(destroyed( TQObject* )), TQT_SLOT(endProgressOperation( TQObject* )) );

    // so we can show the correct progress information
    // after the ProgressBar is setup
    SingleShotPool::startTimer( 0, TQT_TQOBJECT(this), TQT_SLOT(updateProgressAppearance()) );

    progressBox()->show();
    cancelButton()->setEnabled( true );

    return *m_progressMap[ owner ];
}

ProgressBar&
StatusBar::newProgressOperation( TDEIO::Job *job )
{
    SHOULD_BE_GUI

    ProgressBar & bar = newProgressOperation( static_cast<TQObject*>( job ) );
    bar.setTotalSteps( 100 );

    if(!allDone())
        toggleProgressWindowButton()->show();
    connect( job, TQT_SIGNAL(result( TDEIO::Job* )), TQT_SLOT(endProgressOperation()) );
    //TODO connect( job, TQT_SIGNAL(infoMessage( TDEIO::Job *job, const TQString& )), TQT_SLOT() );
    connect( job, TQT_SIGNAL(percent( TDEIO::Job*, unsigned long )), TQT_SLOT(setProgress( TDEIO::Job*, unsigned long )) );

    return bar;
}

void
StatusBar::endProgressOperation()
{
    TQObject *owner = TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>( sender() )); //HACK deconsting it
    TDEIO::Job *job = dynamic_cast<TDEIO::Job*>( owner );

    //FIXME doesn't seem to work for TDEIO::DeleteJob, it has it's own error handler and returns no error too
    // if you try to delete http urls for instance <- KDE SUCKS!

    if( job && job->error() )
        shortLongMessage( TQString(), job->errorString(), Error );

    endProgressOperation( owner );
}

void
StatusBar::endProgressOperation( TQObject *owner )
{
    //the owner of this progress operation has been deleted
    //we need to stop listening for progress from it
    //NOTE we don't delete it yet, as this upsets some
    //things, we just call setDone().

    if ( !m_progressMap.contains( owner ) )
    {
        SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) );
        return ;
    }

    m_progressMap[owner]->setDone();

    if( allDone() && !m_popupProgress->isShown() ) {
        cancelButton()->setEnabled( false );
        SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) );
    }

    updateTotalProgress();
}

void
StatusBar::abortAllProgressOperations() //slot
{
    for( ProgressMap::Iterator it = m_progressMap.begin(), end = m_progressMap.end(); it != end; ++it )
        (*it)->m_abort->animateClick();

    m_mainTextLabel->setText( i18n("Aborting all jobs...") );

    cancelButton()->setEnabled( false );
}

void
StatusBar::toggleProgressWindow( bool show ) //slot
{
    m_popupProgress->reposition(); //FIXME shouldn't be needed, adding bars doesn't seem to do this
    m_popupProgress->setShown( show );

    if( !show )
        SingleShotPool::startTimer( 2000, TQT_TQOBJECT(this), TQT_SLOT(hideMainProgressBar()) );
}

void
StatusBar::showShortLongDetails()
{
    if( !m_shortLongText.isEmpty() )
        longMessage( m_shortLongText, m_shortLongType );

    m_shortLongType = Information;
    m_shortLongText = TQString();
    shortLongButton()->hide();
}

void
StatusBar::showMainProgressBar()
{
    if( !allDone() )
        progressBox()->show();
}

void
StatusBar::hideMainProgressBar()
{
    if( allDone() && !m_popupProgress->isShown() )
    {
        pruneProgressBars();

        resetMainText();

        m_mainProgressBar->setProgress( 0 );
        progressBox()->hide();
    }
}

void
StatusBar::setProgress( int steps )
{
    setProgress( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender())), steps );
}

void
StatusBar::setProgress( TDEIO::Job *job, unsigned long percent )
{
    setProgress( static_cast<TQObject*>( job ), percent );
}

void
StatusBar::setProgress( const TQObject *owner, int steps )
{
    if ( !m_progressMap.contains( owner ) )
        return ;

    m_progressMap[ owner ] ->setProgress( steps );

    updateTotalProgress();
}

void
StatusBar::incrementProgressTotalSteps( const TQObject *owner, int inc )
{
    if ( !m_progressMap.contains( owner ) )
        return ;

    m_progressMap[ owner ] ->setTotalSteps( m_progressMap[ owner ] ->totalSteps() + inc );

    updateTotalProgress();
}

void
StatusBar::setProgressStatus( const TQObject *owner, const TQString &text )
{
    if ( !m_progressMap.contains( owner ) )
        return ;

    m_progressMap[owner]->setStatus( text );
}

void StatusBar::incrementProgress()
{
    incrementProgress( TQT_TQOBJECT(const_cast<TQT_BASE_OBJECT_NAME*>(sender()) ));
}

void
StatusBar::incrementProgress( const TQObject *owner )
{
    if ( !m_progressMap.contains( owner ) )
        return;

    m_progressMap[owner]->setProgress( m_progressMap[ owner ] ->progress() + 1 );

    updateTotalProgress();
}

void
StatusBar::updateTotalProgress()
{
    uint totalSteps = 0;
    uint progress = 0;

    foreachType( ProgressMap, m_progressMap ) {
        totalSteps += (*it)->totalSteps();
        progress += (*it)->progress();
    }

    if( totalSteps == 0 && progress == 0 )
        return;

    m_mainProgressBar->setTotalSteps( totalSteps );
    m_mainProgressBar->setProgress( progress );

    pruneProgressBars();
}

void
StatusBar::updateProgressAppearance()
{
    toggleProgressWindowButton()->setShown( m_progressMap.count() > 1 );

    resetMainText();

    updateTotalProgress();
}

void
StatusBar::pruneProgressBars()
{
    ProgressMap::Iterator it = m_progressMap.begin();
    const ProgressMap::Iterator end = m_progressMap.end();
    int count = 0;
    bool removedBar = false;
    while( it != end )
        if( (*it)->m_done == true ) {
            delete (*it)->m_label;
            delete (*it)->m_abort;
            delete (*it);

            ProgressMap::Iterator jt = it;
            ++it;
            m_progressMap.erase( jt );
            removedBar = true;
        }
        else {
            ++it;
            ++count;
        }
    if(count==1 && removedBar) //if its gone from 2 or more bars to one bar...
    {
        resetMainText();
        toggleProgressWindowButton()->hide();
        m_popupProgress->setShown(false);
    }
}

/// Method which writes to a rotating log file.
void
StatusBar::writeLogFile( const TQString &text )
{
    if( text.isEmpty() ) return;

    const int counter = 4; // number of logs to keep
    const uint maxSize = 30000; // approximately 1000 lines per log file
    int c = counter;
    TQString logBase = Amarok::saveLocation() + "statusbar.log.";
    TQFile file;

    if( m_logCounter < 0 ) //find which log to write to
    {
        for( ; c > 0; c-- )
        {
            TQString log = logBase + TQString::number(c);
            file.setName( log );

            if( TQFile::exists( log ) && file.size() <= maxSize )
                break;
        }
        if( c == 0 ) file.setName( logBase + '0' );
        m_logCounter = c;
    }
    else
    {
        file.setName( logBase + TQString::number(m_logCounter) );
    }

    if( file.size() > maxSize )
    {
        m_logCounter++;
        m_logCounter = m_logCounter % counter;

        file.setName( logBase + TQString::number(m_logCounter) );
        // if we have overflown the log, then we want to overwrite the previous content
        if( !file.open( IO_WriteOnly ) ) return;
    }
    else if( !file.open( IO_WriteOnly|IO_Append ) ) return;

    TQTextStream stream( &file );
    stream.setEncoding( TQTextStream::UnicodeUTF8 );

    stream << "[" << TDEGlobal::locale()->formatDateTime( TQDateTime::currentDateTime() ) << "] " << text << endl;
}

} //namespace KDE


#include "statusBarBase.moc"
