function pdu = bleLLAdvertisingChannelPDU(cfgLLAdv)
%bleLLAdvertisingChannelPDU Generate Bluetooth LE LL advertising channel
%PDU
%   ADVLLPDU = bleLLAdvertisingChannelPDU(CFGLLADV) generates a
%   Bluetooth(R) low energy (LE) link layer (LL) advertising channel
%   protocol data unit (PDU), ADVLLPDU, corresponding to the Bluetooth LE
%   LL advertising channel PDU configuration object, CFGLLADV.
%
%   ADVLLPDU is the generated Bluetooth LE LL advertising channel PDU,
%   returned as a binary column vector.
%
%   CFGLLADV is a Bluetooth LE LL advertising channel PDU configuration
%   object, specified as a <a
%   href="matlab:help('bleLLAdvertisingChannelPDUConfig')">bleLLAdvertisingChannelPDUConfig</a>
%   object.
%
%   % Example 1:
%   % Generate an 'Advertising indication' PDU with advertising data
%   % '020106'.
%
%   cfgLLAdv = bleLLAdvertisingChannelPDUConfig;
%   cfgLLAdv.PDUType         = 'Advertising indication';
%   cfgLLAdv.AdvertisingData = '020106';
%   pdu = bleLLAdvertisingChannelPDU(cfgLLAdv);
%
%   % Example 2:
%   % Generate a 'Connection indication' PDU with connection interval
%   % set to 8 (10 ms, each unit is taken as 1.25 ms) and used channels
%   % set as '[0 4 12 16 18 24 25]'.
%
%   cfgLLAdv = bleLLAdvertisingChannelPDUConfig('PDUType', ...
%                                               'Connection indication');
%   cfgLLAdv.ConnectionInterval = 8;
%   cfgLLAdv.UsedChannels       = [0 4 12 16 18 24 25];
%   pdu = bleLLAdvertisingChannelPDU(cfgLLAdv);
%
%   See also bleLLAdvertisingChannelPDUDecode,
%   bleLLAdvertisingChannelPDUConfig.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Validate input argument CFGLLADV
validateattributes(cfgLLAdv, {'bleLLAdvertisingChannelPDUConfig'}, ...
    {'scalar'}, 'bleLLAdvertisingChannelPDU', 'cfgLLAdv', 1);

% Default CRC initialization value for advertising channel PDUs
defaultCRCInit = '555555';

% Payload
[payload, payloadLength] = generatePayload(cfgLLAdv);

% Header
header = generateHeader(cfgLLAdv, payloadLength);

% Put it all together (Advertising channel PDU)
advertisingPDU = [header; payload];

% Append CRC to the generated advertising channel PDU
pdu = ble.internal.appendCRC(advertisingPDU, defaultCRCInit);
end

% Generate header
function header = generateHeader(cfg, pduLength)
% Initialize
octetLen = 8;

% PDU Type (4-bits), Tx address type (1-bit), Rx address type (1-bit)
[pdutype, txAddress, rxAddress] = generateHeaderFields(cfg);

% Reserved for future use (1-bit)
rfu = 0;

% Channel Selection (1-bit)
if strcmp(cfg.ChannelSelection,  'Algorithm1')
    channelSelection = 0;
else
    channelSelection = 1;
end

% Length (8-bits)
lengthValue = int2bit(pduLength, octetLen, 0);

% Put it all together
header = [pdutype; rfu; channelSelection; txAddress; rxAddress; lengthValue];
end

% Generate payload
function [payload, lengthValue] = generatePayload(cfg)
% Initialize
octetLen = 8;
doubleOctetLen = 2*octetLen;
tripleOctetLen = 3*octetLen;
fourOctetLen = 4*octetLen;
sixOctetLen = 6*octetLen;
payload = zeros(1, 0);

switch(cfg.PDUType)
    % 'Advertising indication' (or) 'Advertising scannable indication' (or)
    % 'Advertising non connectable indication'
    case {'Advertising indication', 'Advertising scannable indication', ...
            'Advertising non connectable indication'}
        % Advertiser device address (6-octets)
        advertiserAddress = int2bit(...
            hex2dec(cfg.AdvertiserAddress), sixOctetLen, 0);
        
        % Advertising data (variable [0-31 octets])
        if ~isempty(cfg.AdvertisingData)
            advertisingData = reshape(int2bit(...
                hex2dec(cfg.AdvertisingData), octetLen, 0), [], 1);
        else
            advertisingData = [];
        end
        
        % Put it all together
        payload = [advertiserAddress; advertisingData];
        
    % 'Advertising direct indication'
    case 'Advertising direct indication'
        % Advertiser device address (6-octets)
        advertiserAddress = int2bit(...
            hex2dec(cfg.AdvertiserAddress), sixOctetLen, 0);
        
        % Target device address (6-octets)
        targetAddress = int2bit(...
            hex2dec(cfg.TargetAddress), sixOctetLen, 0);
        
        % Put it all together
        payload = [advertiserAddress; targetAddress];
        
    % 'Scan request'
    case 'Scan request'
        % Scanner device address (6-octets)
        scannerAddress = int2bit(...
            hex2dec(cfg.ScannerAddress), sixOctetLen, 0);
        
        % Advertiser device address (6-octets)
        advertiserAddress = int2bit(...
            hex2dec(cfg.AdvertiserAddress), sixOctetLen, 0);
        
        % Put it all together
        payload = [scannerAddress; advertiserAddress];
        
    % 'Scan response'
    case 'Scan response'
        % Advertiser device address (6-octets)
        advertiserAddress = int2bit(...
            hex2dec(cfg.AdvertiserAddress), sixOctetLen, 0);
        
        % ScanResponse data (variable [0-31 octets])
        if ~isempty(cfg.ScanResponseData)
            scanRspData = reshape(int2bit(...
                hex2dec(cfg.ScanResponseData), octetLen, 0), [], 1);
        else
            scanRspData = [];
        end
        
        % Put it all together
        payload = [advertiserAddress; scanRspData];
        
    % 'Connection indication'
    case 'Connection indication'
        % Validate interdependent properties
        % Validate Peripheral latency
        success = validatePeripheralLatency(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLPeripheralLatency', ...
            cfg.PeripheralLatency, cfg.PDUType);
        % Validate connection timeout
        success = validateConnectionTimeout(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLConnectionTimeout', ...
            cfg.ConnectionTimeout, cfg.PDUType);
        % Validate window size
        success = validateWindowSize(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLWindowSize', ...
            cfg.WindowSize, cfg.PDUType);
        % Validate window offset
        success = validateWindowOffset(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLWindowOffset', ...
            cfg.WindowOffset, cfg.PDUType);
        
        % Initiator device address (6-octets)
        initiatorAddress = int2bit(...
            hex2dec(cfg.InitiatorAddress), sixOctetLen, 0);
        
        % Advertiser device address (6-octets)
        advertiserAddress = int2bit(...
            hex2dec(cfg.AdvertiserAddress), sixOctetLen, 0);
        
        % Access address for the connection (4-octets)
        connAccessAddress = int2bit(...
            hex2dec(cfg.AccessAddress), fourOctetLen, 0);
        
        % CRC initialization value (3-octets)
        crcInit = int2bit(...
            hex2dec(cfg.CRCInitialization), tripleOctetLen, 0);
        
        % Transmit window size (1-octet)
        windowSize = int2bit(...
            cfg.WindowSize, octetLen, 0);
        
        % Transmit window offset (2-octets)
        windowOffset = int2bit(...
            cfg.WindowOffset, doubleOctetLen, 0);
        
        % Connection interval (2-octets)
        connInterval = int2bit(...
            cfg.ConnectionInterval, doubleOctetLen, 0);
        
        % Peripheral latency (2-octets)
        peripheralLatency = int2bit(...
            cfg.PeripheralLatency, doubleOctetLen, 0);
        
        % Connection supervision timeout (2-octets)
        connTimeout = int2bit(...
            cfg.ConnectionTimeout, doubleOctetLen, 0);
        
        % Channel map (5-octets)
        channelMap = generateChanMap(cfg.UsedChannels);
        
        % Hop increment count (5-bits)
        hopIncrement = int2bit(cfg.HopIncrement, 5, 0);
        
        % Sleep clock accuracy (3-bits)
        sca = generateSCACode(cfg.SleepClockAccuracy);
        
        % Put it all together
        payload = [initiatorAddress; advertiserAddress; connAccessAddress; ...
            crcInit; windowSize; windowOffset; connInterval; peripheralLatency; ...
            connTimeout; channelMap; hopIncrement; sca];
end
% Calculate payload length
lengthValue = numel(payload)/octetLen;
end

% Generate pduCode, txAddress, rxAddress based on given configuration
function [pduCode, txAddress, rxAddress] = generateHeaderFields(cfg)
% Refer "Table 2.3" in Bluetooth Core Specification v5.0 | Vol 6, Part B

% Initialize
pduTypeCode = 0;
txAddress = 0;
rxAddress = 0;

% Based on PDU type the pduTypeCode, txAddress, rxAddress values will
% change
switch cfg.PDUType
    case 'Advertising indication'
        pduTypeCode = 0;
        txAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
    case 'Advertising direct indication'
        pduTypeCode = 1;
        txAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
        rxAddress = double(strcmp(cfg.TargetAddressType, 'Random'));
    case 'Advertising non connectable indication'
        pduTypeCode = 2;
        txAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
    case 'Scan request'
        pduTypeCode = 3;
        txAddress = double(strcmp(cfg.ScannerAddressType, 'Random'));
        rxAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
    case 'Scan response'
        pduTypeCode = 4;
        txAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
    case 'Connection indication'
        pduTypeCode = 5;
        txAddress = double(strcmp(cfg.InitiatorAddressType, 'Random'));
        rxAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
    case 'Advertising scannable indication'
        pduTypeCode = 6;
        txAddress = double(strcmp(cfg.AdvertiserAddressType, 'Random'));
end
pduCode = int2bit(pduTypeCode, 4, 0);
end

% Generate scaCode from the given SCA description string
function scaCode = generateSCACode(scaString)
% Refer "Table 2.9" in Bluetooth Core Specification v5.0 | Vol 6, Part B

% Initialize
sca = 0;

switch scaString
    case '251 to 500 ppm'
        sca = 0;
    case '151 to 250 ppm'
        sca = 1;
    case '101 to 150 ppm'
        sca = 2;
    case '76 to 100 ppm'
        sca = 3;
    case '51 to 75 ppm'
        sca = 4;
    case '31 to 50 ppm'
        sca = 5;
    case '21 to 30 ppm'
        sca = 6;
    case '0 to 20 ppm'
        sca = 7;
end
scaCode = int2bit(sca, 3, 0);
end

% Generate bitmap indicating good (1) & bad (0) channels from used channels
function bitmap = generateChanMap(chanList)
% Initialize
octetLen = 8;
fiveOctetLen = 5*octetLen;
bitmap = zeros(fiveOctetLen, 1);
% Set the indices specified by the channel list to 1
bitmap(chanList+1) = 1;
end
