/****************************************************************************
**
** 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 "qbluetoothserviceinfo.h"
#include "osxbtservicerecord_p.h"
#include "osxbluetooth_p.h"

#include <QtCore/qvariant.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmap.h>
#include <QtCore/qurl.h>

QT_BEGIN_NAMESPACE

namespace OSXBluetooth {

//
// Returns a dictionary containing the Bluetooth RFCOMM service definition
// corresponding to the provided |uuid| and |options|.
namespace {

typedef ObjCStrongReference<NSMutableDictionary> Dictionary;
typedef ObjCStrongReference<IOBluetoothSDPUUID> SDPUUid;
typedef ObjCStrongReference<NSNumber> Number;
typedef QBluetoothServiceInfo QSInfo;
typedef QSInfo::Sequence Sequence;
typedef QSInfo::AttributeId AttributeId;

}

#if 0
QBluetoothUuid profile_uuid(const QBluetoothServiceInfo &serviceInfo)
{
    // Strategy to pick service uuid:
    // 1.) use serviceUuid()
    // 2.) use first custom uuid if available
    // 3.) use first service class uuid
    QBluetoothUuid serviceUuid(serviceInfo.serviceUuid());

    if (serviceUuid.isNull()) {
        const QVariant var(serviceInfo.attribute(QBluetoothServiceInfo::ServiceClassIds));
        if (var.isValid()) {
            const Sequence seq(var.value<Sequence>());

            for (int i = 0; i < seq.count(); ++i) {
                QBluetoothUuid uuid(seq.at(i).value<QBluetoothUuid>());
                if (uuid.isNull())
                    continue;

                const int size = uuid.minimumSize();
                if (size == 2 || size == 4) { // Base UUID derived
                    if (serviceUuid.isNull())
                        serviceUuid = uuid;
                } else {
                    return uuid;
                }
            }
        }
    }

    return serviceUuid;
}
#endif

template<class IntType>
Number variant_to_nsnumber(const QVariant &);

template<>
Number variant_to_nsnumber<unsigned char>(const QVariant &var)
{
    return Number([NSNumber numberWithUnsignedChar:var.value<unsigned char>()], true);
}

template<>
Number variant_to_nsnumber<unsigned short>(const QVariant &var)
{
    return Number([NSNumber numberWithUnsignedShort:var.value<unsigned short>()], true);
}

template<>
Number variant_to_nsnumber<unsigned>(const QVariant &var)
{
    return Number([NSNumber numberWithUnsignedInt:var.value<unsigned>()], true);
}

template<>
Number variant_to_nsnumber<char>(const QVariant &var)
{
    return Number([NSNumber numberWithChar:var.value<char>()], true);
}

template<>
Number variant_to_nsnumber<short>(const QVariant &var)
{
    return Number([NSNumber numberWithShort:var.value<short>()], true);
}

template<>
Number variant_to_nsnumber<int>(const QVariant &var)
{
    return Number([NSNumber numberWithInt:var.value<int>()], true);
}

template<class ValueType>
void add_attribute(const QVariant &var, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    if (!var.canConvert<ValueType>())
        return;

    const Number num(variant_to_nsnumber<ValueType>(var));
    [dict setObject:num forKey:[NSString stringWithFormat:@"%x", int(key)]];
}

template<>
void add_attribute<QString>(const QVariant &var, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    if (!var.canConvert<QString>())
        return;

    const QString string(var.value<QString>());
    if (string.length()) {
        if (NSString *const nsString = string.toNSString())
            [dict setObject:nsString forKey:[NSString stringWithFormat:@"%x", int(key)]];
    }
}

template<>
void add_attribute<QBluetoothUuid>(const QVariant &var, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    if (!var.canConvert<QBluetoothUuid>())
        return;

    SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
    [dict setObject:ioUUID forKey:[NSString stringWithFormat:@"%x", int(key)]];
}

template<>
void add_attribute<QUrl>(const QVariant &var, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    if (!var.canConvert<QUrl>())
        return;

    Q_UNUSED(var)
    Q_UNUSED(key)
    Q_UNUSED(dict)

    // TODO: not clear how should I pass an url in a dictionary, NSURL does not work.
}

template<class ValueType>
void add_attribute(const QVariant &var, NSMutableArray *list);

template<class ValueType>
void add_attribute(const QVariant &var, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (!var.canConvert<ValueType>())
        return;

    const Number num(variant_to_nsnumber<ValueType>(var));
    [list addObject:num];
}

template<>
void add_attribute<unsigned short>(const QVariant &var, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (!var.canConvert<unsigned short>())
        return;

    const Number num(variant_to_nsnumber<unsigned short>(var));

    NSDictionary* dict = @{
        @"DataElementType"  : [NSNumber numberWithInt:1],
        @"DataElementSize"  : [NSNumber numberWithInt:2],
        @"DataElementValue" : num
    };

    [list addObject: dict];
}

template<>
void add_attribute<QString>(const QVariant &var, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (!var.canConvert<QString>())
        return;

    const QString string(var.value<QString>());
    if (string.length()) {
        if (NSString *const nsString = string.toNSString())
            [list addObject:nsString];
    }
}

template<>
void add_attribute<QBluetoothUuid>(const QVariant &var, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (!var.canConvert<QBluetoothUuid>())
        return;

    SDPUUid ioUUID(iobluetooth_uuid(var.value<QBluetoothUuid>()));
    [list addObject:ioUUID];
}

template<>
void add_attribute<QUrl>(const QVariant &var, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (!var.canConvert<QUrl>())
        return;

    Q_UNUSED(var)
    Q_UNUSED(list)
    // TODO: not clear how should I pass an url in a dictionary, NSURL does not work.
}

void add_rfcomm_protocol_descriptor_list(uint16 channelID, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    QT_BT_MAC_AUTORELEASEPOOL;

    // Objective-C has literals (for arrays and dictionaries), but it will not compile
    // on 10.7 or below, so quite a lot of code here.

    NSMutableArray *const descriptorList = [NSMutableArray array];

    IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP];
    NSArray *const l2capList = [NSArray arrayWithObject:l2capUUID];

    [descriptorList addObject:l2capList];
    //
    IOBluetoothSDPUUID *const rfcommUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16RFCOMM];
    NSMutableDictionary *const rfcommDict = [NSMutableDictionary dictionary];
    [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"];
    [rfcommDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementSize"];
    [rfcommDict setObject:[NSNumber numberWithInt:channelID] forKey:@"DataElementValue"];
    //
    NSMutableArray *const rfcommList = [NSMutableArray array];
    [rfcommList addObject:rfcommUUID];
    [rfcommList addObject:rfcommDict];

    [descriptorList addObject:rfcommList];
    [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
        kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}

void add_l2cap_protocol_descriptor_list(uint16 psm, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    QT_BT_MAC_AUTORELEASEPOOL;

    // Objective-C has literals (for arrays and dictionaries), but it will not compile
    // on 10.7 or below, so quite a lot of code here.

    NSMutableArray *const descriptorList = [NSMutableArray array];
    NSMutableArray *const l2capList = [NSMutableArray array];

    IOBluetoothSDPUUID *const l2capUUID = [IOBluetoothSDPUUID uuid16:kBluetoothSDPUUID16L2CAP];
    [l2capList addObject:l2capUUID];

    NSMutableDictionary *const l2capDict = [NSMutableDictionary dictionary];
    [l2capDict setObject:[NSNumber numberWithInt:1] forKey:@"DataElementType"];
    [l2capDict setObject:[NSNumber numberWithInt:2] forKey:@"DataElementSize"];
    [l2capDict setObject:[NSNumber numberWithInt:psm] forKey:@"DataElementValue"];
    [l2capList addObject:l2capDict];

    [descriptorList addObject:l2capList];
    [dict setObject:descriptorList forKey:[NSString stringWithFormat:@"%x",
        kBluetoothSDPAttributeIdentifierProtocolDescriptorList]];
}

bool add_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
{
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (var.canConvert<Sequence>())
        return false;

    if (var.type() == QVariant::String) {
        //ServiceName, ServiceDescription, ServiceProvider.
        add_attribute<QString>(var, list);
    } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
        add_attribute<QBluetoothUuid>(var, list);
    } else {
        // Here we need 'key' to understand the type.
        // We can have different integer types actually, so I have to check
        // the 'key' to be sure the conversion is reasonable.
        switch (key) {
        case QSInfo::ServiceRecordHandle:
        case QSInfo::ServiceRecordState:
        case QSInfo::ServiceInfoTimeToLive:
            add_attribute<unsigned>(var, list);
            break;
        case QSInfo::BluetoothProfileDescriptorList:
            add_attribute<unsigned short>(var, list);
            break;
        case QSInfo::ServiceAvailability:
            add_attribute<unsigned char>(var, list);
            break;
        case QSInfo::IconUrl:
        case QSInfo::DocumentationUrl:
        case QSInfo::ClientExecutableUrl:
            add_attribute<QUrl>(var, list);
            break;
        default:;
        }
    }

    return true;
}

bool add_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dict (nil)");

    const QVariant var(serviceInfo.attribute(key));
    if (var.canConvert<Sequence>())
        return false;

    if (var.type() == QVariant::String) {
        //ServiceName, ServiceDescription, ServiceProvider.
        add_attribute<QString>(var, key, dict);
    } else if (var.userType() == qMetaTypeId<QBluetoothUuid>()) {
        add_attribute<QBluetoothUuid>(serviceInfo.attribute(key), key, dict);
    } else {
        // We can have different integer types actually, so I have to check
        // the 'key' to be sure the conversion is reasonable.
        switch (key) {
        case QSInfo::ServiceRecordHandle:
        case QSInfo::ServiceRecordState:
        case QSInfo::ServiceInfoTimeToLive:
            add_attribute<unsigned>(serviceInfo.attribute(key), key, dict);
            break;
        case QSInfo::ServiceAvailability:
            add_attribute<unsigned char>(serviceInfo.attribute(key), key, dict);
            break;
        case QSInfo::IconUrl:
        case QSInfo::DocumentationUrl:
        case QSInfo::ClientExecutableUrl:
            add_attribute<QUrl>(serviceInfo.attribute(key), key, dict);
            break;
        default:;
        }
    }

    return true;
}

bool add_sequence_attribute(const QVariant &var, AttributeId key, NSMutableArray *list)
{
    // Add a "nested" sequence.
    Q_ASSERT_X(list, Q_FUNC_INFO, "invalid list (nil)");

    if (var.isNull() || !var.canConvert<Sequence>())
        return false;

    NSMutableArray *const nested = [NSMutableArray array];
    [list addObject:nested];

    const Sequence sequence(var.value<Sequence>());
    for (const QVariant &var : sequence) {
        if (var.canConvert<Sequence>()) {
            add_sequence_attribute(var, key, nested);
        } else {
            add_attribute(var, key, nested);
        }
    }

    return true;
}

bool add_sequence_attribute(const QBluetoothServiceInfo &serviceInfo, AttributeId key, Dictionary dict)
{
    Q_ASSERT_X(dict, Q_FUNC_INFO, "invalid dictionary (nil)");

    const QVariant &var(serviceInfo.attribute(key));
    if (var.isNull() || !var.canConvert<Sequence>())
        return false;

    QT_BT_MAC_AUTORELEASEPOOL;

    NSMutableArray *const list = [NSMutableArray array];
    const Sequence sequence(var.value<Sequence>());
    for (const QVariant &element : sequence) {
        if (!add_sequence_attribute(element, key, list))
            add_attribute(element, key, list);
    }
    [dict setObject:list forKey:[NSString stringWithFormat:@"%x", int(key)]];
    return true;
}

Dictionary iobluetooth_service_dictionary(const QBluetoothServiceInfo &serviceInfo)
{
    Dictionary dict;

    if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol)
        return dict;

    const QList<quint16> attributeIds(serviceInfo.attributes());
    if (!attributeIds.size())
        return dict;

    dict.reset([[NSMutableDictionary alloc] init]);

    for (quint16 key : attributeIds) {
        if (key == QSInfo::ProtocolDescriptorList) // We handle it in a special way.
            continue;
        // TODO: check if non-sequence QVariant still must be
        // converted into NSArray for some attribute ID.
        if (!add_sequence_attribute(serviceInfo, AttributeId(key), dict))
            add_attribute(serviceInfo, AttributeId(key), dict);
    }

    if (serviceInfo.socketProtocol() == QBluetoothServiceInfo::L2capProtocol) {
        add_l2cap_protocol_descriptor_list(serviceInfo.protocolServiceMultiplexer(),
                                           dict);
    } else {
        add_rfcomm_protocol_descriptor_list(serviceInfo.serverChannel(), dict);
    }

    return dict;
}

}

QT_END_NAMESPACE
