function dataBlock = bleGAPDataBlock(cfgGAP)
%bleGAPDataBlock Generate Bluetooth LE GAP data block
%   DATABLOCK = bleGAPDataBlock(CFGGAP) generates a Bluetooth(R) low energy
%   (LE) generic access profile (GAP) data block of the type advertising
%   data (AD) or scan response data (SRD) corresponding to the Bluetooth LE
%   GAP data block configuration object, CFGGAP.
%
%   DATABLOCK is the generated Bluetooth LE GAP data block, returned as a
%   character array. Each row in this array is the hexadecimal
%   representation of an octet.
%
%   CFGGAP is a Bluetooth LE GAP data block configuration object, specified
%   as a <a
%   href="matlab:help('bleGAPDataBlockConfig')">bleGAPDataBlockConfig</a>
%   object.
%
%   % Example 1:
%   % Generate an advertising data (AD) block with the advertising data
%   % types 'Flags' and 'Tx power level'.
%
%   cfgGAP = bleGAPDataBlockConfig;
%   cfgGAP.AdvertisingDataTypes   = {'Flags'; 'Tx power level'};
%   cfgGAP.LEDiscoverability      = 'Limited';
%   cfgGAP.TxPowerLevel           = 45;
%   dataBlock = bleGAPDataBlock(cfgGAP)
%
%   % Example 2:
%   % Generate an advertising data (AD) block with the advertising data
%   % types 'Advertising interval' and 'Local name'.
%
%   cfgGAP = bleGAPDataBlockConfig('AdvertisingDataTypes', ...
%                                     {'Advertising interval', ...
%                                      'Local name'});
%   cfgGAP.AdvertisingInterval  = 48;
%   cfgGAP.LocalNameShortening  = true;
%   cfgGAP.LocalName            = 'MathWorks';
%   dataBlock = bleGAPDataBlock(cfgGAP)
%
%   % Example 3:
%   % Generate an advertising data (AD) block with advertising data type
%   % 'Flags' and simultaneous support for LE and BR/EDR.
%
%   cfgGAP = bleGAPDataBlockConfig;
%   cfgGAP.LEDiscoverability      = 'Limited';
%   cfgGAP.BREDR                  = true;
%   cfgGAP.LE                     = 'Host';
%   dataBlock = bleGAPDataBlock(cfgGAP)
%
%   See also bleGAPDataBlockConfig, bleGAPDataBlockDecode.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Validate input argument CFGGAP
validateattributes(cfgGAP, {'bleGAPDataBlockConfig'}, {'scalar'}, ...
    mfilename, 'cfgGAP', 1);

% Initialize
cnt = 1;
% Pre-allocation for GAP data block
totalOctets = getADSize(cfgGAP);
dataBlock = char(zeros(totalOctets, 2));

for i=1:numel(cfgGAP.AdvertisingDataTypes)
    % Advertising data type (1 octet)
    adType = generateADType(cfgGAP.AdvertisingDataTypes{i}, cfgGAP);
    
    % Initialize
    adLength = 0;
    data = char(zeros(1, 0));
    
    switch cfgGAP.AdvertisingDataTypes{i}
        case 'Flags'
            % Length of the single advertising data block (1 octet)
            adLength = 1;
            % Advertising data (Flags) (1 octet)
            tmp = generateFlags(cfgGAP);
            data = dec2hex(bit2int(tmp', numel(tmp), 0), adLength*2);

        case 'Local name'
            % Length of the single advertising data block (1 octet)
            adLength = numel(cfgGAP.LocalName);
            % Advertising data (Local name) (variable octets)
            % Generate LocalName as specified in Supplement to Bluetooth
            % core specification CSSv7, Part-A, Section: 1.2 and Section 2
            data = dec2hex(uint8(cfgGAP.LocalName)', 2);

        case 'UUIDs'
            % Length of the single advertising data block (1 octet)
            adLength = (numel(cfgGAP.Identifiers)/2);
            if adLength > 0
                % Advertising data (UUIDs) (variable octets)
                data = convertToOctets(cfgGAP.Identifiers);
            end

        case 'Advertising interval'
            % Length of the single advertising data block (1 octet)
            adLength = 2;
            % Advertising data (Advertising interval) (2 octets)
            data = convertToOctets(dec2hex(cfgGAP.AdvertisingInterval, adLength*2));

        case 'Tx power level'
            % Length of the single advertising data block (1 octet)
            adLength = 1;
            % Advertising data (Tx power level) (1 octet)
            data = dec2hex(getUint8Value(int8(cfgGAP.TxPowerLevel)), adLength*2);

        case 'Connection interval range'
            % Length of the single advertising data block (1 octet)
            adLength = 4;
            
            % IntervalMin (2 octets)
            intervalMin = convertToOctets(dec2hex(cfgGAP.ConnectionIntervalRange(1), 4));
            
            % IntervalMax (2 octets)
            intervalMax = convertToOctets(dec2hex(cfgGAP.ConnectionIntervalRange(2), 4));
            
            % Advertising data (Connection interval range) (4 octets)
            data = [intervalMin; intervalMax];
    end
    
    % Append advertising data (data) to GAP data block (dataBlock)
    if ~isempty(data)
        tempBlock = [dec2hex((adLength+1), 2); adType; data];
        dataBlock(cnt : cnt + (numel(tempBlock)/2) - 1, :) = tempBlock;
        cnt = cnt + numel(tempBlock)/2;
    end
end
end

% Get required advertising data size for the given configuration
function totalOctets = getADSize(cfg)
% Initialize
totalOctets = 0;
for i = 1:numel(cfg.AdvertisingDataTypes)
    switch cfg.AdvertisingDataTypes{i}
        case 'Flags'
            totalOctets = totalOctets + 3;

        case 'Local name'
            if ~isempty(cfg.LocalName)
                totalOctets = totalOctets + numel(cfg.LocalName) + 2;
            end

        case 'UUIDs'
            if ~isempty(cfg.Identifiers)
                totalOctets = totalOctets + (numel(cfg.Identifiers)/2) + 2;
            end

        case 'Advertising interval'
            totalOctets = totalOctets + 4;

        case 'Tx power level'
            totalOctets = totalOctets + 3;

        case 'Connection interval range'
            totalOctets = totalOctets + 6;
    end
end
end

% Generate adTypeCode from adTypeValue
% Refer Advertising Data Type (AD Type) from 'Generic Access Profile' in
% Bluetooth SIG
function adTypeCode = generateADType(adTypeValue, cfg)
adTypeCode = '';
switch adTypeValue
    case 'Flags'
        adTypeCode = '01';
    case 'UUIDs'
        if strcmp(cfg.IdentifierType, 'Complete')
            adTypeCode = '03';
        else
            adTypeCode = '02';
        end
    case 'Local name'
        if cfg.LocalNameShortening
            adTypeCode = '08';
        else
            adTypeCode = '09';
        end
    case 'Tx power level'
        adTypeCode = '0A';
    case 'Connection interval range'
        adTypeCode = '12';
    case 'Advertising interval'
        adTypeCode = '1A';
end
end

% Generate Flags
function flags = generateFlags(cfg)
% LE discover-ability
switch cfg.LEDiscoverability
    case 'General'
        limitedDiscoverable = 0;
        generalDiscoverable = 1;
    case 'Limited'
        limitedDiscoverable = 1;
        generalDiscoverable = 0;
    case 'Limited and general'
        limitedDiscoverable = 1;
        generalDiscoverable = 1;
    otherwise
        limitedDiscoverable = 0;
        generalDiscoverable = 0;
end

% BR/EDR support
if cfg.BREDR
    notSupported = 0;
else
    notSupported = 1;
end

% Initialize
controllerCapable = 0;
hostCapable = 0;

% Simultaneous LE and BR/EDR
if cfg.BREDR
    switch cfg.LE
        case 'Host'
            hostCapable = 1;
        case 'Controller'
            controllerCapable = 1;
        case 'Host and controller'
            controllerCapable = 1;
            hostCapable = 1;
    end
end
flags = [limitedDiscoverable generalDiscoverable notSupported ...
    controllerCapable hostCapable 0 0 0];
end

% Convert the value into octets in LSB first format
function octets = convertToOctets(value)
if isvector(value)
    octets = reshape(value, 2, [])';
    % LSB first
    octets = octets(end:-1:1, :);
else
    curPos = 1;
    fieldLen = size(value, 2)/2;
    octets = reshape(value', 2, [])';
    noOfOctets = (numel(octets)/2)/fieldLen;
    % LSB first
    for i = 1:noOfOctets
        temp = octets(curPos:curPos+(fieldLen-1), :);
        temp = temp(end:-1:1, :);
        octets(curPos:curPos+(fieldLen-1), :) = temp;
        curPos = curPos + fieldLen;
    end
end
end

% Get uint8 value from the given int8 value
function uint8Value = getUint8Value(int8Value)
value = zeros(1, 8);

if int8Value >= 0
    value(1:8) = int2bit(double(int8Value), 8, 0)';
else
    value(8) = 1;
    value(1:7) = int2bit(128+double(int8Value), 7, 0)';
end

uint8Value = bit2int(value', numel(value), 0);
end
