/*
 *
 *  New Bluetooth App for TDE and bluez5
 *
 *  Copyright (C) 2018  Emanoil Kotsev <deloptes@gmail.com>
 *
 *
 *  This file is part of tdebluez.
 *
 *  tdebluez 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.
 *
 *  tdebluez 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 kbluetooth; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include <dcopclient.h>
#include <kuser.h>
#include <tqregexp.h>

#include <errno.h>

#include <sys/types.h>
#include <signal.h>

#include "trayicon.h"
#include "application.h"

#define WAIT_BEFORE_KILL    5000 // 5sec
#define CONFIGURATION_FILE  "tdebluezrc"
#define TDEBLUEZAUTH_EXE    "tdebluezauth"
#define OBEX_EXE    "/usr/lib/bluetooth/obexd"
#define OBEX_EXE_ALT    "/usr/libexec/bluetooth/obexd"
#define BLUEZ_DN    "org.bluez"
#define DOWNLOAD_DIRECTORY "Downloads"

TDEBluetoothApp::TDEBluetoothApp() :
        KUniqueApplication()
{
    // set default config file
    m_config = new TDEConfig(CONFIGURATION_FILE);
    m_config->setGroup("General");
    bool autostart = m_config->readBoolEntry("autoStart", false);
    m_waitBeforeKill = m_config->readNumEntry("waitBeforeKill", WAIT_BEFORE_KILL);
    TQString authAgentExe = m_config->readEntry("authAgentExe", TDEBLUEZAUTH_EXE);
    TQString obexSrvExe = m_config->readEntry("obexSrvExe", OBEX_EXE);
    TQString downloadDir = m_config->readPathEntry("downloadDir", "");

    if (!autostart)
        disableSessionManagement();

    if (m_waitBeforeKill == 0)
    {
        m_waitBeforeKill = WAIT_BEFORE_KILL;
        m_config->writeEntry("waitBeforeKill", m_waitBeforeKill);
    }

    if (obexSrvExe.isEmpty())
    {
        obexSrvExe=TQString(OBEX_EXE);
    }

    if (authAgentExe.isEmpty())
    {
        authAgentExe = TDEBLUEZAUTH_EXE;
        m_config->writePathEntry("authAgentExe", authAgentExe);
    }

    if (downloadDir.isEmpty())
    {
        downloadDir = DOWNLOAD_DIRECTORY;
        m_config->writePathEntry("downloadDir", downloadDir);
    }

    manager = new ObjectManagerImpl(BLUEZ_DN, "/", this, "ObjectManager");
    if (!manager->isConnectedToDBUS())
    {
        tqDebug(i18n("ObjectManager is not connected to DBus"));
        return;
    }

    ObjectManagerImpl::AdapterList al = manager->getAdapters();
    ObjectManagerImpl::AdapterList::Iterator ait = al.begin();
    for (ait; ait != al.end(); ++ait)
    {
        AdapterImpl *a = new AdapterImpl(BLUEZ_DN, (*ait));
        a->setConnection((*(manager->getConnection())));
        adapters.insert((*ait), a);
    }

    ObjectManagerImpl::DeviceList dl = manager->getDevices();
    ObjectManagerImpl::DeviceList::Iterator dit = dl.begin();
    for (dit; dit != dl.end(); ++dit)
    {
        DeviceImpl *d = new DeviceImpl(BLUEZ_DN, (*dit));
        d->setConnection((*(manager->getConnection())));
        devices.insert((*dit), d);
    }

    authAgent = new TQProcess(authAgentExe, this);
    authAgent->addArgument("--nofork");

    TQFileInfo obexSrvExeFile( obexSrvExe );
    if (!obexSrvExeFile.exists())
    {
        obexSrvExeFile.setFile(OBEX_EXE_ALT);
        if (obexSrvExeFile.exists())
        {
            m_config->writePathEntry("obexSrvExe", obexSrvExeFile.filePath());
        }
        else
        {
            tqWarning(i18n("obexd executable was not found\nSet path in configuration file \"%1\"\nVariable: obexSrvExe=<path to obexd>").arg(CONFIGURATION_FILE));
            m_config->writeEntry("obexSrv", false);
        }
    }

    obexServer = new TQProcess(obexSrvExeFile.filePath(), this);
    obexServer->addArgument("-n");
    obexServer->addArgument("-a");
    obexServer->addArgument("-l");
    obexServer->addArgument("-r");
    obexServer->addArgument(downloadDir);

    //stop tdebluezauth or obexd daemon to regain control
    KUser user;
    long uid = user.uid();

    TQDir d("/proc");
    d.setFilter(TQDir::Dirs);

    TQRegExp rx( "^\\d+$" );
    for (int i = 0; i < d.count(); i++)
    {
        if ( ! rx.exactMatch(d[i]) )
            continue;

        TQFile f("/proc/" + d[i] + "/status");
        if ( ! f.open( IO_ReadOnly ) )
        {
            tqDebug(i18n("Failed to open file for reading: %1").arg(f.name()));
            continue;
        }

        TQTextStream stream( &f );
        TQString pid;
        bool tokill = false;
        while ( !stream.atEnd() ) {
            TQString line = stream.readLine(); // line of text excluding '\n'
            if (line.startsWith("Name:") &&
                    ( line.endsWith("tdebluezauth") || line.endsWith("obexd") ))
            {
                pid = d[i];
            }
            if (line.find(TQRegExp(TQString("Uid:\\s+%1").arg(uid))) != -1)
            {
                tokill = true;
            }
        }
        f.close();

        if (tokill && ! pid.isEmpty())
        {
            if ( kill( (pid_t) pid.toLong(), SIGKILL ) == -1 )
            {
              switch ( errno )
              {
                case EINVAL:
                    tqDebug(i18n("4\t%1").arg(pid) );
                  break;
                case ESRCH:
                    tqDebug(i18n("3\t%1").arg(pid) );
                  break;
                case EPERM:
                    tqDebug(i18n("2\t%1").arg(pid) );
                  break;
                default: /* unknown error */
                    tqDebug(i18n("1\t%1").arg(pid) );
                  break;
              }
            } else {
                tqWarning(i18n("Cleanup pid\t%1").arg(pid) );
            }
        }
    }

    // connect to manager signals
    connect(manager, TQ_SIGNAL(adapterAdded(const TQString&)), TQ_SLOT(slotAdapterAdded(const TQString&)));
    connect(manager, TQ_SIGNAL(adapterRemoved(const TQString&)), TQ_SLOT(slotAdapterRemoved(const TQString&)));
    connect(manager, TQ_SIGNAL(deviceAdded(const TQString&)), TQ_SLOT(slotDeviceAdded(const TQString&)));
    connect(manager, TQ_SIGNAL(deviceRemoved(const TQString&)), TQ_SLOT(slotDeviceRemoved(const TQString&)));
//    connect(manager, TQ_SIGNAL(adapterPowerOnChanged(const TQString&, bool)), TQ_SLOT(slotPowerOnChanged(const TQString&, bool)));

    trayIcon = new TrayIcon(this);
    setMainWidget(trayIcon);
}

TDEBluetoothApp::~TDEBluetoothApp()
{

    if (obexServer)
    {
        if (obexServer->isRunning())
            obexServer->kill();
        delete obexServer;
    }
    if (authAgent)
    {
        if (authAgent->isRunning())
            authAgent->kill();
        delete authAgent;
    }
    delete trayIcon;

    if (manager->isConnectedToDBUS())
    {
        DevicesMap::Iterator dit = devices.begin();
        for (dit; dit != devices.end(); ++dit)
        {
            DeviceImpl *d = dit.data();
            if (d)
                delete d;
        }
        devices.clear();

        AdaptersMap::Iterator ait = adapters.begin();
        for (ait; ait != adapters.end(); ++ait)
        {
            AdapterImpl *a = ait.data();
            if (a)
            {
                TQT_DBusError error;
                if (a->getDiscovering(error))
                    a->StopDiscovery(error);
                if (error.isValid())
                    tqDebug(i18n("Stop discoverable for the adapter failed: %1").arg(error.message()));
                delete a;
            }
        }
        adapters.clear();
    }
    delete manager;

    if (m_config->isDirty())
        m_config->sync();

    delete m_config;
}

bool TDEBluetoothApp::startAuthAgent()
{
    if (!authAgent->isRunning())
    {
        if (!authAgent->start())
            return false;
    }
    return true;
}

bool TDEBluetoothApp::stopAuthAgent()
{
    if (authAgent->isRunning())
    {
        authAgent->tryTerminate();
        TQTimer::singleShot(m_waitBeforeKill, authAgent, TQ_SLOT(kill()));
    }
    return true;
}

bool TDEBluetoothApp::startObexSrv()
{
    if (!obexServer->isRunning())
    {
        if (!obexServer->start())
            return false;
    }
    return true;
}

bool TDEBluetoothApp::stopObexSrv()
{
    if (obexServer->isRunning())
    {
        obexServer->tryTerminate();
        TQTimer::singleShot(m_waitBeforeKill, obexServer, TQ_SLOT(kill()));
    }
    return true;
}

bool TDEBluetoothApp::isConnected()
{
    return manager->isConnectedToDBUS();
}

void TDEBluetoothApp::setAutoStart(bool val)
{
    if (val)
        enableSessionManagement();
    else
        disableSessionManagement();

    m_config->setGroup("General");
    m_config->writeEntry("autoStart", val);
}

void TDEBluetoothApp::setStartObex(bool val)
{
    m_config->setGroup("General");
    m_config->writeEntry("obexSrv", val);
}

void TDEBluetoothApp::setStartAuthAgent(bool val)
{
    m_config->setGroup("General");
    m_config->writeEntry("authAgent", val);
}

bool TDEBluetoothApp::getAutoStart()
{
    m_config->setGroup("General");
    return m_config->readBoolEntry("autoStart");
}

bool TDEBluetoothApp::getStartObex()
{
    m_config->setGroup("General");
    return m_config->readBoolEntry("obexSrv");
}

bool TDEBluetoothApp::getStartAuthAgent()
{
    m_config->setGroup("General");
    return m_config->readBoolEntry("authAgent");
}

void TDEBluetoothApp::slotAdapterAdded(const TQString &adapter)
{
    AdapterImpl *a = new AdapterImpl(BLUEZ_DN, adapter);
    a->setConnection((*(manager->getConnection())));
    adapters.insert(adapter, a);
    emit signalAdapterAdded(adapter);
}

void TDEBluetoothApp::slotAdapterRemoved(const TQString &adapter)
{
    delete adapters[adapter];
    adapters.remove(adapter);
    emit signalAdapterRemoved(adapter);
}

void TDEBluetoothApp::slotDeviceAdded(const TQString &device)
{
    DeviceImpl *d = new DeviceImpl(BLUEZ_DN, device);
    d->setConnection((*(manager->getConnection())));
    devices.insert(device, d);
}

void TDEBluetoothApp::slotDeviceRemoved(const TQString &device)
{
    delete devices[device];
    devices.remove(device);
}

#include "application.moc"
