/*
 *
 *  Object Manager implementation of bluez5
 *
 *  Copyright (C) 2018  Emanoil Kotsev <deloptes@gmail.com>
 *
 *
 *  This file is part of libtdebluez.
 *
 *  libtdebluez 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.
 *
 *  libtdebluez 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 <tqstringlist.h>

#include <tqdbusmessage.h>
#include <tqdbusobjectpath.h>
#include <tqdbusdatamap.h>
#include <tqdbusdata.h>
#include <tqdbusdatalist.h>
#include <tqdbusvariant.h>

#include "objectmanagerImpl.h"
#include "btuuids.h"

namespace TDEBluetooth
{

ObjectManagerImpl::ObjectManagerImpl(const TQString& service, const TQString& path, TQObject* parent, const char* name) :
        ObjectManagerProxy(service, path, parent, name)
{
    agentManager = 0;
    profileManager = 0;
    healthManager  = 0;
    agentRegisteredStatus = false;
    agentIsDefaultAgent = false;
    // init connection to dbus
    initDBUS();
}

ObjectManagerImpl::~ObjectManagerImpl()
{
    // close D-Bus connection
    close();

    if(agentManager)
        delete agentManager;
    if(profileManager)
        delete profileManager;
    if(healthManager)
        delete healthManager;
}

/*!
 * This function try a reconnect to D-Bus.
 * \return boolean with the result of the operation
 * \retval true if successful reconnected to D-Bus
 * \retval false if unsuccessful
 */
bool ObjectManagerImpl::reconnect()
{
    // close D-Bus connection
    close();
    // init D-Bus conntection
    return (initDBUS());
}

/*!
 * This function return information about connection status to the DBUS daemon.
 * \return boolean with the state of the connection to D-Bus
 * \retval true if connected
 * \retval false if disconnected
 */
bool ObjectManagerImpl::isConnectedToDBUS()
{
    return dBusConn.isConnected();
}

/*!
 * This function returns pointer to connection of the DBUS.
 * \return TQT_DBusConnection* of the connection to D-Bus
 * \retval TQT_DBusConnection*
 */
TQT_DBusConnection* ObjectManagerImpl::getConnection()
{
    return &dBusConn;
}

/*!
 * This function close the connection to manager over the D-Bus daemon.
 * \return boolean with the result of the operation
 * \retval true if successful closed the connection
 * \retval false if any problems
 */
bool ObjectManagerImpl::close()
{
    disconnect(this, SIGNAL(InterfacesAdded(const TQT_DBusObjectPath&, const TQT_DBusDataMap< TQString >&)),
            this, SLOT(slotInterfacesAdded(const TQT_DBusObjectPath&, const TQT_DBusDataMap< TQString >& )));
    disconnect(this, SIGNAL(InterfacesRemoved(const TQT_DBusObjectPath& , const TQStringList& )),
            this, SLOT(slotInterfacesRemoved(const TQT_DBusObjectPath& , const TQStringList& )));

    for (PropertiesMap::iterator it = adapters.begin(); it != adapters.end();
            ++it)
    {
        org::freedesktop::DBus::PropertiesProxy *p;
        p = it.data();
        if (p != NULL)
            delete p;
    }
    for (PropertiesMap::iterator it = devices.begin(); it != devices.end();
            ++it)
    {
        org::freedesktop::DBus::PropertiesProxy *p;
        p = it.data();
        if (p != NULL)
            delete p;
    }
    adapters.clear();
    devices.clear();

    dBusConn.closeConnection(DBUS_CONN_NAME);
    return true;
}

/*!
 * This function initializes the connection to the D-Bus daemon.
 * \return pointer to AgentManager1Proxy
 */
AgentManager1Proxy * ObjectManagerImpl::getAgentManager()
{
    return agentManager;
}

/*!
 * This function initializes the connection to the D-Bus daemon.
 * \return pointer to ProfileManager1Proxy
 */
ProfileManager1Proxy * ObjectManagerImpl::getProfileManager()
{
    return profileManager;
}

/*!
 * This function initializes the connection to the D-Bus daemon.
 * \return pointer to HealthManager1Proxy
 */
HealthManager1Proxy * ObjectManagerImpl::getHealthManager()
{
    return healthManager;
}

/*!
 * This function returns a list of objectpaths
 * \return TQValueList<TQString>
 * \retval TQValueList<TQString>
 */
ObjectManagerImpl::AdapterList ObjectManagerImpl::getAdapters()
{
    return adapters.keys();
}

/*!
 * This function returns a list of objectpaths
 * \return TQValueList<TQString>
 * \retval TQValueList<TQString>
 */
ObjectManagerImpl::DeviceList ObjectManagerImpl::getDevices()
{
    return devices.keys();
}

ObjectManagerImpl::ConnectionList ObjectManagerImpl::listConnections(const TQString &adapter)
{
    ConnectionList list;
    return list;
}

bool ObjectManagerImpl::registerAgent()
{
    if (!agentRegisteredStatus)
    {
        TQT_DBusError error;
        agentManager->RegisterAgent(
                TQT_DBusObjectPath(TQCString(DBUS_AUTH_SERVICE_PATH)), DEVICE_PIN_CAPABILITY, error);
        if (error.isValid())
        {
            tqDebug("Could not register agent: %s", error.message().local8Bit().data());
            return false;
        }
        agentRegisteredStatus = true;
    }
    return true;
}

bool ObjectManagerImpl::unregisterAgent()
{
    kdDebug() << k_funcinfo << endl;
    if (agentRegisteredStatus)
    {
        TQT_DBusError error;
        getAgentManager()->UnregisterAgent(
                TQT_DBusObjectPath(TQCString(DBUS_AUTH_SERVICE_PATH)), error);
        if (error.isValid())
        {
            tqDebug("Could not unregister agent");
            return false;
        }
        agentRegisteredStatus = false;
        agentIsDefaultAgent = false;
    }
    return true;
}

bool ObjectManagerImpl::requestDefaultAgent()
{
    TQT_DBusError error;
    agentManager->RequestDefaultAgent(
            TQT_DBusObjectPath(TQCString(DBUS_AUTH_SERVICE_PATH)), error);
    if (error.isValid())
    {
        tqDebug("Could not request default agent: %s", error.message().local8Bit().data());
        return false;
    }
    agentIsDefaultAgent = true;
    return true;
}

bool ObjectManagerImpl::isAgentRegistered()
{
    return agentRegisteredStatus;
}

bool ObjectManagerImpl::isAgentDefaultAgent()
{
    return agentIsDefaultAgent;
}

/*!
 * This function initializes the connection to the D-Bus daemon.
 * \return boolean with the result of the operation
 * \retval true if successful initialized D-Bus connection
 * \retval false if unsuccessful
 */
bool ObjectManagerImpl::initDBUS()
{
    dBusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus, DBUS_CONN_NAME);
    if (!dBusConn.isConnected())
    {
        tqDebug("Failed to open connection to system message bus: %s", dBusConn.lastError().message().local8Bit().data());
        TQTimer::singleShot(4000, this, TQT_SLOT(reconnect()));
        return false;
    }
    setConnection(dBusConn);

    TQT_DBusDataMap<TQT_DBusObjectPath> objects;
    TQT_DBusError error;
    if (!GetManagedObjects(objects, error))
    {
        tqDebug("GetManagedObjects(objects,error) FAILED:\n%s\n", error.message().latin1());
        return false;
    }

    TQT_DBusDataMap<TQT_DBusObjectPath>::const_iterator it = objects.begin();
    for (it; it != objects.end(); ++it)
    {
        bool ok = false;
        slotInterfacesAdded(it.key(), it.data().toStringKeyMap(&ok));
        if (!ok)
            tqWarning("Failed to convert dbus data to string map: %s", it.key().latin1());
    }

    connect(this, SIGNAL(InterfacesAdded(const TQT_DBusObjectPath&, const TQT_DBusDataMap< TQString >&)),
            this, SLOT(slotInterfacesAdded(const TQT_DBusObjectPath&, const TQT_DBusDataMap< TQString >& )));
    connect(this, SIGNAL(InterfacesRemoved(const TQT_DBusObjectPath& , const TQStringList& )),
            this, SLOT(slotInterfacesRemoved(const TQT_DBusObjectPath& , const TQStringList& )));

    return true;
}

void ObjectManagerImpl::adapterPropertiesChanged(TQString path, const TQMap<
        TQString, TQT_DBusVariant>& changed_properties)
{
    TQMap<TQString, TQT_DBusVariant>::const_iterator it;
    for (it = changed_properties.begin(); it != changed_properties.end(); ++it)
    {
        bool ok = false;
        if (it.key() == "Powered")
            emit adapterPowerOnChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "Class")
            emit adapterClassChanged(path, it.data().value.toUInt32(&ok));
        else if (it.key() == "Name")
            emit adapterNameChanged(path, it.data().value.toString(&ok));
        else if (it.key() == "Alias")
            emit adapterAliasChanged(path, it.data().value.toString(&ok));
        else if (it.key() == "DiscoverableTimeout")
            emit adapterDiscoverableTimeoutChanged(path, it.data().value.toUInt32(&ok));
        else if (it.key() == "Discoverable")
            emit adapterDiscoverableChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "Discovering")
            emit adapterDiscoveringChanged(path, it.data().value.toBool(&ok));
        else
            continue;
        if (!ok)
            tqDebug("ObjectManagerImpl::adapterPropertiesChanged conversion failed");
    }
}

void ObjectManagerImpl::devicePropertiesChanged(TQString path, const TQMap<TQString, TQT_DBusVariant>& changed_properties)
{
    //  https://github.com/r10r/bluez/blob/master/doc/device-api.txt
    TQMap<TQString, TQT_DBusVariant>::const_iterator it;
    for (it = changed_properties.begin(); it != changed_properties.end(); ++it)
    {
        bool ok = false;
        if (it.key() == "Address")
            emit deviceAddressChanged(path, it.data().value.toString(&ok));
        else if (it.key() == "Class")
            emit deviceClassChanged(path, it.data().value.toUInt32(&ok));
        else if (it.key() == "Name")
            emit deviceNameChanged(path, it.data().value.toString(&ok));
        else if (it.key() == "Alias")
            emit deviceAliasChanged(path, it.data().value.toString(&ok));
//      https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml
        else if (it.key() == "Appearance")
            emit deviceAppearanceChanged(path, it.data().value.toUInt16(&ok));
        else if (it.key() == "Icon")
            emit deviceIconChanged(path, it.data().value.toString(&ok));
        else if (it.key() == "Paired")
            emit devicePairedChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "Trusted")
            emit deviceTrustedChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "Blocked")
            emit deviceBlockedChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "LegacyPairing")
            emit deviceLegacyPairingChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "RSSI")
            emit deviceRSSIChanged(path, it.data().value.toInt16(&ok)); //INT16
        else if (it.key() == "Connected")
            emit deviceConnectedChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "UUIDs")
        {
            TQT_DBusDataList vl = TQT_DBusDataList(it.data().value.toTQValueList(&ok));
            emit deviceUUIDsChanged(path, vl.toStringList(&ok));
        }
        else if (it.key() == "Adapter")
            emit deviceAdapterChanged(path, it.data().value.toObjectPath(&ok));
        else if (it.key() == "ManufacturerData")
            emit deviceManufacturerDataChanged(path, it.data().value.toUInt16KeyMap(&ok)); //a{qv}
        else if (it.key() == "ServiceData")
            emit deviceServiceDataChanged(path, it.data().value.toStringKeyMap(&ok)); //a{sv}
        else if (it.key() == "TxPower")
            emit deviceTxPowerChanged(path, it.data().value.toInt16(&ok)); //INT16
        else if (it.key() == "ServicesResolved")
            emit deviceServicesResolvedChanged(path, it.data().value.toBool(&ok));
        else
            continue;
        if (!ok)
            tqDebug("ObjectManagerImpl::devicePropertiesChanged conversion failed");
    }

}

void ObjectManagerImpl::mediaControlPropertiesChanged(TQString path, const TQMap<TQString, TQT_DBusVariant>& changed_properties)
{
    TQMap<TQString, TQT_DBusVariant>::const_iterator it;
    for (it = changed_properties.begin(); it != changed_properties.end(); ++it)
    {
        bool ok = false;
        if (it.key() == "Connected")
            emit mediaControlConnectedChanged(path, it.data().value.toBool(&ok));
        else if (it.key() == "Player")
            emit mediaControlPlayerChanged(path, it.data().value.toObjectPath(&ok));
        else
            continue;
        if (!ok)
            tqDebug("ObjectManagerImpl::mediaControlPropertiesChanged conversion failed");
    }
}

void ObjectManagerImpl::slotInterfacesAdded(const TQT_DBusObjectPath& object, const TQT_DBusDataMap<TQString>& interfaces)
{
    TQT_DBusDataMap<TQString>::const_iterator it1 = interfaces.begin();
    for (it1; it1 != interfaces.end(); it1++)
    {
        TQString interface = it1.key();
        if (interface == "org.bluez.AgentManager1")
        {
            agentManager = new AgentManager1Proxy("org.bluez", object/*, this, "AgentManager1"*/);
            if (agentManager)
                agentManager->setConnection(dBusConn);
        }
        else if (interface == "org.bluez.ProfileManager1")
        {
            profileManager = new ProfileManager1Proxy("org.bluez", object/*, this, "ProfileManager1"*/);
            if (profileManager)
                profileManager->setConnection(dBusConn);
        }
        else if (interface == "org.bluez.HealthManager1")
        {
            healthManager = new HealthManager1Proxy("org.bluez", object/*, this, "HealthManager1"*/);
            if (healthManager)
                healthManager->setConnection(dBusConn);
        }
        else if (interface == "org.bluez.Adapter1")
        {
            org::freedesktop::DBus::PropertiesProxy *properties;
            properties = new org::freedesktop::DBus::PropertiesProxy("org.bluez", object);
            properties->setConnection(dBusConn);
            connect(properties, SIGNAL(PropertiesChanged ( const TQString&, const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )), this, SLOT(slotPropertiesChanged ( const TQString& , const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )));
            adapters.insert(TQString(object), properties);
            //notify others
            emit adapterAdded(TQString(object));
        }
        else if (interface == "org.bluez.GattManager1")
        {
            kdDebug() << "Interface not implemented: org.bluez.GattManager1" << endl;
            // TODO: Implement GattManager1
        }
        else if (interface == "org.bluez.Media1")
        {
            kdDebug() << "Interface not implemented: org.bluez.Media1" << endl;
            // TODO: Implement Media1
        }
        else if (interface == "org.bluez.NetworkServer1")
        {
            kdDebug() << "Interface not implemented: org.bluez.NetworkServer1" << endl;
            // TODO: Implement NetworkServer1
        }
        else if (interface == "org.bluez.Device1")
        {
            org::freedesktop::DBus::PropertiesProxy *properties;
            properties = new org::freedesktop::DBus::PropertiesProxy("org.bluez", object);
            properties->setConnection(dBusConn);
            connect(properties, SIGNAL(PropertiesChanged ( const TQString&, const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )), this, SLOT(slotPropertiesChanged ( const TQString& , const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )));
            devices.insert(TQString(object), properties);
            //notify others
            emit deviceAdded(TQString(object));
        }
        else if (interface == "org.bluez.MediaControl1")
        {
            kdDebug() << "Interface not implemented: org.bluez.MediaControl1" << endl;
            kdDebug() << "as the media control is triggered via properties changed." << endl;
        }
        else if (interface == "org.bluez.MediaTransport1")
        {
            kdDebug() << "Interface not implemented: org.bluez.MediaTransport1" << endl;
            // TODO: Implement MediaTransport1
        }
        else if (interface == "org.freedesktop.DBus.Introspectable")
        {
            // do nothing
        }
        else if (interface == "org.freedesktop.DBus.Properties")
        {
            // do nothing
        }
        else
        {
            tqWarning("Interface not implemented: %s", interface.local8Bit().data());
        }
    }
}

void ObjectManagerImpl::slotInterfacesRemoved(const TQT_DBusObjectPath& object, const TQStringList& interfaces)
{
    // TODO: remove interface
    for (TQValueListConstIterator<TQString> it = interfaces.begin();
            it != interfaces.end(); ++it)
    {
        if ((*it) == "org.bluez.AgentManager1")
        {
            kdDebug() << "Remove org.bluez.AgentManager1" << endl;
            // TODO: remove AgentManager1
        }
        else if ((*it) == "org.bluez.ProfileManager1")
        {
            kdDebug() << "Interface not implemented: org.bluez.ProfileManager1" << endl;
            // TODO: remove ProfileManager1
        }
        else if ((*it) == "org.bluez.HealthManager1")
        {
            kdDebug() << "Interface not implemented: org.bluez.HealthManager1" << endl;
            // TODO: remove HealthManager1
        }
        else if ((*it) == "org.bluez.Adapter1")
        {
            kdDebug() << "Remove org.bluez.Adapter1" << endl;
            disconnect(adapters[object], SIGNAL(PropertiesChanged ( const TQString&, const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )), this, SLOT(slotPropertiesChanged ( const TQString& , const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )));
            adapters.remove(object);
            emit adapterRemoved(TQString(object));
        }
        else if ((*it) == "org.bluez.GattManager1")
        {
            kdDebug() << "Interface not implemented: org.bluez.GattManager1" << endl;
            // TODO: Implement GattManager1
        }
        else if ((*it) == "org.bluez.Media1")
        {
            kdDebug() << "Interface not implemented: org.bluez.Media1" << endl;
            // TODO: Implement Media1
        }
        else if ((*it) == "org.bluez.NetworkServer1")
        {
            kdDebug() << "Interface not implemented: org.bluez.NetworkServer1" << endl;
            // TODO: Implement NetworkServer1
        }
        else if ((*it) == "org.bluez.Device1")
        {
            kdDebug() << "Remove org.bluez.Device1" << endl;
            disconnect(devices[object], SIGNAL(PropertiesChanged ( const TQString&, const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )), this, SLOT(slotPropertiesChanged ( const TQString& , const TQMap< TQString, TQT_DBusVariant >&, const TQStringList& )));
            devices.remove(object);
            emit deviceRemoved(TQString(object));
        }
        else if ((*it) == "org.bluez.MediaControl1")
        {
            kdDebug() << "Interface not implemented: org.bluez.MediaControl1" << endl;
            kdDebug() << "as the media control is triggered via properties changed." << endl;
            //    		emit mediaControlRemoved(TQString ( object.data() ));
        }
        else if ((*it) == "org.freedesktop.DBus.Introspectable")
        {
            // do nothing
        }
        else if ((*it) == "org.freedesktop.DBus.Properties")
        {
            // do nothing
        }
        else
        {
            tqWarning("Interface not implemented: %s", (*it).local8Bit().data());
        }
    }
}

void ObjectManagerImpl::slotPropertiesChanged(const TQString& interface, const TQMap<TQString, TQT_DBusVariant>& changed_properties, const TQStringList& invalidated_properties)
{
    // who send the signal ?
    const TQObject * o = TQObject::sender();
    org::freedesktop::DBus::PropertiesProxy *obj;
    obj = const_cast<org::freedesktop::DBus::PropertiesProxy*>(reinterpret_cast<const org::freedesktop::DBus::PropertiesProxy*>(o));
    TQString path;

    if (interface == "org.bluez.Adapter1")
    {
        for (PropertiesMap::Iterator it = adapters.begin();
                it != adapters.end(); ++it)
        {
            if (obj == it.data())
                path = it.key();
        }
        if (!path.isEmpty())
            adapterPropertiesChanged(path, changed_properties);
    }
    else if (interface == "org.bluez.Device1")
    {
        for (PropertiesMap::Iterator it = devices.begin(); it != devices.end();
                ++it)
        {
            if (obj == it.data())
                path = it.key();
        }
        if (!path.isEmpty())
            devicePropertiesChanged(path, changed_properties);
    }
    else if (interface == "org.bluez.MediaControl1")
    {
        for (PropertiesMap::Iterator it = devices.begin(); it != devices.end();
                ++it)
        {
            if (obj == it.data())
                path = it.key();
        }
        if (!path.isEmpty())
            mediaControlPropertiesChanged(path, changed_properties);
    }

//			TQStringList::const_iterator it1;
//			for ( it1 = invalidated_properties.begin(); it1 != invalidated_properties.end(); ++it1 )
//			{
//				kdDebug() << "Invalidated Key: " << (*it1) << endl;
////				if ( it.key() == "Powered" )
////					emit powerOnChanged(TQT_DBusData::fromVariant ( it.data() ).toBool());
////				if ( it.key() == "DiscoverableTimeout" )
////					emit discoverableTimeoutChanged(TQT_DBusData::fromVariant ( it.data() ).toUInt32());
////				if ( it.key() == "Discoverable" )
////					emit discoverableTimeoutChanged(TQT_DBusData::fromVariant ( it.data() ).toBool());
//			}

}

}; // namespace TDEBluetooth

#include "objectmanagerImpl.moc"
// End of File