function libraryWideFrame = GeneralFrameGenerator(cfg)
%GENERALFRAMEGENERATOR Generate general, library-wide ZigBee command frames
% generalFrame = GENERALFRAMEGENERATOR(CFG) generates the general,
% library-wide ZigBee command frame generalFrame corresponding to the
% configuration object CFG.
%
% See also zigbee.GeneralFrameConfig, zigbee.GeneralFrameDecoder.

% Copyright 2017-2023 The MathWorks, Inc.

switch cfg.CommandType
  % Read Attributes command
  case 'Read Attributes'    
    % Currently only a single ID is supported (2 octets per ID)    
    libraryWideFrame = int2bit(hex2dec(cfg.AttributeID), 16, false);
    
  % Read Attributes Response command
  case 'Read Attributes Response'
    % Currently only a single record is supported
    
    % 1. Attribute ID (2 octets)
    attributeID = int2bit(hex2dec(cfg.AttributeID), 16, false);

    % 2. Status (1 octet)
    status = generateStatusID(cfg.Status)';

    [attributeType, attributeValue] = deal([]);
    if strcmp(cfg.Status, 'Success')
      % 3. Attribute Data Type (1 octet)
      attributeType = generateDataTypeID(cfg.AttributeType)';
            
      % 4. Attribute Value (Variable octets)
      attributeValue = generateAttributeValue(cfg.AttributeType, cfg.AttributeValue)';
    end
    
    % 5. Putting it all together
    libraryWideFrame = [attributeID; status; attributeType; attributeValue];

  % Write Attributes, Write Attributes Undivided, Write Attributes No Response, Report Attributes commands
  case {'Write Attributes', 'Write Attributes Undivided', 'Write Attributes No Response', ...
        'Report Attributes'}
      
    % Currently a single record is supported    
    
    % 1. Attribute Identifier (2 octets)
    attributeID = int2bit(hex2dec(cfg.AttributeID), 16, false);    

    % 2. Attribute Data Type (1 octet)
    attributeType = generateDataTypeID(cfg.AttributeType)';    

    % 3. Attribute Value (Variable octets)
    attributeValue = generateAttributeValue(cfg.AttributeType, cfg.AttributeValue)';
      
    % 4. Putting it all together
    libraryWideFrame = [attributeID; attributeType; attributeValue];

  % Write Attributes Response command
  case 'Write Attributes Response'
    
    % Currently a single record is supported 

    % 1. Status (1 octet)
    status = generateStatusID(cfg.Status)';

    attributeID = [];
    if ~strcmp(cfg.Status, 'Success')
      % 2. Attribute Identifier (2 octets)
      attributeID = int2bit(hex2dec(cfg.AttributeID), 16, false);
    end
    
    % 4. Putting it all together
    libraryWideFrame = [status; attributeID];
 
  % Default Response command
  case 'Default Response'
    
    % 1. Command Identifier (1 octet)
    commandID = int2bit(hex2dec(cfg.CommandToRespond), 8, false);

    % 2. Status Code (1 octet)
    status = generateStatusID(cfg.Status)';
    
    % 3. Putting it all together
    libraryWideFrame = [commandID; status];
end

% Convert bits to bytes:
libraryWideFrame = zigbee.internal.bits2bytes(libraryWideFrame);

end


function statusID = generateStatusID(statusStr)

statusTable = { 'Success',                                  '00'; ...
                'Failure',                                  '01'; ...
                'Not authorized',                           '7E'; ...
                'Non-zero reserved',                        '7F'; ...
                'Malformed command',                        '80'; ...
                'Unsupported cluster command',              '81'; ...
                'Unsupported general command',              '82'; ...
                'Unsupported manufacturer cluster command', '83'; ...
                'Unsupported manufacturer general command', '84'; ...
                'Invalid field',                            '85'; ...
                'Unsupported attribute',                    '86'; ...
                'Invalid value',                            '87'; ...
                'Read only',                                '88'; ...
                'Insufficient space',                       '89'; ...
                'Duplicate exists',                         '8A'; ...
                'Not found',                                '8B'; ...
                'Unreportable attribute',                   '8C'; ...
                'Invalid data type',                        '8D'; ...
                'Invalid selector',                         '8E'; ...
                'Write only',                               '8F'; ...
                'Inconsistent startup state',               '90'; ...
                'Defined out of band',                      '91'; ...
                'Inconsistent',                             '92'; ...
                'Action denied',                            '93'; ...
                'Timeout',                                  '94'; ...
                'Abort',                                    '95'; ...
                'Invalid image',                            '96'; ...
                'Wait for data',                            '97'; ...
                'No image available',                       '98'; ...
                'Require more image',                       '99'; ...
                'Notification pending',                     '9A'; ...
                'Hardware failure',                         'C0'; ...
                'Software failure',                         'C1'; ...
                'Calibration error',                        'C2'; ...
                'Unsupported cluster',                      'C3'; ...
  };

statusStr2Hex = containers.Map(statusTable(:, 1), statusTable(:, 2));
statusID = int2bit(hex2dec(statusStr2Hex(statusStr)), 8, false)';
end


function dataTypeID = generateDataTypeID(dataTypeStr)

typeTable =   { 'No Data',            '00'; ...
                '8-bit data',         '08'; ...
                '16-bit data',        '09'; ...
                '24-bit data',        '0A'; ...
                '32-bit data',        '0B'; ...
                '40-bit data',        '0C'; ...
                '48-bit data',        '0D'; ...                
                '56-bit data',        '0E'; ...
                '64-bit data',        '0F'; ...
                'Boolean',            '10'; ...
                '8-bit bitmap',       '18'; ...
                '16-bit bitmap',      '19'; ...
                '24-bit bitmap',      '1A'; ...
                '32-bit bitmap',      '1B'; ...
                '40-bit bitmap',      '1C'; ...
                '48-bit bitmap',      '1D'; ...
                '56-bit bitmap',      '1E'; ...
                '64-bit bitmap',      '1F'; ...
                'uint8',              '20'; ...
                'uint16',             '21'; ...
                'uint24',             '22'; ...
                'uint32',             '23'; ...
                'uint40',             '24'; ...
                'uint48',             '25'; ...
                'uint56',             '26'; ...
                'uint64',             '27'; ...
                'int8',               '28'; ...
                'int16',              '29'; ...
                'int24',              '2A'; ...
                'int32',              '2B'; ...
                'int40',              '2C'; ...
                'int48',              '2D'; ...
                'int56',              '2E'; ...
                'int64',              '2F'; ...
                '8-bit enumeration',  '30'; ...
                '16-bit enumeration', '31'; ...
                'Semi-precision',     '38'; ...
                'single',             '39'; ...
                'double',             '3A'; ...
                'Octet string',       '41'; ...
                'Character string',   '42'; ...
                'Long octet string',    '43'; ...
                'Long character string','44'; ...
                'Array',              '48'; ...
                'Structure',          '4C'; ...
                'Set',                '50'; ...
                'Bag',                '51'; ...
                'Time of day',        'E0'; ...
                'Date',               'E1'; ...
                'UTCTime',            'E2'; ...
                'Cluster ID',         'E8'; ...                
                'Attribute ID',       'E9'; ...
                'BACnet OID',         'EA'; ...
                'IEEE address',       'F0'; ...
                '128-bit security key',  'F1'; ...
                'Opaque',             'FF'; ...
  };

typeStr2Hex = containers.Map(typeTable(:, 1), typeTable(:, 2));
dataTypeID = int2bit(hex2dec(typeStr2Hex(dataTypeStr)), 8, false)';

end


function [dataBits, len] = generateAttributeValue(type, value)

if any(strcmp(type, {'int8', 'int16', 'int24', 'int32', 'int40', ...
    'int48', 'int56', 'int64'}))  && (value < 0) % for -Ve integers

  dataLen = getDataLength(type);
  % Converting -ve number into signed equivalent.
  value = dec2hex((2^dataLen)+value);

% Converting numeric input to hexadecimal format
elseif ~ischar(value) && ~isstruct(value) && ~any(strcmp(type, {'single', 'double', 'Boolean'})) 
  dataLen = getDataLength(type);
  if dataLen <= 32
    value = dec2hex(value, dataLen/4);
  elseif dataLen <= 64
    % hex2dec function doesn't works for integers of size greater than 1
    % word (32-bits). so encoding of first word(MSB) and remaining bits are
% done separately.

    % LSB bits (Remaining bits after removal of MSB word).
    word2 = dec2hex(bitand(value, 2^(dataLen-32)-1), (dataLen-32)/4);
    value = bitshift(value, -(dataLen-32));

    % MSB word.
    word1 = dec2hex(bitand(value, hex2dec('ffffffff')), 8);

    value = [word1 word2];

  else % 128-bits value.
    % LSB word.
    word4 = dec2hex(bitand(value, hex2dec('ffffffff')), 8);
    value = bitshift(value, -32);
    word3 = dec2hex(bitand(value, hex2dec('ffffffff')), 8);
    value = bitshift(value, -32);
    word2 = dec2hex(bitand(value, hex2dec('ffffffff')), 8);
    value = bitshift(value, -32);
    % MSB word.
    word1 = dec2hex(bitand(value, hex2dec('ffffffff')), 8);
    value = [word1 word2 word3 word4];
  end
end

switch type
  
  case 'Boolean'
    dataBits = int2bit(uint8(value), 8, false)';
    len = 8; 
  
  case 'single'
    singlePrecisionValue = single(value);
    value = typecast(singlePrecisionValue, 'uint8');
    dataBits = int2bit(uint8(value(:)), 8, false)';
    len = 32;

  case 'double'
    doublePrecisionValue = double(value);
    value = typecast(doublePrecisionValue, 'uint8');
    dataBits = int2bit(uint8(value(:)), 8, false)';
    len = 64;

  otherwise
    len = getDataLength(type);
    if(len <= 32)
      dataBits = int2bit(hex2dec(value), len, false)';
    else
      dataBits = zeros(1, len);
      j = numel(value);
      for i = 1:4:len
        dataBits(i:(i+4-1)) = int2bit(hex2dec(value(j)), 4, false);
        j = j-1;
      end
    end

end
end


function len = getDataLength(type)
switch type  
  case {'8-bit data', 'uint8', '8-bit bitmap', 'int8', '8-bit enumeration', 'Boolean', ...
      'Octet string', 'Character string', 'Long octet string', 'Long character string'}
    len = 8;
    
  case {'16-bit data', '16-bit bitmap', '24-bit data', '32-bit data', '40-bit data', ...
        '48-bit data', '56-bit data', '64-bit data', '24-bit bitmap', '32-bit bitmap', ...
        '40-bit bitmap', '48-bit bitmap', '56-bit bitmap', '64-bit bitmap', ...
        '16-bit enumeration', 'uint16', 'uint24', 'uint32', 'uint40', 'uint48', ...
        'uint56', 'uint64', 'int16', 'int24', 'int32', 'int40', 'int48', 'int56', 'int64'}
      if type(1) == 'u'
        offset = 5;
      elseif type(1) == 'i'
        offset = 4;
      else
        offset = 1;
      end
      len = str2double(type([offset offset+1]));
 
  case 'single'
    len = 32;
  
  case 'double'
    len = 64;
end
end
