function cfg = GeneralFrameDecoder(commandType, zclPayload)
%PROFILEWIDEFRAMEDECODER decodes Profile Wide cluster command frames
% CFG = PROFILEWIDEFRAMEDECODER(COMMANDTYPE, ZCLPAYLOAD) decodes the ZigBee
% Cluster Library payload ZCLPAYLOAD for the command type COMMANDTYPE and
% outputs the corresponding Profile Wide cluster frame configuration to the
% object CFG.
%
% See also zigbee.GeneralFrameConfig, zigbee.GeneralFrameGenerator.

% Copyright 2017-2023 The MathWorks, Inc.

% 0. Initializing configuration object
cfg = zigbee.GeneralFrameConfig('CommandType', commandType);

% convert bytes to bits
zclPayloadBin = zigbee.internal.bytes2bits(zclPayload);

switch commandType
  % Read Attributes command
  case 'Read Attributes'
    % Currently only a single record is supported
       
    % 1. Attribute ID (2 octets)
    cfg.AttributeID = dec2hex(bit2int(zclPayloadBin', length(zclPayloadBin), false), 4);

  % Read Attributes Response command
  case 'Read Attributes Response'
    % Currently only a single record is supported
    
    % 1. Attribute Identifier (2 octets)
    cfg.AttributeID = dec2hex(bit2int(zclPayloadBin(1:2*8)', 2*8, false), 4);    

    % 2. Status (1 octet)
    cfg.Status = decodeStatus(zclPayloadBin(2*8+1 : 3*8));    

    if strcmp(cfg.Status, 'Success')
      % 3. Attribute Data Type (1 octet)
      cfg.AttributeType = decodeDataType(zclPayloadBin(3*8+1 : 4*8));      

      if ~strcmp(cfg.AttributeType, 'No Data')
        % 4. Attribute Value (Variable octets)
        cfg.AttributeValue = decodeAttributeValue(cfg.AttributeType, zclPayloadBin(4*8+1 : end));
      else
        cfg.AttributeValue = [];
      end      
    end

  % 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 only a single record is supported
        
    % 1. Attribute Identifier (2 octets)
    cfg.AttributeID = dec2hex(bit2int(zclPayloadBin(1:2*8)', 2*8, false), 4);

    % 2. Attribute Data Type (1 octet)
    cfg.AttributeType = decodeDataType(zclPayloadBin(2*8+1 : 3*8));

    if ~strcmp(cfg.AttributeType, 'No Data')
      % 3. Attribute Value (Variable octets)
      cfg.AttributeValue = decodeAttributeValue(cfg.AttributeType, zclPayloadBin(3*8+1 : end));
    else
      cfg.AttributeValue = [];
    end
    

  % Write Attributes Response command
  case 'Write Attributes Response'
    
    % Currently only a single record is supported    
    
    % 1. Status (1 octet)
    cfg.Status = decodeStatus(zclPayloadBin(1:8));    

    if ~strcmp(cfg.Status, 'Success')
      % 2. Attribute Identifier (2 octets)
      cfg.AttributeID = dec2hex(bit2int(zclPayloadBin(9:3*8)', 2*8, false), 4);
    else
      cfg.AttributeID = 'FFFF';
    end


  % Default Response command
  case 'Default Response'
    % 1. Command Identifier (1 octet)
    cfg.CommandToRespond = dec2hex(bit2int(zclPayloadBin(1:8)', 8, false), 2);

    % 2. Status Code (1 octet)
    cfg.Status = decodeStatus(zclPayloadBin(9:2*8));

end
end

function statusStr = decodeStatus(statusID)

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'; ...
  };

statusHex2Str = containers.Map(statusTable(:, 2), statusTable(:, 1));
statusStr = statusHex2Str(dec2hex(bit2int(statusID', length(statusID), false), 2));

end


function typeStr = decodeDataType(typeID)

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'; ...
  };

typeHex2Str = containers.Map(typeTable(:, 2), typeTable(:, 1));
typeStr = typeHex2Str(dec2hex(bit2int(typeID', length(typeID), false), 2));
end


function val = decodeAttributeValue(type, zclPayload)

switch type
  case 'Boolean'
    val = logical(zclPayload(1));
    
 case 'single'
    val = typecast(uint32(bit2int(zclPayload(1:4*8)', 4*8, false)), 'single');

  case 'double'
    val = typecast(uint64(bit2int(zclPayload(1:8*8)', 8*8, false)), 'double');

  otherwise
    len = getDataLength(type);
    val = char(zeros(1, len/4));
    j = len/4;
    for i = 1:(len/4)
      val(j) = dec2hex(bit2int(zclPayload(1+((i-1)*4):1+(i*4)-1)', 4, false), 1);
      j = j-1;
    end
    
    if any(strcmp(type, {'int8', 'int16', 'int24', 'int32', 'int40', ...
                         'int48', 'int56', 'int64'}))
      decimalValue = hex2dec(val);
      if(decimalValue > (2^(len-1))) % for -Ve integers
        % Converting unsigned to signed equivalent.
        val = decimalValue - (2^len);
      else
        val = decimalValue;
      end
    end
end
end


function len = getDataLength(type)
switch type  
  case {'8-bit data', 'uint8', '8-bit bitmap', 'int8', '8-bit enumeration', 'Boolean'}
    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
