/*
 This file is part of tdeio_obex.

 Copyright (c) 2003 Mathias Froehlich <Mathias.Froehlich@web.de>

 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 Steet, Fifth Floor,
 Boston, MA 02110-1301, USA.
 */

#include <stdlib.h>
#include <unistd.h>
//#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>

#include <kdebug.h>

#include <tdeapplication.h>
#include <dcopclient.h>
#include <tdecmdlineargs.h>

#include <tqdbusvariant.h>
#include <tqdbusdatalist.h>
#include <tqdbusdatamap.h>

#include "tdeio_obex.h"

static const TDECmdLineOptions options[] = { { "+protocol", I18N_NOOP(
        "Protocol name"), 0 }, { "+pool", I18N_NOOP("Socket name"), 0 }, {
                "+app", I18N_NOOP("Socket name"), 0 }, TDECmdLineLastOption };

extern "C" {
    int TDE_EXPORT kdemain( int argc, char **argv )
    {
        // TDEApplication is necessary to use other ioslaves
        putenv(strdup("SESSION_MANAGER="));
        TDECmdLineArgs::init(argc, argv, "tdeio_obex", 0, 0, 0, 0);
        TDECmdLineArgs::addCmdLineOptions( options );
        TDEApplication app( false, false, false );
        TDELocale::setMainCatalogue("tdebluez");

        // We want to be anonymous even if we use DCOP
        app.dcopClient()->attach();

        TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();
        ObexProtocol slave( args->arg(0), args->arg(1), args->arg(2) );
        slave.dispatchLoop();
        return 0;
    }
}

ObexProtocol::ObexProtocol(const TQCString &protocol, const TQCString &pool_socket, const TQCString &app_socket) :
        SlaveBase(protocol, pool_socket, app_socket)
{
    kdDebug() << k_funcinfo << endl;
    mChannel = 0;
    mAddress = TQString::null;
    mSessionPath = TQString();
    mFileTransfer = 0;
    mSessionProperties = 0;
    mSession = 0;
    mClient = 0;
    mProtocol = protocol;
    mHost = TQString::null;
    mConnected = false;

    mManager = new TDEObex::ObexObjectManagerImpl("org.bluez.obex", "/");
    if (!mManager->isConnectedToDBUS())
    {
        TQString err = i18n("ObexObjectManager is not connected to DBus");
        tqDebug(err);
        //        infoMessage(i18n("Error"));
        TDEIO::SlaveBase::error(TDEIO::ERR_COULD_NOT_CONNECT, err);
        exit();
    }

    kdDebug() << "ObexProtocol::ObexProtocol DBus connection: " << (*(mManager->getConnection())).uniqueName() << endl;

    if (mProtocol == "obexftp" || mProtocol == "obexopp")
    {
        obex = new Obex(mProtocol);
        //        mConnected = connectObex();
    }
    else
        exit();

    if (!mClient)
        mClient = mManager->getClient();
    if (!mClient)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_COULD_NOT_CONNECT, i18n("ObexClient was not created"));
        exit();
    }

}

ObexProtocol::~ObexProtocol()
{
    kdDebug() << k_funcinfo << endl;
    if (mConnected)
        closeObex();
    if (obex)
        delete obex;
    if (mManager)
        delete mManager;
}

void ObexProtocol::closeConnection()
{
    kdDebug() << k_funcinfo << endl;
    closeObex();
}

void ObexProtocol::closeObex()
{
    kdDebug() << k_funcinfo << endl;

    TQT_DBusError dbuserror;
    if (mConnected && !mSessionPath.isEmpty())
    {
        //      infoMessage(i18n("Disconnecting"));
        if (!mClient->RemoveSession(mSessionPath, dbuserror))
        {
            if (dbuserror.isValid())
                TDEIO::SlaveBase::error(TDEIO::ERR_COULD_NOT_CONNECT, i18n(dbuserror.message().utf8()));
        }
        //      infoMessage(i18n("Disconnected"));
    }

    if (mFileTransfer)
        delete mFileTransfer;
    if (mSessionProperties)
        delete mSessionProperties;
    if (mSession)
        delete mSession;
    if (mClient)
        delete mClient;
    mConnected = false;

    exit();
}

//void ObexProtocol::openConnection()
//{
//    kdDebug() << k_funcinfo << endl;
//
//}

bool ObexProtocol::connectObex()
{
    kdDebug() << k_funcinfo << endl;

    TQT_DBusError dbuserror;

    TQT_DBusVariant obexprot;
    if (mProtocol == "obexftp")
        obexprot.value = TQT_DBusData::fromString("00001106-0000-1000-8000-00805f9b34fb");
    else if (mProtocol == "obexopp")
        obexprot.value = TQT_DBusData::fromString("00001105-0000-1000-8000-00805f9b34fb");
    else if (mProtocol == "obexmap")
        obexprot.value = TQT_DBusData::fromString("00001134-0000-1000-8000-00805f9b34fb");
    else if (mProtocol == "obexpbap")
        obexprot.value = TQT_DBusData::fromString("00001130-0000-1000-8000-00805f9b34fb");
    else if (mProtocol == "obexsync")
        obexprot.value = TQT_DBusData::fromString("00001104-0000-1000-8000-00805f9b34fb");
    obexprot.signature = obexprot.value.buildDBusSignature();
    TQMap<TQString, TQT_DBusVariant> args;
    args.insert(TQString("Target"), obexprot);

    if (mSessionPath.isEmpty())
    {
        kdDebug() << "ObexProtocol::connectObex : trying to create session" << endl;
        if (!mClient->CreateSession(mAddress, args, mSessionPath, dbuserror))
        {
            TDEIO::SlaveBase::error(TDEIO::ERR_COULD_NOT_CONNECT, i18n("Could not create session for  %1.").arg(mAddress));
            return false;
        }
    }

    kdDebug() << "ObexProtocol::connectObex mSessionPath: " << mSessionPath << endl;
    if (!mSession)
    {
        mSession = new org::bluez::obex::Session1Proxy("org.bluez.obex", mSessionPath);
        mSession->setConnection((*(mManager->getConnection())));

        mSessionProperties = new org::freedesktop::DBus::PropertiesProxy("org.bluez", mSessionPath);
        mSessionProperties->setConnection((*(mManager->getConnection())));

        connect(mSessionProperties, TQ_SIGNAL(PropertiesChanged ( const TQString&, const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )),
                this, TQ_SLOT(slotPropertiesChanged ( const TQString& , const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )));

        mFileTransfer = new org::bluez::obex::FileTransfer1Proxy("org.bluez.obex", mSessionPath);
        mFileTransfer->setConnection((*(mManager->getConnection())));

    }
    if (mClient != 0 && mSession != 0 && mFileTransfer != 0)
        mConnected = true;
    return mConnected;
}

void ObexProtocol::listDir(const KURL &url)
{
    kdDebug() << k_funcinfo << endl;
    kdDebug() << "utl:  " << url.url() << endl;
    kdDebug() << "path: " << url.path() << endl;

    if (url.path().length() <= 1)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
        finished();
        return;
    }

    TQString address, name, path;
    bool ok = obex->parseURL(url, address, name, path);

    if (!ok || address.isEmpty())
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
        finished();
        return;
    }
    mAddress = address;

    kdDebug() << k_funcinfo << " address " << mAddress << endl;
    kdDebug() << k_funcinfo << " name    " << name << endl;
    kdDebug() << k_funcinfo << " path    " << path << endl;
    kdDebug() << k_funcinfo << " at line " << __LINE__ << endl;

    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }

    if (!path.isEmpty())
    {
        if (!changeWorkingDirectory(path))
        {
            TDEIO::SlaveBase::error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, path);

            finished();
            closeObex();
            return;
        }
    }

    kdDebug() << k_funcinfo << " at line " << __LINE__ << endl;
    TQT_DBusDataList folderinfo;
    TQT_DBusError dbuserror;
    if (!mFileTransfer->ListFolder(folderinfo, dbuserror))
    {
        if (dbuserror.isValid())
        {
            TDEIO::SlaveBase::error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, i18n("%1.\n%2").arg(url.prettyURL()).arg(dbuserror.message()));
        }
        else
        {
            TDEIO::SlaveBase::error(TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL());
        }

        finished();
        closeObex();
        return;
    }

    TDEIO::UDSEntryList entries;
    entries.clear();

    TQValueList<TQT_DBusData> vl = folderinfo.toTQValueList();
    TQValueList<TQT_DBusData>::Iterator dit = vl.begin();
    for (dit; dit != vl.end(); ++dit)
    {
        bool ok = false;
        TQMap<TQString, TQT_DBusData> map = (*dit).toStringKeyMap(&ok).toTQMap();
        if (!ok)
        {
            kdDebug() << k_funcinfo << " failed " << endl;
            continue;
        }
        TDEIO::UDSEntry entry = obex->createUDSEntry(map);
        entries.append(entry);
        kdDebug() << k_funcinfo << " at line " << __LINE__ << endl;
    }

    listEntries(entries);
    listEntry(UDSEntry(), true); // ready

    finished();
    closeObex();
}

void ObexProtocol::stat(const KURL &url)
{
    kdDebug() << k_funcinfo << " url: " << url << endl;

    TQString address, name, path;
    bool ok = obex->parseURL(url, address, name, path);
    kdDebug() << k_funcinfo << " addr: " << address << endl;
    kdDebug() << k_funcinfo << " name: " << name << endl;
    kdDebug() << k_funcinfo << " path: " << path << endl;

    if (!ok || address.isEmpty())
    {
        error(TDEIO::ERR_MALFORMED_URL, url.prettyURL());
        return;
    }

    TDEIO::UDSEntry entry;
    if (path.isEmpty() || path == "/")
    {
        // The root is "virtual" - it's not a single physical directory
        obex->createTopLevelEntry(entry);
    }
    else
    {
        obex->createDirEntry(entry, url.url());
    }
    statEntry(entry);
    finished();
}

void ObexProtocol::get(const KURL& url)
{
    kdDebug() << k_funcinfo << endl;

    if (!mFileTransfer)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not get file: %1. No file transport").arg(url.prettyURL()));
        return;
    }
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }
//    TQT_DBusError dbuserror;
//    if (!mFileTransfer->GetFile(url.url(), dbuserror)) {
//        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not get file: %1.").arg(dbuserror.message()));
//    }
}

void ObexProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite)
{
    kdDebug() << k_funcinfo << endl;
    //	obex->copy(src, dest, permissions, overwrite);
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }
}

void ObexProtocol::put(const KURL& url, int permissions, bool overwrite, bool resume)
{
    kdDebug() << k_funcinfo << endl;
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }
    if (!mFileTransfer)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not put file: %1. No file transport").arg(url.prettyURL()));
        return;
    }

//
//    TQT_DBusError dbuserror;
//    if (!mFileTransfer->PutFile(url.url(), dbuserror)) {
//        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not put file: %1.").arg(dbuserror.message()));
//    }

}

void ObexProtocol::del(const KURL &url, bool isfile)
{
    kdDebug() << k_funcinfo << endl;

    if (!isfile)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Only files can be deleted. Request to delete: %1").arg(url.prettyURL()));
        return;
    }
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }
    if (!mFileTransfer)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not delete file: %1. No file transport").arg(url.prettyURL()));
        return;
    }

    TQT_DBusError dbuserror;
    if (!mFileTransfer->Delete(url.url(), dbuserror))
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not delete file: %1.").arg(dbuserror.message()));
    }

}

//void ObexProtocol::rename(const KURL& src, const KURL& dest, bool overwrite)
//{
//    kdDebug() << k_funcinfo << endl;
//    //	obex->rename(src, dest, overwrite);
//
//}

void ObexProtocol::mkdir(const KURL&url, int permissions)
{
    kdDebug() << k_funcinfo << endl;
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return;
        }
    }
    if (!mFileTransfer)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not create directory: %1. No file transport").arg(url.prettyURL()));
        return;
    }

    TQT_DBusError dbuserror;
    if (!mFileTransfer->CreateFolder(url.url(), dbuserror))
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not create directory: %1.").arg(dbuserror.message()));
    }

}

bool ObexProtocol::changeWorkingDirectory(const TQString& dir)
{
    kdDebug() << "ObexProtocol::changeWorkingDirectory( " << dir << " )" << endl;
    if (!dir.startsWith("/"))
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_MALFORMED_URL, i18n("Could not change directory: %1. Directory should start with \"/\"").arg(dir));
        return false;
    }
    if (!mConnected)
    {
        if (!connectObex())
        {
            finished();
            return false;
        }
    }
    if (!mFileTransfer)
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not change directory: %1. No file transport").arg(dir));
        return false;
    }

    TQT_DBusError dbuserror;
    if (!mFileTransfer->ChangeFolder(dir, dbuserror))
    {
        TDEIO::SlaveBase::error(TDEIO::ERR_INTERNAL, i18n("Could not change directory: %1.").arg(dbuserror.message()));
        return false;
    }

    return true;
}

void ObexProtocol::slotPropertiesChanged(const TQString& interface, const TQMap<
        TQString, TQT_DBusVariant>& changed_properties, const TQStringList& invalidated_properties)
{
    kdDebug() << k_funcinfo << endl;
    kdDebug() << interface << endl;

    if (interface == "org.bluez.obex.Session1")
    {
        TQMap<TQString, TQT_DBusVariant>::const_iterator it;
        for (it = changed_properties.begin(); it != changed_properties.end();
                ++it)
        {
            bool ok = false;
            if (it.key() == "Source")
                emit sessionSourceChanged(mSessionPath, it.data().value.toBool(&ok));
            else if (it.key() == "Destination")
                emit sessionDestinationChanged(mSessionPath, it.data().value.toString(&ok));
            else if (it.key() == "Channel")
                emit sessionChannelChanged(mSessionPath, it.data().value.toByte(&ok));
            else if (it.key() == "Target")
                emit sessionTargetChanged(mSessionPath, it.data().value.toString(&ok));
            else if (it.key() == "Root")
                emit sessionRootChanged(mSessionPath, it.data().value.toString(&ok));
            else
                continue;
            if (!ok)
                tqDebug(i18n("ObjectManagerImpl::slotPropertiesChanged conversion failed"));
        }
    }

    if (interface == "org.bluez.obex.FileTransfer1" || interface == "org.bluez.obex.Transfer1")
    {
        TQMap<TQString, TQT_DBusVariant>::const_iterator it;
        for (it = changed_properties.begin(); it != changed_properties.end();
                ++it)
        {
            bool ok = false;
            if (it.key() == "Size")
                emit transferSizeChanged(mSessionPath, it.data().value.toUInt64(&ok));
            else if (it.key() == "Status")
                emit transferStatusChanged(mSessionPath, it.data().value.toString(&ok));
            else if (it.key() == "Transferred")
                emit transferTransferredChanged(mSessionPath, it.data().value.toUInt64(&ok));
            else if (it.key() == "Time")
                emit transferTimeChanged(mSessionPath, it.data().value.toUInt64(&ok));
            else if (it.key() == "Filename")
                emit transferFilenameChanged(mSessionPath, it.data().value.toString(&ok));
            else
                continue;
            if (!ok)
                tqDebug(i18n("ObjectManagerImpl::slotPropertiesChanged conversion failed"));
        }
    }
}

#include "tdeio_obex.moc"
