function sceneFrame = ScenesFrameGenerator(cfg)
%SCENESFRAMEGENERATOR Generate frames for the Scenes ZigBee cluster
% SCENEFRAME = SCENESFRAMEGENERATOR(CFG) generates the Scenes cluster
% library frame SCENEFRAME corresponding to the configuration object CFG.
%
% See also zigbee.ScenesFrameConfig, zigbee.ScenesFrameDecoder.

% Copyright 2017-2023 The MathWorks, Inc.

switch cfg.CommandType
  % Add Scene Commands
  case {'Add Scene', 'Enhanced Add Scene'}
    % 1 & 2. Group and Scene IDs (2 + 1 octets)
    [groupIDFrom, sceneIDFrom] = generateGroupSceneID(cfg);

    % 3, 4 & 5. Transition Time (2 octets), Scene Name (variable), Extension Field (variable)
    [transitionTime, nameLen, sceneName, extFieldSet] = ...
                                        generateTimeNameExtField(cfg);

    % 7. Putting it all together:
    sceneFrame = [groupIDFrom sceneIDFrom transitionTime nameLen sceneName extFieldSet]';
    
  % View/Remove/Store/Recall Scene Commands
  case {'View Scene', 'Remove Scene', 'Store Scene', 'Recall Scene', 'Enhanced View Scene'}
    % 1 & 2. Group and Scene IDs (2 + 1 octets)
    [groupIDFrom, sceneIDFrom] = generateGroupSceneID(cfg);

    % 3. Putting it all together:
    sceneFrame = [groupIDFrom; sceneIDFrom]';

  % 'Remove All Scenes' / 'Get Scene Membership' Commands
  case {'Remove All Scenes', 'Get Scene Membership'}
    % 1. Group ID (2 octets)
    sceneFrame = int2bit(hex2dec(cfg.GroupID), 16, false);

  % Copy Scene Command
  case 'Copy Scene'
    % 1. Mode (1 octet)
    mode = [cfg.CopyAllScenes(:) zeros(7, 1)];

    % 2. Group ID From (2 octets)
    groupIDFrom = int2bit(hex2dec(cfg.GroupIDFrom), 16, false);

    % 3. Scene ID From (1 octet)
    sceneIDFrom = int2bit(hex2dec(cfg.SceneIDFrom), 8, false);

    % 4. Group ID To (2 octets)
    groupIdTo = int2bit(hex2dec(cfg.GroupIDTo), 16, false);

    % 5. Scene ID To (1 octet)
    sceneIdTo = int2bit(hex2dec(cfg.SceneIDTo), 8, false);

    % 6. Putting it all together:
    sceneFrame = [mode; groupIDFrom; sceneIDFrom; groupIdTo; sceneIdTo];

  % Add/Remove/Store Scene Response Command
  case {'Add Scene Response', 'Remove Scene Response', 'Store Scene Response', ...
        'Enhanced Add Scene Response'}
    % 1, 2 & 3. Status (1 octet), groupID (2 octets), sceneID (1 octet)
    [status, groupIDFrom, sceneIDFrom] = generateStatusGroupSceneID(cfg);

    % 4. Putting it all together:
    sceneFrame = [status groupIDFrom sceneIDFrom]';

  % View Scene Response Command
  case {'View Scene Response', 'Enhanced View Scene Response'}
    % 1, 2 & 3. Status (1 octet), groupID (2 octets), sceneID (1 octet)
    [status, groupIDFrom, sceneIDFrom] = generateStatusGroupSceneID(cfg);

    % Initializing for variable size fields
    transitionTime = [];
    sceneName = [];
    extFieldSet = [];
    nameLen = [];
    if strcmp(cfg.Status, 'Success')
      % 4, 5 & 6. Transition Time (2 octets), Scene Name (variable), Extension Field (variable)
      [transitionTime, nameLen, sceneName, extFieldSet] = ...
                                        generateTimeNameExtField(cfg);
    end

    % 8. Putting it all together:
    sceneFrame = [status groupIDFrom sceneIDFrom transitionTime nameLen sceneName extFieldSet]';

  % Remove All Scenes Response Command
  case 'Remove All Scenes Response'
    % 1. Status (1 octet)
    status = generateStatus(cfg);

    % 2. Group ID (2 octets)
    groupIDFrom = int2bit(hex2dec(cfg.GroupID), 16, false);

    % 3. Putting it all together:
    sceneFrame = [status; groupIDFrom];


  % Get Scene Membership Response Command
  case 'Get Scene Membership Response'
    % 1. Status (1 octet)
    status = generateStatus(cfg);

    % 2. Capacity (1 octet)
    capacity = int2bit(cfg.Capacity, 8, false);

    % 3. Group ID (2 octets)
    groupIDFrom = int2bit(hex2dec(cfg.GroupID), 16, false);

    % Initializing for variable size fields
    sceneCount = [];
    sceneList = [];

    if strcmp(cfg.Status, 'Success')
      % 5. Scene count (1 octet)
      sceneCount = int2bit(cfg.SceneCount, 8, false);

      % 6. Scenes list (sceneCount number of octets)
      sceneList = zeros(cfg.SceneCount*8, 1);
      offset = 1;
      for i = 1:cfg.SceneCount
        sceneList(offset:offset+7) = int2bit(hex2dec(cfg.SceneList(i, :)), 8, false);
        offset = offset+8;
      end
    end

    % 7. Putting it all together:
    sceneFrame = [status; capacity; groupIDFrom; sceneCount; sceneList];
    
  % Copy Scene Response Command
  case 'Copy Scene Response'
    % 1. Status (1 octet)
    status = generateStatus(cfg);

    % 2. Group ID (2 octets)
    groupIDFrom = int2bit(hex2dec(cfg.GroupIDFrom), 16, false);

    % 3. Scene ID (1 octet)
    sceneIDFrom = int2bit(hex2dec(cfg.SceneIDFrom), 8, false);

    % 4. Putting it all together:
    sceneFrame = [status; groupIDFrom; sceneIDFrom];
end

% Convert bits to bytes:
if ~isempty(sceneFrame)
  sceneFrame = zigbee.internal.bits2bytes(sceneFrame);
end


function extFieldSet = generateExtFieldSets(cfg)
offset = 1;
extFieldSetLen = numel(cfg.ExtensionFieldSets);
isExtFieldExist = false;
% 0 Preallocate:
extFieldSet = zeros(1, extFieldSetLen*8);
if extFieldSetLen > 0
  for i = 1:extFieldSetLen
    if ~strcmpi(cfg.ExtensionFieldSets(i).ClusterID, 'ffff')
      isExtFieldExist = true;

      % 1. Profile ID (2 octets)
      extFieldSet(offset:offset+15) = int2bit(hex2dec(cfg.ExtensionFieldSets(i).ClusterID), 16, false);
      offset = offset+16;
      
      % 2. Length (1 octet)
      extFieldSet(offset:offset+7) = int2bit(cfg.ExtensionFieldSets(i).Length, 8, false);
      offset = offset+8;
      
      % 3. Extension field set (cfg.Length octets)
      extFieldSet(offset:offset+(cfg.ExtensionFieldSets(i).Length*8)-1) = int2bit(hex2dec(cfg.ExtensionFieldSets(i).ExtensionValues), ...
        (cfg.ExtensionFieldSets(i).Length*8), false);
      offset = offset+(cfg.ExtensionFieldSets(i).Length*8);
    end
  end
  if ~isExtFieldExist
    extFieldSet = [];
  end
end


function [groupID, sceneID] = generateGroupSceneID(cfg)

  % 1. Group ID (2 octets)
  groupID = int2bit(hex2dec(cfg.GroupID), 16, false);

  % 2. Scene ID (1 octet)
  sceneID = int2bit(hex2dec(cfg.SceneID), 8, false);


function [status, groupID, sceneID] = generateStatusGroupSceneID(cfg)

  % 1. Status (1 octet)
	status = generateStatus(cfg);
  
  % 2 & 3. Group and Scene IDs (2 + 1 octets)
  [groupID, sceneID] = generateGroupSceneID(cfg);


function [transitionTime, nameLen, sceneName, extFieldSet] = ...
                                        generateTimeNameExtField(cfg)
  % 1. Transition time (2 octets)
  transitionTime = int2bit(cfg.TransitionTime, 16, false);

  % 2. Scene Name String Length (1 octet)
  nameLen = int2bit(numel(cfg.SceneName), 8, false);

  % 3. Scene name (String)
  if nameLen == 0
    sceneName = [];
  else
    sceneName = int2bit(uint8(cfg.SceneName(:)), 8, false)';
  end

  % 4. Extension field set (variable size)
  extFieldSet = generateExtFieldSets(cfg);


function status = generateStatus(cfg)
switch cfg.Status
  case 'Success'
    status = int2bit(hex2dec('00'), 8, false);
  case 'Invalid field'
    status = int2bit(hex2dec('85'), 8, false);
  case 'No space'
    status = int2bit(hex2dec('89'), 8, false);
  case 'Not found'
    status = int2bit(hex2dec('8B'), 8, false);
end
