/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "darwin/btsocketlistener_p.h"
#include "qbluetoothserver_p.h"

// The order is important: a workround for
// a private header included by private header
// (incorrectly handled dependencies).
#include "qbluetoothsocketbase_p.h"
#include "qbluetoothsocket_macos_p.h"

#include "qbluetoothlocaldevice.h"
#include "darwin/btutility_p.h"
#include "qbluetoothserver.h"
#include "qbluetoothsocket.h"

#include <QtCore/qloggingcategory.h>
#include <QtCore/qvariant.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmutex.h>

#include <Foundation/Foundation.h>

#include <IOBluetooth/IOBluetooth.h>

#include <limits>

QT_BEGIN_NAMESPACE

namespace {

using DarwinBluetooth::RetainPolicy;
using ServiceInfo = QBluetoothServiceInfo;
using ObjCListener = QT_MANGLE_NAMESPACE(DarwinBTSocketListener);

QMap<quint16, QBluetoothServerPrivate *> &busyPSMs()
{
    static QMap<quint16, QBluetoothServerPrivate *> psms;
    return psms;
}

QMap<quint16, QBluetoothServerPrivate *> &busyChannels()
{
    static QMap<quint16, QBluetoothServerPrivate *> channels;
    return channels;
}

typedef QMap<quint16, QBluetoothServerPrivate *>::iterator ServerMapIterator;

} // unnamed namespace

QBluetoothServerPrivate::QBluetoothServerPrivate(ServiceInfo::Protocol type,
                                                 QBluetoothServer *parent)
    : serverType(type),
      q_ptr(parent),
      port(0)
{
    if (serverType == ServiceInfo::UnknownProtocol)
        qCWarning(QT_BT_DARWIN) << "unknown protocol";
}

QBluetoothServerPrivate::~QBluetoothServerPrivate()
{
    const QMutexLocker lock(&channelMapMutex());
    unregisterServer(this);
}

bool QBluetoothServerPrivate::startListener(quint16 realPort)
{
    Q_ASSERT_X(realPort, Q_FUNC_INFO, "invalid port");

    if (serverType == ServiceInfo::UnknownProtocol) {
        qCWarning(QT_BT_DARWIN) << "invalid protocol";
        return false;
    }

    if (!listener) {
        listener.reset([[ObjCListener alloc] initWithListener:this],
                       RetainPolicy::noInitialRetain);
    }

    bool result = false;
    if (serverType == ServiceInfo::RfcommProtocol)
        result = [listener.getAs<ObjCListener>() listenRFCOMMConnectionsWithChannelID:realPort];
    else
        result = [listener.getAs<ObjCListener>() listenL2CAPConnectionsWithPSM:realPort];

    if (!result)
        listener.reset();

    return result;
}

bool QBluetoothServerPrivate::isListening() const
{
    if (serverType == ServiceInfo::UnknownProtocol)
        return false;

    const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex());
    return QBluetoothServerPrivate::registeredServer(q_ptr->serverPort(), serverType);
}

void QBluetoothServerPrivate::stopListener()
{
    listener.reset();
}

void QBluetoothServerPrivate::openNotifyRFCOMM(void *generic)
{
    auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic);

    Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)");
    Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)");
    Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");

    PendingConnection newConnection(channel, RetainPolicy::doInitialRetain);
    pendingConnections.append(newConnection);

    emit q_ptr->newConnection();
}

void QBluetoothServerPrivate::openNotifyL2CAP(void *generic)
{
    auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic);

    Q_ASSERT_X(listener, Q_FUNC_INFO, "invalid listener (nil)");
    Q_ASSERT_X(channel, Q_FUNC_INFO, "invalid channel (nil)");
    Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");

    PendingConnection newConnection(channel, RetainPolicy::doInitialRetain);
    pendingConnections.append(newConnection);

    emit q_ptr->newConnection();
}

QMutex &QBluetoothServerPrivate::channelMapMutex()
{
    static QMutex mutex;
    return mutex;
}

bool QBluetoothServerPrivate::channelIsBusy(quint16 channelID)
{
    // External lock is required.
    return busyChannels().contains(channelID);
}

quint16 QBluetoothServerPrivate::findFreeChannel()
{
    // External lock is required.
    for (quint16 i = 1; i <= 30; ++i) {
        if (!busyChannels().contains(i))
            return i;
    }

    return 0; //Invalid port.
}

bool QBluetoothServerPrivate::psmIsBusy(quint16 psm)
{
    // External lock is required.
    return busyPSMs().contains(psm);
}

quint16 QBluetoothServerPrivate::findFreePSM()
{
    // External lock is required.
    for (quint16 i = 1, e = std::numeric_limits<qint16>::max(); i < e; i += 2) {
        if (!psmIsBusy(i))
            return i;
    }

    return 0; // Invalid PSM.
}

void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, quint16 port)
{
    // External lock is required + port must be free.
    Q_ASSERT_X(server, Q_FUNC_INFO, "invalid server (null)");

    const ServiceInfo::Protocol type = server->serverType;
    if (type == ServiceInfo::RfcommProtocol) {
        Q_ASSERT_X(!channelIsBusy(port), Q_FUNC_INFO, "port is busy");
        busyChannels()[port] = server;
    } else if (type == ServiceInfo::L2capProtocol) {
        Q_ASSERT_X(!psmIsBusy(port), Q_FUNC_INFO, "port is busy");
        busyPSMs()[port] = server;
    } else {
        qCWarning(QT_BT_DARWIN) << "can not register a server "
                                   "with unknown protocol type";
    }
}

QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol)
{
    // Eternal lock is required.
    if (protocol == ServiceInfo::RfcommProtocol) {
        ServerMapIterator it = busyChannels().find(port);
        if (it != busyChannels().end())
            return it.value();
    } else if (protocol == ServiceInfo::L2capProtocol) {
        ServerMapIterator it = busyPSMs().find(port);
        if (it != busyPSMs().end())
            return it.value();
    } else {
        qCWarning(QT_BT_DARWIN) << "invalid protocol";
    }

    return nullptr;
}

void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server)
{
    // External lock is required.
    const ServiceInfo::Protocol type = server->serverType;
    const quint16 port = server->port;

    if (type == ServiceInfo::RfcommProtocol) {
        ServerMapIterator it = busyChannels().find(port);
        if (it != busyChannels().end()) {
            busyChannels().erase(it);
        } else {
            qCWarning(QT_BT_DARWIN) << "server is not registered";
        }
    } else if (type == ServiceInfo::L2capProtocol) {
        ServerMapIterator it = busyPSMs().find(port);
        if (it != busyPSMs().end()) {
            busyPSMs().erase(it);
        } else {
            qCWarning(QT_BT_DARWIN) << "server is not registered";
        }
    } else {
        qCWarning(QT_BT_DARWIN) << "invalid protocol";
    }
}

void QBluetoothServer::close()
{
    d_ptr->listener.reset();

    // Needs a lock :(
    const QMutexLocker lock(&d_ptr->channelMapMutex());
    d_ptr->unregisterServer(d_ptr);
    d_ptr->port = 0;
}

bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port)
{
    DarwinBluetooth::qt_test_iobluetooth_runloop();

    if (d_ptr->listener) {
        qCWarning(QT_BT_DARWIN) << "already in listen mode, close server first";
        return false;
    }

    const QBluetoothLocalDevice device(address);
    if (!device.isValid()) {
        qCWarning(QT_BT_DARWIN) << "device does not support Bluetooth or"
                                << address.toString()
                                << "is not a valid local adapter";
        d_ptr->m_lastError = UnknownError;
        emit errorOccurred(UnknownError);
        return false;
    }

    const QBluetoothLocalDevice::HostMode hostMode = device.hostMode();
    if (hostMode == QBluetoothLocalDevice::HostPoweredOff) {
        qCWarning(QT_BT_DARWIN) << "Bluetooth device is powered off";
        d_ptr->m_lastError = PoweredOffError;
        emit errorOccurred(PoweredOffError);
        return false;
    }

    const ServiceInfo::Protocol type = d_ptr->serverType;

    if (type == ServiceInfo::UnknownProtocol) {
        qCWarning(QT_BT_DARWIN) << "invalid protocol";
        d_ptr->m_lastError = UnsupportedProtocolError;
        emit errorOccurred(d_ptr->m_lastError);
        return false;
    }

    d_ptr->m_lastError = QBluetoothServer::NoError;

    // Now we have to register a (fake) port, doing a proper (?) lock.
    const QMutexLocker lock(&d_ptr->channelMapMutex());

    if (port) {
        if (type == ServiceInfo::RfcommProtocol) {
            if (d_ptr->channelIsBusy(port)) {
                qCWarning(QT_BT_DARWIN) << "server port:" << port
                                        << "already registered";
                d_ptr->m_lastError = ServiceAlreadyRegisteredError;
            }
        } else {
            if (d_ptr->psmIsBusy(port)) {
                qCWarning(QT_BT_DARWIN) << "server port:" << port
                                        << "already registered";
                d_ptr->m_lastError = ServiceAlreadyRegisteredError;
            }
        }
    } else {
        type == ServiceInfo::RfcommProtocol ? port = d_ptr->findFreeChannel()
                                       : port = d_ptr->findFreePSM();
    }

    if (d_ptr->m_lastError != QBluetoothServer::NoError) {
        emit errorOccurred(d_ptr->m_lastError);
        return false;
    }

    if (!port) {
        qCWarning(QT_BT_DARWIN) << "all ports are busy";
        d_ptr->m_lastError = ServiceAlreadyRegisteredError;
        emit errorOccurred(d_ptr->m_lastError);
        return false;
    }

    // It's a fake port, the real one will be different
    // (provided after a service was registered).
    d_ptr->port = port;
    d_ptr->registerServer(d_ptr, port);
    d_ptr->listener.reset([[ObjCListener alloc] initWithListener:d_ptr],
                          RetainPolicy::noInitialRetain);

    return true;
}

void QBluetoothServer::setMaxPendingConnections(int numConnections)
{
    d_ptr->maxPendingConnections = numConnections;
}

bool QBluetoothServer::hasPendingConnections() const
{
    return d_ptr->pendingConnections.size();
}

QBluetoothSocket *QBluetoothServer::nextPendingConnection()
{
    if (!d_ptr->pendingConnections.size())
        return nullptr;

    std::unique_ptr<QBluetoothSocket> newSocket = std::make_unique<QBluetoothSocket>();
    QBluetoothServerPrivate::PendingConnection channel(d_ptr->pendingConnections.front());

    // Remove it even if we have some errors below.
    d_ptr->pendingConnections.pop_front();

    if (d_ptr->serverType == ServiceInfo::RfcommProtocol) {
        if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setRFCOMChannel(channel.getAs<IOBluetoothRFCOMMChannel>()))
            return nullptr;
    } else {
        if (!static_cast<QBluetoothSocketPrivate *>(newSocket->d_ptr)->setL2CAPChannel(channel.getAs<IOBluetoothL2CAPChannel>()))
            return nullptr;
    }

    return newSocket.release();
}

QBluetoothAddress QBluetoothServer::serverAddress() const
{
    return QBluetoothLocalDevice().address();
}

quint16 QBluetoothServer::serverPort() const
{
    return d_ptr->port;
}

void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security)
{
    Q_UNUSED(security);
    Q_UNIMPLEMENTED();
}

QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const
{
    Q_UNIMPLEMENTED();
    return QBluetooth::Security::NoSecurity;
}

QT_END_NAMESPACE
