/***************************************************************************
                         kio_slp.cpp  -  description
                             -------------------
    begin                : Fri Nov 14 10:48:58 CET 2003
    copyright            : (C) 2003 by Adrian Schroeter
    email                : adrian@suse.de
                           2009 Friedrich W. H. Kossebau
                           kossebau@kde.org
 ***************************************************************************/

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

#include "kio_slp.h"

// ioslave
#include "servicelistingurl.h"
// KDE
#include <KStandardDirs>
#include <KConfigGroup>
#include <KLocale>
#include <KProtocolInfo>
#include <KProtocolManager>
// C
#include <time.h>

#include <KDebug>


using namespace KIO;

extern "C"
{
    /* let it still work, even someone installed a non-SuSE openslp */
    SLPEXP const char* SLPAPI SLPGetMDNSName( SLPHandle hSLP, const char *pcURL ) __attribute__ ((weak));
}


ServicesSlave::ServicesSlave( const QByteArray& protocol, const QByteArray& pool_socket, const QByteArray& app_socket )
  : SlaveBase( protocol, pool_socket, app_socket )
  , mProtocolMode( ServiceProtocol )
{
    SLPError result = SLPOpen( 0, SLP_FALSE, &mSLP );
    if (result == SLP_OK)
    {
        KConfig config;
        KConfigGroup configGroup( &config, "Default" );

        EnforceNativeServiceList = configGroup.readEntry( "EnforceNativeServiceList", QStringList() );
        PositiveServiceList      = configGroup.readEntry( "PositiveServiceList",      QStringList() );
        NegativeServiceList      = configGroup.readEntry( "NegativeServiceList",      QStringList() );
        PositiveOnly             = configGroup.readEntry( "PositiveOnly",             false );
        mKnownProtocolsOnly       = configGroup.readEntry( "mKnownProtocolsOnly",       false );
        mDefaultIconName         = configGroup.readEntry( "DefaultIcon",              "network-server" );
    }
    // TODO: how to proceed here best? exit or signal error on all calls?
//     else
//     {
//         qWarning("Unable to open SLP.");
//         exit();
//     }

}

ServicesSlave::~ServicesSlave()
{
    SLPClose( mSLP );
}


// TODO: this needs to move to a suburl. Can be seen to fail by entering a service url and pressing enter in Konqueror
// also used for unknown services
void ServicesSlave::get( const KUrl& url )
{
kDebug()<<url;
    if( ! isSlpOk() )
        return;
kDebug()<<url;

    // should only be called for unknown services,
    // so we do not check for the type and 

    mimeType( "text/html" );

    const QString serviceUrl = ServiceListingUrl::serviceUrlFromKioUrl( url );
// kDebug()<<url<<"serviceProtocolName"<<serviceProtocolName;

    mAttributeHtml =
        "<html>"
        ""
        "<head>"
        "  <title></title>"
            "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"
        "</head>"
        "<body bgcolor=\"#e3e3e3\">"
        "<table width=\"100%\" align=\"center\" bgcolor=\"#d3d3d3\">"
        "  <thead bgcolor=\"gray\">"
        "    <tr valign=center>"
        "      <th><h1>Service Attributes</h1></th>"
        "    </tr>"
        "  </thead>"
        "  <tbody>"
        "    <tr>"
        "      <th><center><h3>" + serviceUrl + "</h3></center></th>"
        "    </tr>"
        "  </tbody>"
        "</table>"
        "<p>&nbsp;</p>";

    SLPError result =
        SLPFindAttrs( mSLP, serviceUrl.toLatin1(),
                      scope.toLatin1(),
                      "", /* return all attributes */
                      onAttributesFound,
                      this );

    if ( result != SLP_OK )
    {
        mAttributeHtml += "SLPFindAttr failed for \"" + serviceUrl + "\""
                        "</body> "
                        "</html> ";

        data( mAttributeHtml.toUtf8() );
        finished();
   }
}

void ServicesSlave::mimetype( const KUrl& url )
{
kDebug()<<url;
    if( ! isSlpOk() )
        return;

    const ServiceListingUrl serviceListingUrl( url, false );

    const ServiceListingUrl::Type dataType = serviceListingUrl.dataType( mNativeProtocolAllowed );
    switch ( dataType )
    {
    case ServiceListingUrl::RootDir:
    case ServiceListingUrl::NativeRootDir:
    case ServiceListingUrl::ServiceDir:
    {
        const QString mimetypeName( "inode/directory" );
        mimetype( mimetypeName );
        finished();
        break;
    }
    case ServiceListingUrl::Service:
    {
        const QString serviceType = serviceListingUrl.serviceType();
        const QString protocolName = ServiceListingUrl::toKdeProtocolName(serviceType);
        const bool protocolKnown = ! protocolName.isEmpty();
        const QString mimetypeName = protocolKnown ?
            KProtocolManager::defaultMimetype( protocolName ) :
            "text/html"; //no special handling found -> show attributes
        mimetype( mimetypeName );
        finished();
        break;
    }
    default:
        error( KIO::ERR_MALFORMED_URL, url.prettyUrl() );
    }
}

void ServicesSlave::stat( const KUrl& url )
{
kDebug()<<url;
    if( ! isSlpOk() )
        return;

    const ServiceListingUrl serviceListingUrl( url, false );

    const ServiceListingUrl::Type dataType = serviceListingUrl.dataType( mNativeProtocolAllowed );
    switch ( dataType )
    {
    case ServiceListingUrl::RootDir:
    {
        KIO::UDSEntry entry;
        // i18n("Network Browser")
        feedEntryAsDir( &entry, "/", "network-server", QString() );
        statEntry( entry );
        finished();
        break;
    }
    case ServiceListingUrl::NativeRootDir:
    {
        const QString serviceUrl = serviceListingUrl.fullServiceUrl();
        redirection( serviceUrl );
        finished();
        break;
    }
    case ServiceListingUrl::ServiceDir:
    {
        const QString serviceType = serviceListingUrl.serviceType();

        const QString protocolName = ServiceListingUrl::toKdeProtocolName(serviceType);
        const bool protocolKnown = ! protocolName.isEmpty();

        QString serviceProtocol = serviceType;
        QString name = serviceType;
        QString iconName;
        if( protocolKnown )
            iconName = KProtocolInfo::icon( protocolName );

        overwriteWithConfig( &serviceProtocol, &name, &iconName  );

        if( iconName.isEmpty() )
            iconName = mDefaultIconName;

        const QString destUrl = ServiceListingUrl::createKioUrl( serviceListingUrl.protocolType(), serviceProtocol );

        KIO::UDSEntry entry;
        feedEntryAsDir( &entry, name, iconName, destUrl );
        finished();
        break;
    }
    case ServiceListingUrl::Service:
    {
        const QString serviceType = serviceListingUrl.serviceType();
        QString hostNPath = serviceListingUrl.hostNPath();

        const QString name = hostNPath;
        // TODO: should be checked if the service url is matching an exising service!
        const QString displayName; // not needed here hopefully serviceName.isEmpty() ? serviceType : serviceName;

        const QString protocolName = ServiceListingUrl::toKdeProtocolName(serviceType);
        const bool protocolKnown = ! protocolName.isEmpty();

        QString serviceProtocol = serviceType;
        QString iconName;
        if( protocolKnown )
            iconName = KProtocolInfo::icon( protocolName );

        overwriteWithConfig( &serviceProtocol, 0, &iconName  );

        if( iconName.isEmpty() )
            iconName = mDefaultIconName;

        const QString mimetype = protocolKnown ?
            KProtocolManager::defaultMimetype( protocolName ) :
            "text/html"; //no special handling found -> show attributes

        const QString destUrl = protocolKnown ?
            ServiceListingUrl::createKioUrl( serviceProtocol, hostNPath ) :
            QString();

        KIO::UDSEntry entry;
        feedEntryAsFile( &entry, name, iconName, mimetype, displayName, destUrl );
        finished();
        break;
    }
    default:
        error( KIO::ERR_MALFORMED_URL, url.prettyUrl() );
    }
}

void ServicesSlave::listDir( const KUrl& url )
{
    if( ! isSlpOk() )
        return;

    const ServiceListingUrl serviceListingUrl( url, false );

    mCurrentServiceTypesList.clear();

    mProtocolMode = serviceListingUrl.protocolType();

    // find which provider to use
    if ( mProtocolMode == MDNSProtocol )
        scope = "_mdns";
    else
    {
        const char* useScopes = SLPGetProperty( "net.slp.useScopes" );
        QStringList useScopeList = QString(useScopes).split( ',' );
        if ( mProtocolMode == SLPProtocol )
            useScopeList.removeAll( "_mdns" );
        else
        {
            if ( !useScopeList.contains("_mdns") )
                useScopeList.append( "_mdns" );
        }

        scope = useScopeList.join( "," );
    }

    const ServiceListingUrl::Type dataType = serviceListingUrl.dataType( mNativeProtocolAllowed );
kDebug()<<"url="<<url<<"mProtocolMode="<<mProtocolMode<<"dataType="<<dataType<<"fullServiceUrl="<<serviceListingUrl.fullServiceUrl();
    switch ( dataType )
    {
    case ServiceListingUrl::RootDir:
    {
        if ( mProtocolMode == ServiceProtocol )
        {
            // add 
            foreach( const QString& nativeServiceProtocolName, EnforceNativeServiceList )
            {
                mNativeProtocolAllowed[nativeServiceProtocolName] = true;
                addServiceTypeEntry( nativeServiceProtocolName );
            }
        }

        // root dir listing -> list services
        SLPFindSrvTypes( mSLP, "*", scope.toLatin1(),
                         onServiceTypesFound, this );

        KIO::UDSEntry entry;
        listEntry( entry, true );
        finished();
        break;
    }
    case ServiceListingUrl::NativeRootDir:
    {
        const QString serviceUrl = serviceListingUrl.fullServiceUrl();
        redirection( serviceUrl );
        finished();
        break;
    }
    case ServiceListingUrl::ServiceDir:
    {
kDebug()<<"querying for"<<serviceListingUrl.serviceType().toLatin1()<<"scope="<<scope.toLatin1();
        // host dir listing -> list hosts for a service
        SLPFindSrvs( mSLP, serviceListingUrl.serviceType().toLatin1(),
                     scope.toLatin1(),
                     "", // TODO: search filter
                     onServiceFound, this );
        if( serviceListingUrl.serviceType().endsWith("ssh") )
        {
        onServiceFound( mSLP, "service:ssh://192.168.0.8/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://192.168.0.8/has/a/path", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://192.168.0.8/has/a/path/and/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:Meisterzugriff.besonders:ssh://192.168.0.8/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://ja.wo.denn/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:login:ssh://192.168.0.8/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://hans@192.168.0.23:2324/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://localhost/", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:ssh://grrete:geheim@192.168.0.8:80/some/where", 12, SLP_OK, this );
        }
        else
        {
        onServiceFound( mSLP, "service:fantasy://192.168.0.8:324/some/where/over", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:real.abstraction:fantasy://gibt.es.nicht:324/some/where/over", 12, SLP_OK, this );
        onServiceFound( mSLP, "service:fantasy://gibt.es.auch.nicht/", 12, SLP_OK, this );
        }
        KIO::UDSEntry entry;
        listEntry( entry, true );
        finished();
        break;
    }
    case ServiceListingUrl::Service: // TODO: really needed, not already done in the master?
    {
        const QString fullServiceUrl = serviceListingUrl.fullServiceUrl();
        if ( ! fullServiceUrl.isEmpty() )
        {
            redirection( fullServiceUrl );
            finished();
            break;
        }
        // fall through
    }
    default:
        error( KIO::ERR_MALFORMED_URL, url.prettyUrl() );
    }

//     error( KIO::ERR_UNSUPPORTED_ACTION,
//             i18n("This url could not be resolved: %1.", url.prettyUrl()));
}

bool ServicesSlave::isSlpOk()
{
    bool result;

    result = ( mSLP != NULL );

    if( ! result )
        error( KIO::ERR_UNSUPPORTED_ACTION,
               "Reading from SLP failed."); //TODO: find term for i18n

    return result;
}

void ServicesSlave::addServiceTypeEntry( const QString& serviceType )
{
    if( serviceType.isEmpty() )
        return;

    if ( mProtocolMode == ServiceProtocol
        && (NegativeServiceList.contains(serviceType)
                || ( PositiveOnly && !PositiveServiceList.contains(serviceType) )) )
        return;

    // check for double entries
    if ( mCurrentServiceTypesList.contains(serviceType) )
        return;
    mCurrentServiceTypesList.append( serviceType );

    const QString protocolName = ServiceListingUrl::toKdeProtocolName(serviceType);
    const bool protocolKnown = ! protocolName.isEmpty();

    if ( mKnownProtocolsOnly && protocolKnown )
        // konq wouldn't be able to handle this protocol
        // only for service:/, not for slp:/ or mdns:/
        return;

    QString serviceProtocol = serviceType;
    QString name = serviceType;
    QString iconName;
    if( protocolKnown )
        iconName = KProtocolInfo::icon( protocolName );

    overwriteWithConfig( &serviceProtocol, &name, &iconName  );

    if( iconName.isEmpty() )
        iconName = mDefaultIconName;

    const QString destUrl = ServiceListingUrl::createKioUrl( mProtocolMode, serviceProtocol );

    KIO::UDSEntry entry;
    feedEntryAsDir( &entry, name, iconName, destUrl );
    listEntry( entry, false );
}

void ServicesSlave::addServiceEntry( const QString& serviceType, const QString& siteNParameter, const QString& serviceName )
{
    if( serviceType.isEmpty() )
        return;

    const int indexOfSemicolon = siteNParameter.indexOf( ';' );/* mainly for the krdc hack */
    const QString hostNPath = siteNParameter.left( indexOfSemicolon );

    const QString name = ServiceListingUrl::createKioPath( serviceType, hostNPath );
    const QString displayName = serviceName.isEmpty() ? serviceType : serviceName;

//     if ( !displayName.isEmpty() )
//         titel = displayName + " (" + titel + ')';

    const QString protocolName = ServiceListingUrl::toKdeProtocolName(serviceType);
    const bool protocolKnown = ! protocolName.isEmpty();

    QString serviceProtocol = serviceType;
    QString iconName;
    if( protocolKnown )
        iconName = KProtocolInfo::icon( protocolName );

    overwriteWithConfig( &serviceProtocol, 0, &iconName  );

    if( iconName.isEmpty() )
        iconName = mDefaultIconName;

//     if (host.count(':')>1) /* workaround for broken IPv6 srvurls */
//             host = '[' + host + ']';

    const QString mimetype = protocolKnown ?
        KProtocolManager::defaultMimetype( protocolName ) :
        "text/html"; //no special handling found -> show attributes

    const QString destUrl = protocolKnown ?
        ServiceListingUrl::createKioUrl( serviceProtocol, hostNPath ) :
        QString();
kDebug() <<"protocolName="<<protocolName<<"mimetype="<<mimetype<<"destUrl="<<destUrl;

    KIO::UDSEntry entry;
    feedEntryAsFile( &entry, name, iconName, mimetype, displayName, destUrl );
    listEntry( entry, false );
}

void ServicesSlave::overwriteWithConfig( QString* serviceType, QString* name, QString* iconName  )
{
    KConfig defaultConfig;
    QString configGroupName;
    KConfig* protocolConfig = 0;
    KConfig* c;

    const QString serviceTypeRC = "kio_slp/" + *serviceType + "rc";
    if ( !KStandardDirs::locate("config",serviceTypeRC).isEmpty() )
    {
        protocolConfig = new KConfig( serviceTypeRC );
        c = protocolConfig;
        configGroupName = "Default";
    }
    else if( defaultConfig.hasGroup(*serviceType) )
    {
        c = &defaultConfig;
        configGroupName = *serviceType;
    }
    else
    {
        const QString abstractServiceType = serviceType->left( serviceType->indexOf(':') ); //TODO: hm, examples?
        const QString abstractServiceTypeRC = "kio_slp/" + abstractServiceType + "rc";
        if ( !KStandardDirs::locate("config", abstractServiceTypeRC).isEmpty() )
        {
            protocolConfig = new KConfig( abstractServiceTypeRC );
            c = protocolConfig;
            configGroupName = "Default";
            *serviceType = abstractServiceType;
        }
        else
        {
            c = &defaultConfig;
            configGroupName = abstractServiceType;
            *serviceType = abstractServiceType;
        }
    }

    // c might be deleted, so restrict existence configGroup in limited scope
    {
        KConfigGroup configGroup( c, configGroupName );
        if ( name && configGroup.hasKey( "Name" ) )
            *name = configGroup.readEntry( "Name" );
        if ( configGroup.hasKey( "Icon" ) )
            *iconName = configGroup.readEntry( "Icon" );
// TODO; what? iconName?
//         if ( configGroup.hasKey( "ServiceURL" ) )
// 	   iconName = configGroup.readEntry( "ServiceURL" );
    }
    delete protocolConfig;
}


void ServicesSlave::feedEntryAsDir( KIO::UDSEntry* entry,
                                    const QString& name, const QString& iconName,
                                    const QString& destUrl )
{
    entry->insert( UDSEntry::UDS_NAME,              name);
    entry->insert( UDSEntry::UDS_FILE_TYPE,         S_IFDIR );//REG or DIR or LNK);
    entry->insert( UDSEntry::UDS_ACCESS,            0555 );
    entry->insert( UDSEntry::UDS_MIME_TYPE,         "inode/directory" );
//         entry->insert( KIO::UDS_SIZE, 0);
    entry->insert( UDSEntry::UDS_ICON_NAME,         iconName );
    entry->insert( UDSEntry::UDS_CREATION_TIME,     1 );
    entry->insert( UDSEntry::UDS_MODIFICATION_TIME, time(0) );
    if ( ! destUrl.isEmpty() )
        entry->insert( UDSEntry::UDS_URL,    destUrl ); // UDS_TARGET_URL
//     if (!displayName.isEmpty())
//         entry->insert( UDSEntry::UDS_DISPLAY_NAME, displayName );
}

void ServicesSlave::feedEntryAsFile( KIO::UDSEntry* entry,
                                     const QString& name, const QString& iconName, const QString& mimeTypeName,
                                     const QString& displayName, const QString& destUrl )
{
    entry->insert( UDSEntry::UDS_NAME,              name);
    entry->insert( UDSEntry::UDS_FILE_TYPE,         S_IFREG );
    entry->insert( UDSEntry::UDS_ACCESS,            0555 );
    entry->insert( UDSEntry::UDS_MIME_TYPE,         mimeTypeName );
//         entry->insert( KIO::UDS_SIZE, 0);
    entry->insert( UDSEntry::UDS_ICON_NAME,         iconName );
    entry->insert( UDSEntry::UDS_CREATION_TIME,     1 );
    entry->insert( UDSEntry::UDS_MODIFICATION_TIME, time(0) );
    if ( ! destUrl.isEmpty() )
        entry->insert( UDSEntry::UDS_URL,    destUrl ); // UDS_TARGET_URL
    if (!displayName.isEmpty())
        entry->insert( UDSEntry::UDS_DISPLAY_NAME, displayName );
}


SLPBoolean ServicesSlave::onServiceTypesFound( SLPHandle hslp, const char* serviceTypes, SLPError errorCode, void* kio_slave )
{
Q_UNUSED( hslp )

    if(errorCode == SLP_LAST_CALL)
        return SLP_FALSE;

    if ( !serviceTypes || !*serviceTypes )
        return SLP_TRUE; /* ignore, go on .. */

    if ( errorCode != SLP_OK )
        return SLP_TRUE; /* ignore, go on .. */

    // optimize by operating on original data as much as possible
    const QStringList serviceTypeList = QString(serviceTypes).split( ',' );
    foreach( const QString& rawServiceType, serviceTypeList )
    {
        const QString serviceType = ServiceListingUrl::createServiceTypeFrom( rawServiceType );

        static_cast<ServicesSlave*>( kio_slave )->addServiceTypeEntry( serviceType );
    }

    return SLP_TRUE;
}

SLPBoolean ServicesSlave::onServiceFound( SLPHandle hslp, const char* serviceUrl, unsigned short lifetime, SLPError errorCode, void* kio_slave )
{
Q_UNUSED( hslp )
Q_UNUSED( lifetime )

    if (errorCode == SLP_LAST_CALL)
        return SLP_FALSE;

    if ( !serviceUrl || !*serviceUrl )
        return SLP_TRUE; /* ignore, go on .. */

    if ( errorCode != SLP_OK )
        return SLP_TRUE; /* ignore, go on .. */

    QString serviceType;
    QString siteNParameter;
    const bool success = ServiceListingUrl::splitSlpServiceUrl( &serviceType, &siteNParameter, serviceUrl );

    if ( ! success )
        return SLP_TRUE;

    const QString mdnsName = ( SLPGetMDNSName ) ? QString::fromUtf8(SLPGetMDNSName(hslp, serviceUrl)) : QString();

    static_cast<ServicesSlave*>( kio_slave )->addServiceEntry( serviceType, siteNParameter, mdnsName );

    return SLP_TRUE;
}

SLPBoolean ServicesSlave::onAttributesFound( SLPHandle hslp, const char* attrlist, SLPError errorCode, void* kio_slave )
{
Q_UNUSED( hslp )

    QString& attributeHtml = static_cast<ServicesSlave*>( kio_slave )->attributeHtml();

    if( errorCode == SLP_OK )
    {
        QString l = QString::fromUtf8(attrlist); //right for mdns at least
        // TODO: create QDomDocument and enable external (KDE) css styling
        attributeHtml +=
            "<center>"
            " <table bgcolor=\"white\" cellpadding=5>"
            "  <caption></caption>"
            "  <thead bgcolor=\"#000000\">"
            "    <tr>"
            "      <th><font color=\"#EEEEEE\">Attribute</font></th>"
            "      <th><font color=\"#EEEEEE\">Value</font></th>"
            "    </tr>"
            "  </thead>"
            "  <tbody>";
        bool colorFlag = true;
        // TODO: speed up by not resetting l but tracking the current index
        while ( !l.isEmpty() )
        {
            const QString color = colorFlag ? "white" : "#eff7ff";
            colorFlag = !colorFlag;

            if ( l[0] == '(' )
            {
                const int indexOfBracket = l.indexOf( ')' );
                QString m = l.mid( 1, indexOfBracket-1 );
                attributeHtml += "<tr valign=top bgcolor=\"" + color + "\"><td>";
                const int indexOfEquals = m.indexOf( '=' );
                attributeHtml += m.left( indexOfEquals );
                attributeHtml += "</td><td align=right>";
                m.remove( 0, indexOfEquals+1 );
                attributeHtml += m.split( ',' ).join("<br>");
                attributeHtml += "</td></tr>";

                l.remove( 0, indexOfBracket+2 );
            }
            else
            {
                const int indexOfComma = l.indexOf( ',' );
                QString m = l.left( indexOfComma );
                attributeHtml += "<tr valign=top bgcolor=\"" + color + "\"><td>";
                attributeHtml += m;
                attributeHtml += "</td><td>&nbsp;</td></tr>";

                l.remove( 0, indexOfComma+1 );
            }
        }

        attributeHtml += "   </center>"
            "   </td>"
            "  </tr>"
            "  </tbody>"
            "</table>";
    }
    else
    {
        attributeHtml += "</body></html>";
        static_cast<ServicesSlave*>( kio_slave )->data( attributeHtml.toUtf8() );
        static_cast<ServicesSlave*>( kio_slave )->finished();
    }

    return SLP_FALSE;
}
