//-*-c++-*-
/***************************************************************************
 *   Copyright (C) 2003 by Fred Schaettgen                                 *
 *   kdebluetooth@schaettgen.de                                            *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "kiobluetooth.h"

#include <sys/stat.h>
#include <qregexp.h>
#include <unistd.h>
#include <algorithm>

#include <kdebug.h>
#include <klocale.h>
#include <kinstance.h>
#include <dcopclient.h>

#include <libkbluetooth/inquiry.h>
#include <libkbluetooth/adapter.h>
#include <libkbluetooth/devicemimeconverter.h>
#include <libkbluetooth/namecache.h>

using namespace KIO;
using namespace KBluetooth;
using namespace std;

KioBluetooth::KioBluetooth(const QCString &pool_socket, const QCString &app_socket) :
        SlaveBase("kio_bluetooth", pool_socket, app_socket)
{
    kdDebug() << "KioBluetooth::KioBluetooth()" << endl;
    DevInfo localInfo;
    localInfo.realName = localInfo.uniqueName = "localhost";
    localInfo.address = DeviceAddress("FF:FF:FF:00:00:00");
    deviceList.push_back(localInfo);
    if (Adapters().count() == 0) {
        warning(i18n("No working Bluetooth adapter found. \
Only local services will be displayed."));
    }
}

KioBluetooth::~KioBluetooth()
{
    kdDebug() << "KioBluetooth::~KioBluetooth()" << endl;
}

/*void KioBluetooth::mimetype(const KURL &url)
{
    kdDebug(7101) << "kio_bluetooth::mimetype(" << url.url() << ")" << endl ;
    //mimeType("text/plain");
    finished();
}*/

void KioBluetooth::stat(const KURL &url)
{
    kdDebug() << "kio_bluetooth::stat(" << url.url() << ")" << endl ;
    UDSEntry entry;
    QString path = url.path(+1);
    if (url.hasHost() == false && path == "/")
    {
        createDirEntry(entry, "Bluetooth neighborhood");
        statEntry(entry);
        finished();
    }
    else {
        error(KIO::ERR_SLAVE_DEFINED,
            QString(i18n("Could not stat %1.").arg(url.url())));
    }
}

void KioBluetooth::get(const KURL &/*url*/)
{
    kdDebug() << "kio_bluetooth: get() was called! This is nonsense." << endl ;
    error(KIO::ERR_IS_DIRECTORY, QString::null);
}

void KioBluetooth::listDir(const KURL &url)
{
    QString host = url.host();
    QString path = url.path(+1);

    kdDebug() << "kio_bluetooth::listdir(" << host << ") (" << path <<  ")" << endl ;

    if (host == QString::null && path == "/") {
        doListBrowse(url);
    }
    else {
        doListInvalid(url);
    }
}


struct AddrInfo {
    DeviceAddress devAddr;
    int devClass;
    bool noname;
};

vector<DeviceAddress> KioBluetooth::getCurrentConnections()
{
    Adapters adapters;
    vector<DeviceAddress> connections;
    if (adapters.count() > 0) {
        Adapter::ConnectionInfoVector connectionInfos = adapters[0].getAclConnections();
        for (int n=0; n < int(connectionInfos.size()); n++) {
            connections.push_back(connectionInfos[n].address);
        }
    }
    return connections;
}

vector<DeviceAddress> KioBluetooth::getCurrentNonDiscoverableDevices()
{
    vector<DeviceAddress> ret;
    QByteArray retArray;
    QDataStream retStream(retArray, IO_ReadOnly);
    QCString replyType;
    if (dcopClient()->call("kbluetoothd", "DeviceScanner", "getCurrentNeighbours()",
        QByteArray(), replyType, retArray, false) && replyType == "QStringList") 
    {
        QStringList retStrList;
        retStream >> retStrList;
        for (uint n = 0; n < retStrList.size(); ++n) {
            ret.push_back(KBluetooth::DeviceAddress(retStrList[n]));
        } 
    }
    return ret;
}

void KioBluetooth::listDevice(const QString& devName, int devClass)
{
    UDSEntry entry;
    UDSEntryList entryList;
            
    entryList.clear();
    entry.clear();
    createDirEntry(entry, devName,
        QString("sdp://[%1]/").arg(devName),
        DeviceClassMimeConverter::classToMimeType(devClass));
    entryList.append(entry);
    listEntries(entryList);
}

bool KioBluetooth::doListBrowse(const KURL&)
{
    kdDebug() << "sdp::doListBrowse()" << endl;

    // Add an entry for the local system
    listDevice("localhost", 0);

    vector<DeviceAddress> connections = getCurrentConnections();
    for (size_t n=0; n<connections.size(); ++n) {
        QString devName = QString(connections[n]);
        NameCache::getCachedName(connections[n], devName, dcopClient());
        if (devName == QString::null) {
            devName = QString(connections[n]);
        } 
        int devClass = 0;
        NameCache::getCachedClass(connections[n], devClass, dcopClient());
        listDevice(devName, devClass);
    }
    
    vector<DeviceAddress> foundNondiscoverable = getCurrentNonDiscoverableDevices();
    for (size_t n=0; n<foundNondiscoverable.size(); ++n) {
        if (find(connections.begin(), connections.end(), foundNondiscoverable[n]) != connections.end())
        {
            continue;
        }
        QString devName = QString(foundNondiscoverable[n]);
        NameCache::getCachedName(foundNondiscoverable[n], devName, dcopClient());
        if (devName == QString::null) {
            devName = QString(foundNondiscoverable[n]);
        }
        int devClass = 0;
        NameCache::getCachedClass(foundNondiscoverable[n], devClass, dcopClient());
        listDevice(devName, devClass);
    }
        
    KBluetooth::Inquiry inquiry;
    QValueList<AddrInfo> addresses;
    const size_t numRetries = 5;
    for (size_t n=0; n<numRetries; ++n) {
        inquiry.inquiry();
        KBluetooth::DeviceAddress devAddr;
        int devClass;
        QString sTry;
        if (n==0) {
            sTry = i18n("Searching for devices... ");
        }
        else {
            sTry = i18n("Searching for devices (retry %1 of %2)...").arg(n).arg(numRetries);
        }
        infoMessage(sTry);
        while (inquiry.nextNeighbour(devAddr, devClass)) {
        
            // Skip devices which were listed already because we are connected to them.
            if (find(connections.begin(), connections.end(), devAddr) != 
                connections.end()) 
            {
                continue;
            }
            if (find(foundNondiscoverable.begin(), foundNondiscoverable.end(), devAddr) != 
                foundNondiscoverable.end()) 
            {
                continue;
            }
            
            QString cachedName = QString(devAddr);
            NameCache::getCachedName(devAddr, cachedName, dcopClient());
            AddrInfo info;
            info.devClass = devClass;
            info.devAddr = devAddr;
            if (cachedName != QString::null) {
                // We immediately output every device which has
                // a cached name.
                listDevice(cachedName, devClass);
                info.noname = false;
            }
            else {
                // Items without a cached name will have to to wait
                // until after the inquiry.
                info.noname = true;
            }
            addresses.append(info);
        }
        if (inquiry.isComplete()) {
            break;
        }
        else {
            ::sleep(5);
        }
    }

    // Now we do a name request for all addresses
    // to refresh their name, but we only output the
    // devices without a cached name here.
    QValueList<AddrInfo>::const_iterator infoIt = addresses.begin();
    for (; infoIt != addresses.end(); ++infoIt) {
        if (wasKilled()) {
            kdDebug() << "Slave was killed. Skipping name update." << endl;
            finished();
            return true;        
        };
        
        infoMessage(i18n("Updating name for device %1.").arg(QString((*infoIt).devAddr)));
        QString devName = nameRequester.resolve((*infoIt).devAddr);
        if (devName == QString::null) {
            devName = QString((*infoIt).devAddr);
        }
        if ((*infoIt).noname) {
            listDevice(devName, (*infoIt).devClass);
        }
    }

    
    listEntry(UDSEntry(), true); // ready
    finished();
    return true;
}


bool KioBluetooth::doListInvalid(const KURL &url)
{
    error(KIO::ERR_MALFORMED_URL, url.url());
    return true;
}


bool KioBluetooth::createDirEntry(UDSEntry &entry, const QString &title,
    const QString &url, const QString &mimeType)
{
    entry.clear();

    addAtom(entry, UDS_NAME, title);

    if (url != QString::null)
    {
        addAtom(entry, UDS_URL, url);
    }

    addAtom(entry, UDS_MIME_TYPE, mimeType);
    addAtom(entry, UDS_FILE_TYPE, S_IFDIR);
    // No more necessary
    // addAtom(entry, UDS_GUESSED_MIME_TYPE, "inode/folder");

    return true;
}


void KioBluetooth::addAtom(KIO::UDSEntry &entry, KIO::UDSAtomTypes type, QString s)
{
    UDSAtom atom;
    atom.m_uds = type;
    atom.m_str = s;
    entry.append(atom);
}


void KioBluetooth::addAtom(KIO::UDSEntry &entry, KIO::UDSAtomTypes type, long l)
{
    UDSAtom atom;
    atom.m_uds = type;
    atom.m_long = l;
    entry.append(atom);
}


extern "C"
{
    int kdemain(int argc, char **argv)
    {
        KInstance instance( "kio_bluetooth" );
        kdDebug() << "*** Starting kio_bluetooth " << endl;
        if (argc != 4)
        {
            kdDebug() << "Usage: kio_bluetooth  protocol domain-socket1 domain-socket2" << endl;
            exit(-1);
        }
        KioBluetooth slave(argv[2], argv[3]);
        slave.dispatchLoop();
        kdDebug() << "*** kio_bluetooth Done" << endl;
        return 0;
    }
}
