function pdu = helperBluetoothLMPPDU(cfgLMP)
%helperBluetoothLMPPDU Blued link manager protocol (AMP) protocol data
%unit (PDU) generation
%   PDU = helperBluetoothLMPPDU(CFGLMP) generates a Bluetooth LMP PDU for a
%   given configuration, CFGLMP, with default values.
%
%   PDU represents the output Bluetooth LMP PDU, returned as a binary
%   column vector.
%
%   CFGLMP is a configuration object of type <a
%   href="matlab:help('helperBluetoothLMPPDUConfig')">helperBluetoothLMPPDUConfig</a>
%   that configures the LMP PDU.
%
%   % Example 1:
%   % Generate a 'LMP accepted' PDU with transaction ID set as 1 and
%   % requested opcode set as 60.
%
%   cfgLMP                 = helperBluetoothLMPPDUConfig;
%   cfgLMP.PDUType         = 'LMP accepted';
%   cfgLMP.TransactionID   = 1;
%   cfgLMP.RequestedOpcode = 'LMP set AFH';
%   pdu = helperBluetoothLMPPDU(cfgLMP);
%
%   % Example 2:
%   % Create a helperBluetoothLMPPDUConfig object for a 'LMP channel
%   % classification request' PDU with adaptive frequency hopping (AFH) 
%   % minimum interval of 1800 slots and AFH maximum interval of 24000 slots.
%
%   cfgLMP = helperBluetoothLMPPDUConfig(...
%                          'PDUType', 'LMP channel classification request', ...
%                          'TransactionID', 0, ...
%                          'AFHReportingMode', true, ...
%                          'AFHMinInterval', 1800, ...
%                          'AFHMaxInterval', 24000);
%   pdu = helperBluetoothLMPPDU(cfgLMP);
%
%   See also helperBluetoothLMPPDUDecode, helperBluetoothLMPPDUConfig.

%   Copyright 2020 The MathWorks, Inc.

%#codegen

% Validate input argument CFGLMP
validateattributes(cfgLMP, {'helperBluetoothLMPPDUConfig'}, ...
    {'scalar'}, 'helperBluetoothLMPPDU', 'cfgLMP', 1);

% Opcode octet (TID | Opcode)
opcode = generateOpcodeOctet(cfgLMP);

% Payload
payload = generatePayload(cfgLMP);

% Put it all together
pdu = [opcode; payload];
end

% Generate payload
function payload = generatePayload(cfg)
% Initialize
payload = zeros(1, 0);
octetLength = 8;

switch(cfg.PDUType)
    % 'LMP accepted', refer Bluetooth Core Specification v5.1 - Vol 2, Part
    % C, Section 2.7
    case 'LMP accepted'
        % Opcode (1-octet)
        [opCodeInterger, ~] = getOpcode(cfg.RequestedOpcode);
        requestedOpcode = comm.internal.utilities.de2biBase2RightMSB(...
            opCodeInterger, octetLength)';
        
        % Payload
        payload = requestedOpcode;
    
    % 'LMP not accepted', refer Bluetooth Core Specification v5.1 - Vol 2,
    % Part C, Section 2.7
    case 'LMP not accepted'
        % Opcode (1-octet)
        [opCodeInterger, ~] = getOpcode(cfg.RequestedOpcode);
        requestedOpcode = comm.internal.utilities.de2biBase2RightMSB(...
            opCodeInterger, octetLength)';
        
        % Error code (1-octet)
        errorCode = comm.internal.utilities.de2biBase2RightMSB(...
            hex2dec(cfg.ErrorCode), octetLength)';
        
        % Put it all together to form payload
        payload = [requestedOpcode; errorCode];
    
    % 'LMP set AFH', refer Bluetooth Core Specification v5.1 - Vol 2, Part
    % C, Section 4.1.4
    case 'LMP set AFH'
        % AFH instant (4-octets)
        AFHInstant = comm.internal.utilities.de2biBase2RightMSB(...
            cfg.AFHInstant, 4*octetLength)';
        
        % AFH mode (1-octet)
        if cfg.AFHMode
            AFHMode = comm.internal.utilities.de2biBase2RightMSB(...
                1, octetLength)';
        else
            AFHMode = comm.internal.utilities.de2biBase2RightMSB(...
                0, octetLength)';
        end
        
        % AFH channel map (10-octets)
        AFHChannelMap = generateChanMap(cfg.UsedChannels);
        
        % Put it all together to form payload
        payload = [AFHInstant; AFHMode; AFHChannelMap];
        
    % 'LMP channel classification', refer Bluetooth Core Specification v5.1
    % - Vol 2, Part C, Section 4.1.5
    case 'LMP channel classification'
        % AFH channel classification (10-octets)
        AFHChannelClassification = reshape((de2bi(...
            cfg.AFHChannelClassification, 2))', [1 10*octetLength]); 
        
        % Payload
        payload = AFHChannelClassification';
    
    % 'LMP channel classification request', refer Bluetooth Core
    % Specification v5.1 - Vol 2, Part C, Section 4.1.5
    case 'LMP channel classification request'
        if cfg.AFHMaxInterval < cfg.AFHMinInterval
            fprintf(['''LMP channel classification request'' generation failed: ' ...
                'AFH maximum interval value must be greater than or equal to AFH minimum interval.\n']);
            return;
        end
        % AFH reporting mode (1-octet)
        if cfg.AFHReportingMode
            AFHReportingMode = comm.internal.utilities.de2biBase2RightMSB(...
                1, octetLength)';
        else
            AFHReportingMode = comm.internal.utilities.de2biBase2RightMSB(...
                0, octetLength)';
        end
        
        % AFH minimum interval (2-octets)
        AFHMinInterval = comm.internal.utilities.de2biBase2RightMSB(...
            cfg.AFHMinInterval, 2*octetLength)';
        
        % AFH maximum interval (2-octets)
        AFHMaxInterval = comm.internal.utilities.de2biBase2RightMSB(...
            cfg.AFHMaxInterval, 2*octetLength)';
        
        % Put it all together to form payload
        payload = [AFHReportingMode; AFHMinInterval; AFHMaxInterval];
end
end

% Generate opcode
function opcodeField = generateOpcodeOctet(cfg)
% Initialize
opcodeField = zeros(1, 0);
octetLength = 8;

% Based on the PDU type, the opcode and the extended opcode values will
% change
switch(cfg.PDUType)
    % Opcode for LMP accepted
    case 'LMP accepted'
        [opCodeInterger, ~] = getOpcode(cfg.PDUType);
        opCodeBinary = comm.internal.utilities.de2biBase2RightMSB(opCodeInterger, ...
            octetLength - 1)';
        opcodeField = [cfg.TransactionID; opCodeBinary];
    
    % Opcode for LMP not accepted
    case 'LMP not accepted'
        [opCodeInterger, ~] = getOpcode(cfg.PDUType);
        opCodeBinary = comm.internal.utilities.de2biBase2RightMSB(opCodeInterger, ...
            octetLength - 1)';
        opcodeField = [cfg.TransactionID; opCodeBinary];
    
    % Opcode for LMP set AFH
    case 'LMP set AFH'
        [opCodeInterger, ~] = getOpcode(cfg.PDUType);
        opCodeBinary = comm.internal.utilities.de2biBase2RightMSB(opCodeInterger, ...
            octetLength - 1)';
        opcodeField = [cfg.TransactionID; opCodeBinary];
    
    % Opcode for LMP channel classification
    case 'LMP channel classification'
        [opCodeInterger, extOpcodeInteger] = getOpcode(cfg.PDUType);
        opCodeBinary = comm.internal.utilities.de2biBase2RightMSB(...
            opCodeInterger, octetLength - 1)';
        extOpcodeBinary = comm.internal.utilities.de2biBase2RightMSB(...
            extOpcodeInteger, octetLength)';
        opcodeField = [cfg.TransactionID; opCodeBinary; extOpcodeBinary];
    
    % Opcode for LMP channel classification request
    case 'LMP channel classification request'
        [opCodeInterger, extOpcodeInteger] = getOpcode(cfg.PDUType);
        opCodeBinary = comm.internal.utilities.de2biBase2RightMSB(...
            opCodeInterger, octetLength - 1)';
        extOpcodeBinary = comm.internal.utilities.de2biBase2RightMSB(...
            extOpcodeInteger, octetLength)';
        opcodeField = [cfg.TransactionID; opCodeBinary; extOpcodeBinary];
end
end

% Get opcode of the PDU 
function [opcode, extOpcode] = getOpcode(pduType)
opcode = zeros(1, 0);
extOpcode = zeros(1, 0);
switch pduType
    case 'LMP accepted'
        opcode = 3;
    case 'LMP not accepted'
        opcode = 4;
    case 'LMP set AFH'
        opcode = 60;
    case 'LMP channel classification'
        opcode = 127;
        extOpcode = 17;
    case 'LMP channel classification request'
        opcode = 127;
        extOpcode = 16;
end
end

% Generate bitmap indicating the list of good and bad channels
function bitmap = generateChanMap(chanList)
% Bitmap is 10 octets i.e. 80 bits
bitmap = zeros(80, 1);
% Set the indices specified by the channel list to 1
bitmap(chanList+1) = 1;
end

% LocalWords:  CFGLMP Bluetooth LMP PDU LMPPDU opcode AFH pdu
