classdef (Hidden) DetectionConcatenation < ...
    matlabshared.tracking.internal.SimulinkBusUtilities

% This class is for internal use only. It may be removed in the future.

%DetectionConcatenation  Concatenate detections from multiple sensors
%   Combines object detection lists from multiple sensors into a single
%   output object detection list. Sensors include the
%   radarDetectionGenerator and visionDetectionGenerator. This is useful
%   when detections from multiple sensors are passed to a
%   multiObjectTracker. This class only supports the Simulink object
%   detection output created by the sensors mentioned above and is not
%   intended for use in MATLAB.
%
%   DetectionConcatenation properties:
%     NumInputs   - Number of input sensors to combine
%
%   DetectionConcatenation methods:
%     step        - Combines object detection lists
%     release     - Allow property value and input characteristics changes
%     clone       - Create detection concatenation object with same
%                   property values
%     isLocked    - Locked status (logical)
%     reset       - Reset states of detection concatenation object
%     createBus   - Create Simulink output bus
    
% Copyright 2017-2023 The MathWorks, Inc.

%#codegen

    properties(Nontunable)
        %NumInputs Number of input sensors to combine
        %   Specify the number of input sensors to combine into a single
        %   output detection report list as a positive integer.
        NumInputs (1, 1) {mustBePositive, mustBeInteger} = 2
    end
    
    % Simulink only
    properties(Constant, Access=protected)
        %pBusPrefix Prefix used to create bus names
        %   Buses will be created with the name <pBusPrefix>#, where
        %   <pBusPrefix> is the char array set to pBusPrefix and # is an
        %   integer. Subbuses will be created by appending the name of the
        %   structure field associated with the subbus to the base bus
        %   name. For example: <pBusPrefix>#<fieldName>
        pBusPrefix = 'BusDetectionConcatenation'
    end
    
    properties(Access=private)
        pOutTemp
    end
    
    properties(Nontunable,Access=private)
        pHasMeasurementParameters
        pHasObjectAttributes
    end
    
    methods
        function obj = DetectionConcatenation(varargin)
            % Constructor
            setProperties(obj,nargin,varargin{:});
        end
    end
    
    methods(Access=protected)

        function [out, argsToBus] = defaultOutput(obj)
            
            out = struct.empty();
            argsToBus = {};
            
            % Create template for output struct as combination of all input
            % structs
            busIn = propagatedInputBus(obj,1);
            if isempty(busIn)
                return
            end
            
            st = matlabshared.tracking.internal.SimulinkBusUtilities.bus2struct(busIn);
            checkValidInputBus(obj,st,1);
            tmp = st;
            hasMeasParams = isfield(tmp.Detections,'MeasurementParameters');
            hasAttribs = isfield(tmp.Detections,'ObjectAttributes');
            for m = 2:obj.NumInputs
                busIn = propagatedInputBus(obj,m);
                if ~isempty(busIn)
                    st = matlabshared.tracking.internal.SimulinkBusUtilities.bus2struct(busIn);
                    checkValidInputBus(obj,st,m);
                    [tmp, hasMeasParams0, hasAttribs0] = createObjDetStructTemplate(obj,tmp,st,m);
                    hasMeasParams = hasMeasParams || hasMeasParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                else
                    %Input buses to a block are propagated sequentially.
                    %When there are more than one buses, we need to wait
                    %until all buses are propagated.
                    
                    %If the propagated bus is empty, return from here. This
                    %creates an empty signal descriptor to indicate that
                    %all buses are not propagated yet.
                    return
                end
            end
            obj.pHasMeasurementParameters = hasMeasParams;
            obj.pHasObjectAttributes = hasAttribs;
            
            out = optionalDetFields(obj,tmp);
            out = nullify(out);
        end
        
        % Currently only support simulink environment, so nothing to do
        % here
        function y = sendToBus(~,x,varargin)
            y = x;
        end
        
        function checkUniqueSensorIndex(obj,varargin)
            % Check for unique SensorIndex on all inputs. If not all inputs
            % have a unique SensorIndex, then an error is thrown.
            
            % Find all sensor indices for inputs with valid detections
            sensorIndices = NaN(obj.NumInputs,1);
            for m = 1:obj.NumInputs
                in = varargin{m};
                
                % Has valid detections?
                if in.NumDetections>0
                    % Yes, grab sensor index from the first
                    sensorIndices(m) = in.Detections(1).SensorIndex;
                end
            end
            
            validIndices = sensorIndices(~isnan(sensorIndices));
            uniqueIndices = unique(validIndices);
            
            % Were any indices repeated?
            if numel(uniqueIndices)==numel(validIndices)
                return % no
            end
            
            % Find the inputs without a unique sensor index
            notUnique = false(obj.NumInputs,1);
            for m = 1:numel(uniqueIndices)
                thisIndex = uniqueIndices(m);
                iFnd = sensorIndices==thisIndex;
                
                % If more than one match, then its not unique
                if sum(iFnd)>1
                    notUnique = notUnique | iFnd;
                end
            end
            notUniqueInputs = find(notUnique);
            if coder.target('MATLAB')
                str = sprintf('%i, ',notUniqueInputs);
                str = ['[' str(1:end-1) ']'];
            else
                num = numel(notUniqueInputs);
                cellStr = cell(num,1);
                for m = 1:num
                    cellStr{m} = codegenInt2Str(notUniqueInputs(m));
                end
                str = ['[' strjoin(cellStr,', ') ']'];
            end
            coder.internal.error('shared_tracking:DetectionConcatenation:sensorIDsNotUnique',str);
        end
        
        function s = saveObjectImpl(obj)
            
            s = saveObjectImpl@matlabshared.tracking.internal.SimulinkBusUtilities(obj);
            
            if isLocked(obj)
                s.pHasMeasurementParameters = obj.pHasMeasurementParameters;
                s.pHasObjectAttributes = obj.pHasObjectAttributes;
                s.pOutTemp = obj.pOutTemp;
            end
        end
        
        function loadObjectImpl(obj,s,wasLocked)
            
            if wasLocked
                obj.pHasMeasurementParameters = s.pHasMeasurementParameters;
                s = rmfield(s,'pHasMeasurementParameters');
                obj.pHasObjectAttributes = s.pHasObjectAttributes;
                s = rmfield(s,'pHasObjectAttributes');
                obj.pOutTemp = s.pOutTemp;
                s = rmfield(s,'pOutTemp');
            end
            
            loadObjectImpl@matlabshared.tracking.internal.SimulinkBusUtilities(obj,s,wasLocked);
        end
        
        function setupImpl(obj,varargin)
            % Create the objectDetection struct template to populate
            if coder.target('MATLAB')
                in = varargin{1};
                tmp = in;
                hasMeasParams = isfield(tmp.Detections,'MeasurementParameters');
                hasAttribs = isfield(tmp.Detections,'ObjectAttributes');
                for m = 2:obj.NumInputs
                    in = varargin{m};
                    [tmp, hasMeasParams0, hasAttribs0] = createObjDetStructTemplate(obj,tmp,in,m);
                    hasMeasParams = hasMeasParams || hasMeasParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                end
            else
                in = varargin{1};
                tmpc = cell(obj.NumInputs,1);
                tmpc{1} = in;
                hasMeasParams = isfield(in.Detections,'MeasurementParameters');
                hasAttribs = isfield(in.Detections,'ObjectAttributes');
                for m = 2:obj.NumInputs
                    in = varargin{m};
                    [tmpc{m}, hasMeasParams0, hasAttribs0] = createObjDetStructTemplate(obj,tmpc{m-1},in,m);
                    hasMeasParams = hasMeasParams || hasMeasParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                end
                tmp = tmpc{end};
            end
            obj.pHasMeasurementParameters = hasMeasParams;
            obj.pHasObjectAttributes = hasAttribs;
            
            out0 = struct('NumDetections',tmp.NumDetections);
            if isfield(tmp,'IsValidTime')
                out0.IsValidTime = tmp.IsValidTime;
            end
            out0.Detections = tmp.Detections;
            
            out = optionalDetFields(obj,out0);
            out = nullify(out);
            
            obj.pOutTemp = out;
        end
        
        function validateInputsImpl(obj,varargin)
            for m = 1:nargin-1
                in = varargin{m};
                checkValidInputBus(obj, in, m);
            end
        end
        
        function out = stepImpl(obj,varargin)
            % dets = step(h, dets1, dets2, ..., detsN) combines N
            % objectDetection cell arrays into a single objectDetection
            % cell array, dets.
            %
            % The number of elements in dets is the sum of the number of
            % elements in each of the input objectDetection cell arrays.
            %
            % The length of det's Measurement property is the maximum
            % length, L, taken from the N objectDetection inputs. The
            % MeasurementNoise is an L-by-L matrix. Where detections are
            % zero padded when needed.
            %
            % The scalar structs in the ObjectAttributes and
            % MeasurementParameters of dets contains all of the fields
            % defined across all of the N objectDetection cell arrays.
            %
            % dets = step(h, dets1, numDets1, dets2, numDets2, ..., detsN, numDetsN)
            %
            
            checkUniqueSensorIndex(obj,varargin{:});
            
            out = obj.pOutTemp;
            
            ONE = integerOne();
            ZERO = integerZero();

            % Populate the objectDetection struct with the input values
            idx = ONE;
            numDets = ZERO;
            numDetsIn = ZERO;
            hasValidTimeFld = false;
            isValidTime = false;
            for m = 1:obj.NumInputs
                in = varargin{m};
                
                numDetsIn(1) = in.NumDetections;
                for n = ONE:numDetsIn
                    out.Detections(idx) = setScalarStructVals(obj,out.Detections(idx), in.Detections(n));
                    idx = idx+ONE;
                end
                numDets = numDets+numDetsIn;
                if isfield(in,'IsValidTime')
                    isValidTime = isValidTime || in.IsValidTime;
                    hasValidTimeFld = true;
                end
            end
            out.NumDetections(1) = numDets;
            if hasValidTimeFld
                out.IsValidTime = isValidTime;
            end
        end
        
        function dt = getOutputDataTypeImpl(obj)
            dt = getOutputDataTypeImpl@matlabshared.tracking.internal.SimulinkBusUtilities(obj);
        end
        
        function str = getIconImpl(~)
            str = sprintf('Detection\nConcatenation');
        end
        
        function num = getNumInputsImpl(obj)
            num = obj.NumInputs;
        end
        
        function varargout = getInputNamesImpl(obj)
            varargout = cell(obj.NumInputs,1);
            for m = 1:obj.NumInputs
                varargout{m} = sprintf('In%i',m);
            end
        end
        
        function varargout = getOutputNamesImpl(~)
            varargout = {sprintf('Out')};
        end
        
        function out = isOutputFixedSizeImpl(~)
            out = true;
        end

        function sz = getOutputSizeImpl(~)
            sz = [1 1];
        end
    
        function cp = isOutputComplexImpl(~)
            cp = false;
        end
    end
    
    methods(Access=private)
        function checkValidInputBus(~, in, port)
            
            % Is scalar bus
            validateattributes(in,{'struct'},{'scalar'},...
                'DetectionConcatenation',['In' codegenInt2Str(port)]);
            
            % Has NumDetections
            cond = ~isfield(in,'NumDetections');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.NumDetections']);
            
            % NumDetections is a nonnegative integer scalar
            validateattributes(in.NumDetections,{'numeric'},{'scalar','nonnegative','integer','real','finite'},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.NumDetections']);
            
            % If IsValidTime is defined...
            if isfield(in,'IsValidTime') % Optional field
                validateattributes(in.IsValidTime,{'logical'},{'scalar'},...
                    'DetectionConcatenation',['In' codegenInt2Str(port) '.IsValidTime']);
            end
            
            % Check for invalid fields
            validFlds = {'NumDetections', 'IsValidTime', 'Detections'};
            flds = fieldnames(in);
            for m = 1:numel(flds)
                thisFld = flds{m};
                cond = ~any(strcmp(thisFld,validFlds));
                coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:invalidField',['In' codegenInt2Str(port) '.' thisFld]);
            end
            
            % Has Detections
            cond = ~isfield(in,'Detections');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections']);
            
            % Make sure that detections is not empty
            validateattributes(in.Detections,{'struct'},{'nonempty'},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections']);
            cond = numel(in.Detections)<in.NumDetections;
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:tooManyDetections',['In' codegenInt2Str(port) '.Detections(:)'],numel(in.Detections),in.NumDetections);
            
            % Has valid detection fields
            validFlds = {'Time', 'Measurement', 'MeasurementNoise', ...
                'SensorIndex', 'ObjectClassID', 'MeasurementParameters', 'ObjectAttributes'};
            flds = fieldnames(in.Detections);
            for m = 1:numel(flds)
                thisFld = flds{m};
                cond = ~any(strcmp(thisFld,validFlds));
                coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:invalidField',['In' codegenInt2Str(port) '.Detections(:).' thisFld]);
            end
            
            % Check detection fields
            
            % Has Time
            cond = ~isfield(in.Detections,'Time');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections(:).Time']);
            
            value = in.Detections(1).Time;
            validateattributes(value, {'numeric'}, ...
                {'real', 'finite', 'nonsparse', 'nonnegative', 'scalar'},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).Time']);

            % Has Measurement
            cond = ~isfield(in.Detections,'Measurement');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections(:).Measurement']);
            
            value = in.Detections(1).Measurement;
            validateattributes(value, {'numeric'}, ...
                {'real', 'finite', 'nonsparse', 'vector'},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).Measurement']);

            % Has MeasurementNoise
            cond = ~isfield(in.Detections,'MeasurementNoise');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections(:).MeasurementNoise']);
            
            value = in.Detections(1).MeasurementNoise;
            validateattributes(value, {'numeric'}, {'real', ...
                'square', 'nonsparse', 'finite'}, ...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).MeasurementNoise']);

            % Has SensorIndex
            cond = ~isfield(in.Detections,'SensorIndex');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections(:).SensorIndex']);
            
            value = in.Detections(1).SensorIndex;
            validateattributes(value, {'numeric'}, {'real', 'nonnegative', 'nonsparse', 'scalar', 'integer'},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).SensorIndex']);

            % Has ObjectClassID
            cond = ~isfield(in.Detections,'ObjectClassID');
            coder.internal.errorIf(cond,'shared_tracking:DetectionConcatenation:missingField',['In' codegenInt2Str(port) '.Detections(:).ObjectClassID']);
            
            value = in.Detections(1).ObjectClassID;
            validateattributes(value, {'numeric'}, {'real', 'integer', 'nonsparse', 'finite', 'nonnegative', 'scalar'}, ...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).ObjectClassID']);
            
            % Optional: MeasurementParameters
            if isfield(in.Detections,'MeasurementParameters')
                value = in.Detections(1).MeasurementParameters;
                validateattributes(value, {'struct'}, {},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).MeasurementParameters']);
            end
            
            % Optional: ObjectAttributes
            if isfield(in.Detections,'ObjectAttributes')
                value = in.Detections(1).ObjectAttributes;
                validateattributes(value, {'struct'}, {},...
                'DetectionConcatenation',['In' codegenInt2Str(port) '.Detections(:).ObjectAttributes']);
            end
        end
        
        function out = optionalDetFields(obj, in)
            % Removes optional fields from the output Detections struct
            % (MeasurementParameters and ObjectAttributes) when they are
            % not provided in the input detections.
            
            if obj.pHasMeasurementParameters && obj.pHasObjectAttributes
                out = in;
            else
                % Remove ObjectAttributes and MeasurementParameters from
                % from struct
                out = struct('NumDetections', in.NumDetections);
                if isfield(in,'IsValidTime')
                    out.IsValidTime = in.IsValidTime;
                end
                detsIn = in.Detections;
                if coder.target('MATLAB')
                    if ~obj.pHasMeasurementParameters && isfield(detsIn,'MeasurementParameters')
                        detsIn = rmfield(detsIn,'MeasurementParameters');
                    end
                    if ~obj.pHasObjectAttributes && isfield(detsIn,'ObjectAttributes')
                        detsIn = rmfield(detsIn,'ObjectAttributes');
                    end
                    out.Detections = detsIn;
                else
                    detsOut = struct;
                    flds = fieldnames(detsIn);
                    for m = 1:numel(flds)
                        thisFld = flds{m};
                        if ~obj.pHasMeasurementParameters && strcmp(thisFld,'MeasurementParameters')
                            continue
                        end
                        if ~obj.pHasObjectAttributes && strcmp(thisFld,'ObjectAttributes')
                            continue
                        end
                        
                        detsOut.(thisFld) = detsIn(1).(thisFld);
                    end
                    
                    dets = repmat(detsOut,size(detsIn));
                    flds = fieldnames(detsOut);
                    for m = 1:numel(flds)
                        thisFld = flds{m};
                        for n = 1:numel(detsIn)
                            dets(n).(thisFld) = detsIn(n).(thisFld);
                        end
                    end
                    
                    out.Detections = dets;
                end
            end
        end
        
        function out = setScalarStructVals(obj,out,in)
            % Copies the values in the struct in onto the struct out. in and out must
            % be scalar structs and out must have all of the fields specified by in. in
            % can have structs attached to its fields, but these structs must also be
            % scalar and out must have the same structs defined on its fields.
            %
            % It is assumed that for non-struct field values, that field values on out
            % are the same size or larger than the field values on in. If the field
            % values on out are larger, then the first [M, N] elements of the field's
            % value on struct out are set to the field value of struct in, where M and
            % N define the number of rows and columns for the field value on struct in
            % under consideration.
            
            flds = fieldnames(in);
            for m = 1:numel(flds)
                thisFld = flds{m};
                if ~obj.pHasMeasurementParameters && strcmp(thisFld,'MeasurementParameters')
                    continue
                end
                if ~obj.pHasObjectAttributes && strcmp(thisFld,'ObjectAttributes')
                    continue
                end
                thisVal = in.(thisFld);
                if isstruct(thisVal)
                    setVal = setStructVals(obj,out.(thisFld),thisVal);
                else
                    setVal = thisVal;
                end
                ind = getLogicalIndices(setVal, out.(thisFld));
                out.(thisFld)(ind) = setVal;
            end
        end
        
        function out = setStructVals(obj,out,in)
            for m = 1:numel(in)
                out(m) = setScalarStructVals(obj,out(m),in(m));
            end
        end
        
        function [out, hasMeasParams, hasAttribs] = createObjDetStructTemplate(obj,in1,in2,port)
            % Creates a template for an objectDetection structure which includes the
            % combination of all of the fields defined on the objectDetection structs
            % in1 and in2.
            % - The length of the Detections struct array on the returned
            %   objectDetection struct is the sum of the length of the Detection struct
            %   arrays on in1 and in2
            % - The returned objectDetection struct has not been nullified
            
            out.NumDetections = in1.NumDetections;
            if isfield(in1,'IsValidTime')
                out.IsValidTime = in1.IsValidTime;
            end
            
            % Measurement & MeasurementNoise (grow length if needed)
            meas1 = in1.Detections(1).Measurement;
            meas2 = in2.Detections(1).Measurement;
            len = max(length(meas1),length(meas2));
            meas = zeros(len,1,'like',meas1);
            noise = zeros(len,len,'like',in1.Detections(1).MeasurementNoise);
            
            % Optional detection fields
            [measParams, hasMeasParams] = getOptionalDetField(obj,'MeasurementParameters',in1.Detections(1),in2.Detections(1),port);
            [attribs, hasAttribs] = getOptionalDetField(obj,'ObjectAttributes',in1.Detections(1),in2.Detections(1),port);
            
            det0 = struct( ...
                'Time', in1.Detections(1).Time, ...
                'Measurement', meas, ...
                'MeasurementNoise', noise, ...
                'SensorIndex', in1.Detections(1).SensorIndex, ...
                'ObjectClassID', in1.Detections(1).ObjectClassID, ...
                'MeasurementParameters', measParams, ...
                'ObjectAttributes', attribs);
            
            numDets1 = numel(in1.Detections);
            numDets2 = numel(in2.Detections);
            numDets = numDets1+numDets2;
            out.Detections = repmat(det0,numDets,1);
        end

        function out = combineScalarStructs(obj,in1,in2,port)
            % Returns a structure which includes all of the fields defined on the
            % scalar structs in1 and in2.
            
            % Copy struct in1 to out
            % - This ensures that out has not been read or accessed, which means that
            %   we can continue to add fields to it for codegen
            out = struct;
            flds1 = fieldnames(in1);
            flds2 = fieldnames(in2);
            for m = 1:numel(flds1)
                thisFld = flds1{m};
                if any(strcmp(thisFld,flds2)) % Same field on both structs
                    
                    % Check that they are the same type
                    val1 = in1.(thisFld);
                    val2 = in2.(thisFld);
                    
                    dt2 = class(val2);
                    cond = ~isa(val1, dt2);
                    if cond
                        dt1 = class(val1);
                        msg = message('shared_tracking:DetectionConcatenation:incompatibleType',dt2, thisFld, port, dt1);
                        if isBusPropagated(obj)
                            if ~isModelStopped()
                                Simulink.output.error(MException(msg));
                                error(msg);
                            end
                        else
                            error(msg);
                        end
                    end
                    
                    if isstruct(val1) && isstruct(val2)
                        out.(thisFld) = combineStructs(obj,in1.(thisFld),in2.(thisFld),port);
                    else
                        out.(thisFld) = maxArraySizeAndDim(val1,val2);
                    end
                else % Only on first struct
                    out.(thisFld) = in1.(thisFld);
                end
            end
            
            % Add any additional fields that were only on struct in2
            for m = 1:numel(flds2)
                thisFld = flds2{m};
                if ~any(strcmp(thisFld,flds1))
                    out.(thisFld) = in2.(thisFld);
                end
            end
        end
        
        function out = combineStructs(obj,in1,in2,port)
            % Returns an array of structs whose fields have been combined. The number
            % of elements in the returned array is the sum of the elements in in1 and
            % in2.
            out0 = combineScalarStructs(obj,in1(1),in2(1),port);
            n1 = numel(numel(in1));
            n2 = numel(numel(in2));
            out = repmat(out0,n1+n2,1);
        end
        
        function [out, hasFld] = getOptionalDetField(obj,fld,dets1,dets2,port)
            hasFld = true;
            if isfield(dets1,fld) && isfield(dets2,fld) && ...
                    hasFields(dets1.(fld)) && hasFields(dets2.(fld))
                out0 = combineScalarStructs(obj,dets1.(fld)(1),dets2.(fld)(1),port);
                num = max(numel(dets1.(fld)),numel(dets2.(fld)));
                out = repmat(out0,num,1);
                 
            elseif isfield(dets1,fld) && hasFields(dets1.(fld))
                out = dets1.(fld);
            elseif isfield(dets2,fld) && hasFields(dets2.(fld))
                out = dets2.(fld);
            else
                hasFld = false;
                out = struct;
            end
        end
    end
    
    methods(Static)
        function busName = createBus(varargin)
            % driving.internal.DetectionConcatenation.createBus Create Simulink output bus
            %   The concatenated detection reports generated by the
            %   DetectionConcatenation MATLAB System object are output on a bus
            %   in Simulink. This function creates the Simulink bus object in
            %   the MATLAB base workspace. This bus object defines the datatype
            %   for the detections output port.
            %
            %   busName = driving.internal.DetectionConcatenation.createBus.createBus(blkPath)
            %   creates a bus from the MATLAB System block specified by the
            %   full block path, blkPath. The bus is created in the MATLAB base
            %   workspace and the name of the created bus is returned as
            %   busName. The block's "Source of output bus name" parameter must
            %   be set to 'Property'. The name of the bus is specified on the
            %   block's dialog.
            %
            %   busName = driving.internal.DetectionConcatenation.createBus(blkPath, busName)
            %   creates a bus named busName from the MATLAB System block,
            %   blkPath.
            
            busName = createBus@matlabshared.tracking.internal.SimulinkBusUtilities(varargin{:});
        end
    end
    
    methods(Static, Access=protected)
        function groups = getPropertyGroupsImpl
			pList = matlabshared.tracking.internal.getPropertyDescription(...
                'shared_tracking', 'DetectionConcatenation', 'NumInputs');
            pSection = matlab.system.display.Section('PropertyList',{pList});

            slBusSection = getPropertyGroupsImpl@matlabshared.tracking.internal.SimulinkBusUtilities;
            
            groups = [pSection, slBusSection];
        end
    
        function header = getHeaderImpl
            header = matlab.system.display.Header(...
                'Title',getString(message('shared_tracking:blocks:DetectionConcatenationTitle')),...
                'Text',getString(message('shared_tracking:blocks:DetectionConcatenationDesc')));
        end
    end
end

function indOut = getLogicalIndices(x, y)
% Returns the logical indices to each element in x for in the larger array
% y, where x is a smaller array which is wholly contained by (a subset of)
% y. This enables on the first size(x) values in y to be set to the values
% in x, for example:
%   ind = getLogicalIndices(x, y);
%   y(ind) = x;

szy = size(y);
szx = size(x);
indOut = false(szy);
indSet = cell(ndims(x),1);
for i = 1:ndims(x)
    indSet{i} = 1:szx(i);
end
indOut(indSet{:}) = true;

end

function out = maxArraySizeAndDim(in1, in2)
% Returns an array of which is the maximum of the sizes of in1 and in2

[sz, scalarVal] = getMaxSizeAndDim(in1,in2);
out = repmat(scalarVal,sz);
end

function [sz,scalarVal] = getMaxSizeAndDim(in1, in2)
% Return the maximum size and dimension between inputs in1 and in2, sz, and
% a scalar data value, scalarVal, which is representative of the data stored
% in in1 and in2.
n1 = ndims(in1);
n2 = ndims(in2);
if n2>n1
    sz1 = size(in2);
    sz2 = [size(in1) ones(1,n2-n1)];
else
    sz1 = size(in1);
    sz2 = [size(in2) ones(1,n1-n2)];
end
sz = max([sz1;sz2],[],1);

if nargout<2
    return
end
if ~isempty(in1)
    scalarVal = in1(1);
elseif ~isempty(in2)
    scalarVal = in2(1);
else
    scalarVal = in1;
end
end

function out = nullify(in)
% Zeros out all of the fields on the struct in.
% - Cannot zero out fields that are set to enumerated types, since there is
%   no possible way to cast a zero to an enumerated type. Fields set to
%   enumerated types are not touched
out = in;
flds = fieldnames(in);
for m = 1:numel(flds)
    thisFld = flds{m};
    for n = 1:numel(in)
        thisVal = in(n).(thisFld);
        if isstruct(thisVal)
            nullVal = nullify(thisVal);
        else
            if isenum(thisVal)
                nullVal = thisVal; % Can't nullify enums
            else
                nullVal = zeros(size(thisVal),'like',thisVal);
            end
        end
        out(n).(thisFld) = nullVal;
    end
end
end

function s1 = codegenInt2Str(n)
is_pos = n > 0; % save sign
n = abs(n); % work with positive
numDigits = floor(log10(n))+1;
numChars = numDigits+not(is_pos);
s = repmat('0',[1,numChars]);
i = 0;
while n > 0
    i = i + 1;
    c = mod( n, 10 ); % get current character
    s(1,numChars+1-i) = uint8(c+'0'); % add the character
    n = ( n -  c ) / 10; % "chop" it off and continue
end
if ~is_pos
    s(1) = '-'; % add the sign
end
s1 = s;
end

function flag = hasFields(st)
flag = numel(fieldnames(st))>0;
end

function flag = isModelStopped()
% Returns true if Simulink is currently stopped
flag = strcmp(get_param(bdroot,'SimulationStatus'),'stopped');
end

function ZERO = integerZero()
if coder.target('MATLAB')
    ZERO = 0;
else
    ZERO = coder.internal.indexInt(0);
end
end

function ONE = integerOne()
if coder.target('MATLAB')
    ONE = 1;
else
    ONE = coder.internal.indexInt(1);
end
end