/*
   Copyright (c) 2003 Malte Starostik <malte@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/


#include <cstdlib>
#include <ctime>

#include <dcopclient.h>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <knotifyclient.h>
#include <tdeprotocolmanager.h>

#include "proxyscout.moc"
#include "discovery.h"
#include "script.h"

namespace KPAC
{
    ProxyScout::QueuedRequest::QueuedRequest( const KURL& u )
        : transaction( kapp->dcopClient()->beginTransaction() ),
          url( u )
    {
    }

    ProxyScout::ProxyScout( const TQCString& name )
        : KDEDModule( name ),
          m_instance( new TDEInstance( "proxyscout" ) ),
          m_downloader( 0 ),
          m_script( 0 ),
          m_suspendTime( 0 )
    {
    }

    ProxyScout::~ProxyScout()
    {
        delete m_script;
        delete m_instance;
    }

    TQString ProxyScout::proxyForURL( const KURL& url )
    {
        if ( m_suspendTime )
        {
            if ( std::time( 0 ) - m_suspendTime < 300 ) return "DIRECT";
            m_suspendTime = 0;
        }

        // Never use a proxy for the script itself
        if ( m_downloader && url.equals( m_downloader->scriptURL(), true ) ) return "DIRECT";

        if ( m_script ) return handleRequest( url );

        if ( m_downloader || startDownload() )
        {
            m_requestQueue.append( url );
            return TQString::null;
        }
        else return "DIRECT";
    }

    ASYNC ProxyScout::blackListProxy( const TQString& proxy )
    {
        m_blackList[ proxy ] = std::time( 0 );
    }

    ASYNC ProxyScout::reset()
    {
        delete m_script;
        m_script = 0;
        delete m_downloader;
        m_downloader = 0;
        m_blackList.clear();
        m_suspendTime = 0;
        KProtocolManager::reparseConfiguration();
    }

    bool ProxyScout::startDownload()
    {
        switch ( KProtocolManager::proxyType() )
        {
            case KProtocolManager::WPADProxy:
                m_downloader = new Discovery( this );
                break;
            case KProtocolManager::PACProxy:
                m_downloader = new Downloader( this );
                m_downloader->download( KURL( KProtocolManager::proxyConfigScript() ) );
                break;
            default:
                return false;
        }
        connect( m_downloader, TQT_SIGNAL( result( bool ) ),
                 TQT_SLOT( downloadResult( bool ) ) );
        return true;
    }

    void ProxyScout::downloadResult( bool success )
    {
        KNotifyClient::Instance notifyInstance( m_instance );
        if ( success )
            try
            {
                m_script = new Script( m_downloader->script() );
            }
            catch ( const Script::Error& e )
            {
                KNotifyClient::event( "script-error", i18n(
                    "The proxy configuration script is invalid:\n%1" )
                    .arg( e.message() ) );
                success = false;
            }
        else KNotifyClient::event( "download-error", m_downloader->error() );

        for ( RequestQueue::ConstIterator it = m_requestQueue.begin();
              it != m_requestQueue.end(); ++it )
        {
            TQCString type = TQSTRING_OBJECT_NAME_STRING;
            TQByteArray data;
            TQDataStream ds( data, IO_WriteOnly );
            if ( success ) ds << handleRequest( ( *it ).url );
            else ds << TQString( "DIRECT" );
            kapp->dcopClient()->endTransaction( ( *it ).transaction, type, data );
        }
        m_requestQueue.clear();
        m_downloader->deleteLater();
        m_downloader = 0;
        // Suppress further attempts for 5 minutes
        if ( !success ) m_suspendTime = std::time( 0 );
    }

    TQString ProxyScout::handleRequest( const KURL& url )
    {
        try
        {
            TQString result = m_script->evaluate( url );
            TQStringList proxies = TQStringList::split( ';', result );
            for ( TQStringList::ConstIterator it = proxies.begin();
                  it != proxies.end(); ++it )
            {
                TQString proxy = ( *it ).stripWhiteSpace();
                if ( proxy.left( 5 ) == "PROXY" )
                {
                    KURL proxyURL( proxy = proxy.mid( 5 ).stripWhiteSpace() );
                    // If the URL is invalid or the URL is valid but in opaque
                    // format which indicates a port number being present in
                    // this particular case, simply calling setProtocol() on
                    // it trashes the whole URL.
                    int len = proxyURL.protocol().length();
                    if ( !proxyURL.isValid() || proxy.find( ":/", len ) != len )
                        proxy.prepend("http://");
                    BlackList::Iterator it = m_blackList.find( proxy );
                    if ( it == m_blackList.end() ) return proxy;
                    else if ( std::time( 0 ) - *it > 1800 ) // 30 minutes
                    {
                        // black listing expired
                        m_blackList.remove( it );
                        return proxy;
                    }
                }
                else return "DIRECT";
            }
            // FIXME: blacklist
        }
        catch ( const Script::Error& e )
        {
            KNotifyClient::Instance notifyInstance( m_instance );
            KNotifyClient::event( "evaluation-error", i18n(
                "The proxy configuration script returned an error:\n%1" )
                    .arg( e.message() ) );
        }
        return "DIRECT";
    }

    extern "C" KDE_EXPORT KDEDModule* create_proxyscout( const TQCString& name )
    {
        return new ProxyScout( name );
    }
}
