/***************************************************************************
 *   Copyright (C) 2004 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 "neighbourmonitor.h"
#include <bluetooth/hci.h>
#include <libkbluetooth/hcisocket.h>
#include <libkbluetooth/inquiry.h>
#include <libkbluetooth/deviceaddress.h>
#include <libkbluetooth/adapter.h>
#include <libkbluetooth/hcidefault.h>
#include <kdebug.h>
#include <algorithm>
#include "hcilistener.h"

using namespace KBluetooth;
using namespace std;
 
NeighbourMonitor::NeighbourMonitor(QObject *parent, HciListener* hciListener) : 
    QObject(parent), 
    hciListener(hciListener),
    initialCredits(3)
{
    executing = false;
    hciSocket = new HciSocket(this);
    hciSocket->open();
    processTimeoutTimer = new QTimer(this);
    connect(processTimeoutTimer, SIGNAL(timeout()), 
        this, SLOT(processTimeout()));
    
    connect(hciListener, SIGNAL(connectionStateChanged()),
        this, SLOT(slotConnectionStateChanged()));
    connect(hciListener, SIGNAL(inquiryComplete(int)),
        this, SLOT(slotInquiryComplete(int)));
    connect(hciListener, SIGNAL(inquiryResult(KBluetooth::DeviceAddress,int)),
        this, SLOT(slotInquiryResult(KBluetooth::DeviceAddress,int)));
    connect(hciListener, SIGNAL(inquiryStarted()),
        this, SLOT(slotInquiryStarted()));
    connect(hciListener, SIGNAL(nameRequestComplete(int,KBluetooth::DeviceAddress,QString)),
        this, SLOT(slotNameRequestComplete(int,KBluetooth::DeviceAddress,QString)));
}

NeighbourMonitor::~NeighbourMonitor()
{
    
}

void NeighbourMonitor::processTimeout()
{
    kdDebug() << "Timeout in NeighbourMonitor" << endl;
    executing = false;
    processQueue();
}

void NeighbourMonitor::processQueue()
{
    kdDebug() << "NeighbourMonitor::processQueue" << endl;
    if (executing) {
        kdDebug() << "..still waiting for last inquiry/page to end." << endl;
        return;
    }
    
    processTimeoutTimer->stop();
    
    if (inquiryDue) {
        kdDebug() << "NeighbourMonitor: starting inquiry" << endl;
        KBluetooth::Inquiry inquiry;
        executing = true;
        inquiry.inquiry();
        inquiryDue = false;
        processTimeoutTimer->start(1000*30, true);
    }
    else if (pageQueue.size() > 0) {
        DeviceAddress addr = pageQueue.front();
        kdDebug() << "NeighbourMonitor: paging " << QString(addr) << endl;
        
        executing = true;
        // Send a name request
        remote_name_req_cp reqData; 
        QByteArray nameRequestData;
        reqData.bdaddr = addr.getBdaddr(false);
        reqData.pscan_rep_mode = 0x01;
        reqData.pscan_mode = 0;
        reqData.clock_offset = 0;
        nameRequestData.duplicate((char*)(&reqData), sizeof(reqData));
        hciSocket->sendCommand(OGF_LINK_CTL, OCF_REMOTE_NAME_REQ, nameRequestData);
        pageQueue.pop_front();        
        processTimeoutTimer->start(1000*60, true);
    }
    else return;
}

void NeighbourMonitor::addPage(const KBluetooth::DeviceAddress& addr)
{
    if (find(pageQueue.begin(), pageQueue.end(), addr) == pageQueue.end()) {
        pageQueue.push_back(addr);
    }
    processQueue();
}

void NeighbourMonitor::addInquiry()
{
    inquiryDue = true;
    processQueue();
}

void NeighbourMonitor::slotConnectionStateChanged()
{
    // TODO: Also use connection information to find other devices
}

void NeighbourMonitor::slotNameRequestComplete(
    int errorCode, DeviceAddress addr, QString /*name*/)
{
    bool addressKnown = (neighbourMap.find(addr) != neighbourMap.end());
    if (errorCode == 0) {
        kdDebug() << "NeighbourMonitor: end of name request detected (success)" << endl;
        kdDebug() << "NeighbourMonitor: restored credits for " << QString(addr) << endl;
        neighbourMap[addr].credits = initialCredits;
        if (!addressKnown) {
            neighbourMap[addr].nonDiscoverable = true;
            emit neighboursChanged();
        }
    }
    else {
        kdDebug() << "NeighbourMonitor: end of name request detected (failed)" << endl;    
        
        if (addressKnown) {
            --neighbourMap[addr].credits;
            kdDebug() << "NeighbourMonitor: decreased credits:" 
                << addr << " " << neighbourMap[addr].credits << endl;
            
            if (neighbourMap[addr].credits <= 0) {
                neighbourMap.erase(neighbourMap.find(addr));
                emit neighboursChanged();
            }
        }
    }
    executing = false;
    processQueue();
}

void NeighbourMonitor::slotInquiryStarted()
{
    kdDebug() << "NeighbourMonitor: start of inquiry detected" << endl;
    inquiryResults.clear();
}

void NeighbourMonitor::slotInquiryComplete(int errorCode)
{
    kdDebug() << "NeighbourMonitor: end of inquiry detected" << endl;
    bool changed = false;
    // Insert all disovered devices into the neighbourMap if not there already
    set<DeviceAddress>::iterator devIt;
    for (devIt = inquiryResults.begin(); devIt != inquiryResults.end(); ++devIt) {
        if (neighbourMap.find(*devIt) == neighbourMap.end()) {
            neighbourMap[*devIt].credits = initialCredits;
            neighbourMap[*devIt].nonDiscoverable = false;
            changed = true;
            kdDebug() << "NeighbourMonitor: new device " << QString(*devIt) << endl;
        }
    }
    
    // We add all devices we are currently connected to 
    // to the list of inquiry resulst, since many devices
    // don't respond reliably to inquiries when connected.
    Adapters adapters;
    Adapter::ConnectionInfoVector connectionList;
    connectionList = adapters[HciDefault::defaultHciDeviceNum()].getAclConnections();
    for (int n=0; n < int(connectionList.size()); n++) {
        if (connectionList[n].state == Adapter::CONNECTED) {
            inquiryResults.insert(connectionList[n].address);
        }
    }
    
    // Decrease the credits (and eventually delete) neighbor entries
    // which have not been found during this inquiry
    if (errorCode == 0) {
        map<DeviceAddress, NeighbourInfo>::iterator it = neighbourMap.begin();
        map<DeviceAddress, NeighbourInfo>::iterator delIt;
        while (it != neighbourMap.end()) {
            delIt = it;
            ++it;
            if (delIt->second.nonDiscoverable == false) {
                if (inquiryResults.find(delIt->first) == inquiryResults.end()) {
                    delIt->second.credits--;
                    kdDebug() << "NeighbourMonitor: decreased credits:" 
                        << QString(delIt->first) << " " << delIt->second.credits << endl;
                }
                else {
                    if (delIt->second.credits != initialCredits) {
                        delIt->second.credits = initialCredits;
                        kdDebug() << "NeighbourMonitor: reset credits:" 
                            << QString(delIt->first) << " " << delIt->second.credits << endl;
                    }
                }
                
                if (delIt->second.credits <= 0) {
                    neighbourMap.erase(delIt);
                    changed = true;
                    kdDebug() << "NeighbourMonitor: lost device " 
                        << QString(delIt->first) << endl;
                }
            }
            else {
                //..
            }
        }
    }
    else {
        kdDebug() << "NeighbourMonitor: inquiry failed. ErrorCode=" << errorCode << endl;
        // TODO: Maybe schedule a new inquiry after a few seconds if this
        // one failed? Especially when it's E_AGAIN..
        // At the moment, the next retry will be after the regular update interval.
    }
    if (changed) {
        kdDebug() << "NeighbourMonitor: neighbors changed" << endl;
        emit neighboursChanged();
    }   
    executing = false;
    processQueue();
}

void NeighbourMonitor::slotInquiryResult(DeviceAddress addr, int /*devClass*/)
{
    inquiryResults.insert(addr);
}

/*set<DeviceAddress> NeighbourMonitor::getDiscoverableNeighbourSet()
{
    set<DeviceAddress> ret;
    map<DeviceAddress, NeighbourInfo>::iterator it;
    for (it = neighbourMap.begin(); it != neighbourMap.end(); ++it) {
        if (it->second.nonDiscoverable == false) {
            ret.insert(it->first);
        }
    }
    return ret;
}

set<DeviceAddress> NeighbourMonitor::getNondiscoverableNeighbourSet()
{
    set<DeviceAddress> ret;
    map<DeviceAddress, NeighbourInfo>::iterator it;
    for (it = neighbourMap.begin(); it != neighbourMap.end(); ++it) {
        if (it->second.nonDiscoverable == true) {
            ret.insert(it->first);
        }
    }
    return ret;
}*/

set<DeviceAddress> NeighbourMonitor::getNeighbourSet()
{
    set<DeviceAddress> ret;
    map<DeviceAddress, NeighbourInfo>::iterator it;
    for (it = neighbourMap.begin(); it != neighbourMap.end(); ++it) {
        ret.insert(it->first);
    }
    return ret;
}

#include "neighbourmonitor.moc"
