function appFrame = APSFrameGenerator(cfg, varargin)
%APSFrameGenerator Generate ZigBee application support sub-layer frames
%   APSFRAME = APSFrameGenerator(CFG) generates the application support
%   (APS) sub-layer frame APPFRAME corresponding to the
%   zigbee.APPFrameConfiguration object CFG and containing the payload
%   PAYLOAD.
%
%   See also zigbee.APSFrameConfig, zigbee.APSFrameDecoder

%   Copyright 2016-2023 The MathWorks, Inc.

% 1. Frame Control field
frameControl = generateFrameControl(cfg);

if strcmp(cfg.FrameType, 'Acknowledgment')
  payload = [];
else
  payload = varargin{1};
end

[destinationEndpoint, groupAddress] = deal([]);
if ~strcmp(cfg.DeliveryMode, 'Group')
  % 2. Destination endpoint (1 octet)
  if strcmp(cfg.FrameType, 'Data') || ...
    (strcmp(cfg.FrameType, 'Acknowledgment') && strcmp(cfg.AcknowledgmentType, 'Data') )
  
    destinationEndpoint = int2bit(hex2dec(cfg.DestinationEndpoint), 8, false)';
  end
else
  % 2. Group address (2 octets)
  if strcmp(cfg.FrameType, 'Data') 
    groupAddress = int2bit(hex2dec(cfg.GroupAddress), 16, false)';
  end
end

[clusterID, profileID, sourceEndpoint] = deal([]);
if strcmp(cfg.FrameType, 'Data') || ...
  (strcmp(cfg.FrameType, 'Acknowledgment') && strcmp(cfg.AcknowledgmentType, 'Data') )

  % 4. Cluster identifier
  clusterID = int2bit(hex2dec(cfg.ClusterID), 16, false)';
  
  % 5. Profile identifier
  profileID = int2bit(hex2dec(cfg.ProfileID), 16, false)';
  
  % 6. Source endpoint (1 octet)
  sourceEndpoint = int2bit(hex2dec(cfg.SourceEndpoint), 8, false)';
end

% 7. APS Counter (1 octet)
apsCounter = int2bit(cfg.APSCounter, 8, false)';

% 8. Extended header
extendedHeader = generateExtendedHeader(cfg);

% 9. Auxiliary (security) header
securityHeader = zigbee.internal.generateSecurityHeader(cfg);

% Put everything together
appFrame = [frameControl destinationEndpoint groupAddress clusterID ...
            profileID sourceEndpoint apsCounter extendedHeader securityHeader];
% Convert bits to bytes:
appFrame = zigbee.internal.bits2bytes(appFrame);
appFrame = [appFrame; payload];

function frameControl = generateFrameControl(cfg)

% 0. Initialize (one octet)
frameControl = zeros(1, 8);

% 1. Frame type
if strcmp(cfg.FrameType, 'Data')
  % frameControl(1:2) = [0 0];
  
elseif strcmp(cfg.FrameType, 'Acknowledgment')
  frameControl(2) = 1; %  % frameControl(1:2) = [0 1];
  
elseif strcmp(cfg.FrameType, 'Command')
  error(message('lrwpan:ZigBee:APPCommandsNotSupported'));
end


% 2. Delivery mode
if strcmp(cfg.DeliveryMode, 'Unicast')
  % frameControl(3:4) = [0 0];
elseif strcmp(cfg.DeliveryMode, 'Indirect')
  frameControl(3:4) = [1 0];
elseif strcmp(cfg.DeliveryMode, 'Broadcast')
  frameControl(3:4) = [0 1];
elseif strcmp(cfg.DeliveryMode, 'Group')
  frameControl(3:4) = [1 1];
end


% 3. Ack format
if strcmp(cfg.FrameType, 'Acknowledgment') && strcmp(cfg.AcknowledgmentType, 'Command')
  frameControl(5) = 1;
end

% 4. Security
frameControl(6) = double(cfg.Security);

% 5. Acknowledgment request
frameControl(7) = double(cfg.AcknowledgmentRequest);

% 6. Extended header
frameControl(8) = double(cfg.ExtendedHeader);



function extendedHeader = generateExtendedHeader(cfg)

if ~cfg.ExtendedHeader
  extendedHeader = [];
else
  % 1. Extended frame control
  extendedFrameControl = zeros(1, 8);

  if strcmp(cfg.FragmentationStatus, 'No fragmentation')
    % extendedFrameControl(1:2) = [0 0];
  elseif strcmp(cfg.FragmentationStatus, 'First fragment')
    extendedFrameControl(1:2) = [1 0];
  else % strcmp(cfg.FragmentationStatus, 'Subsequent fragment')
    extendedFrameControl(1:2) = [0 1];
  end

  [blockNumber, ackBitField] = deal([]);
  if ~strcmp(cfg.FragmentationStatus, 'No fragmentation')

    % 2. Block number (1 octet)
    blockNumber = int2bit(cfg.BlockNumber, 8, false)';

    if strcmp(cfg.FrameType, 'Acknowledgment')
      % 3. ACK bit field
      ackBitField = int2bit(hex2dec(cfg.ACKBitField), 8, false)';
    end
  end

  extendedHeader = [extendedFrameControl blockNumber ackBitField];
end

