classdef (Hidden) TrackConcatenation < ...
        matlabshared.tracking.internal.SimulinkBusUtilities
    
    % This class is for internal use only. It may be removed in the future.
    
    %TrackConcatenation  Concatenate Tracks from multiple sources
    %   Combines object tracks lists from multiple sources into a single
    %   output object track list. 
    %
    %   TrackConcatenation properties:
    %     NumInputs   - Number of input sources to combine
    %
    %   TrackConcatenation methods:
    %     step        - Combines object tracks lists
    %     release     - Allow property value and input characteristics changes
    %     clone       - Create track concatenation object with same
    %                   property values
    %     isLocked    - Locked status (logical)
    %     reset       - Reset states of track concatenation object
    %     createBus   - Create Simulink output bus
    
    % Copyright 2020-2022 The MathWorks, Inc.
    
    %#codegen
    
    properties(Nontunable)
        %NumInputs Number of input sources to combine
        %   Specify the number of input sources to combine into a single
        %   output track 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. Sub-buses will be created by appending the name of the
        %   structure field associated with the sub-bus to the base bus
        %   name. For example: <pBusPrefix>#<fieldName>
        pBusPrefix = 'BusTrackConcatenation'
    end
    
    properties(Access=private)
        pOutTemp
    end
    
    properties(Nontunable,Access=private)
        pHasStateParameters
        pHasObjectAttributes
        pHasIsValidTime
    end
    
    methods
        function obj = TrackConcatenation(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;
            hasStateParams = isfield(tmp.Tracks,'StateParameters');
            hasAttribs = isfield(tmp.Tracks,'ObjectAttributes');
            hasIsValidTime = isfield(tmp,'IsValidTime');
            for m = 2:obj.NumInputs
                busIn = propagatedInputBus(obj,m);
                if ~isempty(busIn)
                    st = matlabshared.tracking.internal.SimulinkBusUtilities.bus2struct(busIn);
                    checkValidInputBus(obj,st,m);
                    [tmp, hasStateParams0, hasAttribs0, hasIsValidTime0] = createObjTrsStructTemplate(obj,tmp,st,m);
                    hasStateParams = hasStateParams || hasStateParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                    hasIsValidTime = hasIsValidTime || hasIsValidTime0;
                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.pHasStateParameters = hasStateParams;
            obj.pHasObjectAttributes = hasAttribs;
            obj.pHasIsValidTime = hasIsValidTime;
            
            out = optionalTrsFields(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 checkUniqueSourceIndex(obj,varargin)
            % Check for unique SourceIndex on all inputs. If not all inputs
            % have a unique SourceIndex, then an error is thrown.
            
            % Find all source indices for input tracks
            sourceIndices = zeros(obj.NumInputs,1,'uint32');
            for m = 1:obj.NumInputs
                in = varargin{m};
                
                % Has valid tracks?
                if in.NumTracks>0
                    % Yes, grab source index from the first
                    sourceIndices(m) = in.Tracks(1).SourceIndex;
                end
            end
            
            validIndices = sourceIndices((sourceIndices>uint32(0)));
            uniqueIndices = unique(validIndices);
            
            % Were any indices repeated?
            if numel(uniqueIndices)==numel(validIndices)
                return % no
            end
            
            % Find the inputs without a unique source index
            notUnique = false(obj.NumInputs,1);
            for m = 1:numel(uniqueIndices)
                thisIndex = uniqueIndices(m);
                iFnd = sourceIndices==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:TrackConcatenation:sourceIDsNotUnique',str);
        end
        
        function s = saveObjectImpl(obj)
            
            s = saveObjectImpl@matlabshared.tracking.internal.SimulinkBusUtilities(obj);
            
            if isLocked(obj)
                s.pHasStateParameters = obj.pHasStateParameters;
                s.pHasObjectAttributes = obj.pHasObjectAttributes;
                s.pHasIsValidTime = obj.pHasIsValidTime;
                s.pOutTemp = obj.pOutTemp;
            end
        end
        
        function loadObjectImpl(obj,s,wasLocked)
            
            if wasLocked
                obj.pHasStateParameters = s.pHasStateParameters;
                s = rmfield(s,'pHasStateParameters');
                obj.pHasObjectAttributes = s.pHasObjectAttributes;
                s = rmfield(s,'pHasObjectAttributes');
                obj.pOutTemp = s.pOutTemp;
                s = rmfield(s,'pOutTemp');
                if isfield(s,'pHasIsValidTime')
                    obj.pHasIsValidTime = s.pHasIsValidTime;
                    s = rmfield(s,'pHasIsValidTime');
                end
            end
            
            loadObjectImpl@matlabshared.tracking.internal.SimulinkBusUtilities(obj,s,wasLocked);
        end
     
        function setupImpl(obj,varargin)
            % Create the objectTrack struct template to populate
            if coder.target('MATLAB')
                in = varargin{1};
                tmp = in;
                hasStateParams = isfield(tmp.Tracks,'StateParameters');
                hasAttribs = isfield(tmp.Tracks,'ObjectAttributes');
                hasIsValidTime = isfield(tmp,'IsValidTime');
                for m = 2:obj.NumInputs
                    in = varargin{m};
                    [tmp, hasStateParams0, hasAttribs0, hasIsValidTime0] = createObjTrsStructTemplate(obj,tmp,in,m);
                    hasStateParams = hasStateParams || hasStateParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                    hasIsValidTime = hasIsValidTime || hasIsValidTime0;
                end
            else
                in = varargin{1};
                tmpc = cell(obj.NumInputs,1);
                tmpc{1} = in;
                hasStateParams = isfield(in.Tracks,'StateParameters');
                hasAttribs = isfield(in.Tracks,'ObjectAttributes');
                hasIsValidTime = isfield(in,'IsValidTime');
                for m = 2:obj.NumInputs
                    in = varargin{m};
                    [tmpc{m}, hasStateParams0, hasAttribs0, hasIsValidTime0] = createObjTrsStructTemplate(obj,tmpc{m-1},in,m);
                    hasStateParams = hasStateParams || hasStateParams0;
                    hasAttribs = hasAttribs || hasAttribs0;
                    hasIsValidTime = hasIsValidTime || hasIsValidTime0;
                end
                tmp = tmpc{end};
            end
            
            obj.pHasStateParameters = hasStateParams;
            obj.pHasObjectAttributes = hasAttribs;
            obj.pHasIsValidTime = hasIsValidTime;
            
            out = optionalTrsFields(obj,tmp);
            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)
            % trks = step(h, trks, trks1, trks2, ..., trksN) combines N
            % objectTrack buses into a single objectTrack bus.
            %
            % The number of tracks in the output bus is sum of all
            % 'NumTracks' in input buses.  
            %
            % The length of output tracks State and TrackLogicState
            % property is the maximum length, L, taken from the N
            % objectTrack inputs. The StateCovariance is an L-by-L matrix.
            % Where tracks are zero padded when needed.
            %
            % The scalar structs in the ObjectAttributes and
            % StateParameters of track contains all of the fields
            % defined across all of the N objectTracks input buses.
            %
            
            checkUniqueSourceIndex(obj,varargin{:});
            
            out = obj.pOutTemp;
            
            % Populate the objectTrack struct with the input values
            idx = 1;
            isValidTime = false;
            numTrs = zeros(1,'like',varargin{1}.NumTracks);
            for m = 1:obj.NumInputs
                in = varargin{m};
                numTrsIn = in.NumTracks;
                if isfield(in,'IsValidTime')
                    isValidTime = isValidTime || in.IsValidTime;
                end
                for n = 1:numTrsIn
                    out.Tracks(idx) = setScalarStructVals(obj,out.Tracks(idx), in.Tracks(n));
                    idx = idx+1;
                end
                numTrs = numTrs+numTrsIn;
            end
            out.NumTracks = numTrs;
            if obj.pHasIsValidTime
                out.IsValidTime = isValidTime;
            end
        end
        
        function str = getIconImpl(~)
            str = sprintf('Track\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'},...
                'TrackConcatenation',['In' codegenInt2Str(port)]);
            
            % Check for invalid fields
            validFlds = {'NumTracks', 'IsValidTime', 'Tracks'};
            flds = fieldnames(in);
            for m = 1:numel(flds)
                thisFld = flds{m};
                coder.internal.assert(any(strcmp(thisFld,validFlds)),'shared_tracking:TrackConcatenation:invalidField',['In' codegenInt2Str(port) '.' thisFld]);
            end
           
            % Make sure that Tracks is not empty
            validateattributes(in.Tracks,{'struct'},{'nonempty'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks']);
            coder.internal.assert(in.NumTracks <= numel(in.Tracks),'shared_tracking:TrackConcatenation:tooManyTracks',['In' codegenInt2Str(port) '.Tracks(:)'],numel(in.Tracks),in.NumTracks);
            
            % NumTracks is a nonnegative integer scalar
            validateattributes(in.NumTracks,{'numeric'},{'scalar','nonnegative','integer','real','finite'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.NumTracks']);
            
            % Has valid track fields
            validFlds = {'TrackID', 'BranchID', 'SourceIndex', 'UpdateTime', 'Age', ....
                'State', 'StateCovariance', 'StateParameters', 'ObjectClassID', ...
                'TrackLogic', 'TrackLogicState', 'IsConfirmed', 'IsCoasted', ...
                'IsSelfReported', 'ObjectAttributes','StateSize','LogicSize'};
            flds = fieldnames(in.Tracks);
            for m = 1:numel(flds)
                thisFld = flds{m};
                if ~any(strcmp(thisFld,{'Extent','MeasurementRate'}))
                    coder.internal.assert(any(strcmp(thisFld,validFlds)),'shared_tracking:TrackConcatenation:invalidField',['In' codegenInt2Str(port) '.Tracks(:).' thisFld]);
                end
            end
            
            % Check track fields
            
            % Has TrackID
            coder.internal.assert(isfield(in.Tracks,'TrackID'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).TrackID']);
            
            value = in.Tracks(1).TrackID;
            validateattributes(value, {'numeric'}, ...
                {'real','nonnegative','integer', 'nonsparse','scalar'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).TrackID']);
            
            % Has UpdateTime
            coder.internal.assert(isfield(in.Tracks,'UpdateTime'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).UpdateTime']);
            
            value = in.Tracks(1).UpdateTime;
            validateattributes(value, {'numeric'}, ...
                {'real','nonnegative', 'nonsparse','finite','scalar'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).UpdateTime']);
            
            % Has Age
            coder.internal.assert(isfield(in.Tracks,'Age'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).Age']);
            
            value = in.Tracks(1).Age;
            validateattributes(value, {'numeric'}, ...
                {'real','nonnegative','integer', 'nonsparse','scalar'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).Age']);
            
            % Has State
            coder.internal.assert(isfield(in.Tracks,'State'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).State']);
            
            value = in.Tracks(1).State;
            validateattributes(value, {'single','double'}, ...
                {'real','finite','nonsparse','vector'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).State']);
            
            % Has StateCovariance
            coder.internal.assert(isfield(in.Tracks,'StateCovariance'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).StateCovariance']);
            
            value = in.Tracks(1).StateCovariance;
            validateattributes(value, {'single','double'}, ...
                {'real','finite','nonsparse','2d','square'},...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).StateCovariance']);
            sz = numel(in.Tracks(1).State);
            % Check dims
            matlabshared.tracking.internal.validateDataDims...
                ('StateCovariance', value, [sz, sz]);
            matlabshared.tracking.internal.isSymmetricPositiveSemiDefinite...
                ('StateCovariance', value);
            
            % Optional: Has StateParameters
            if isfield(in.Tracks,'StateParameters')
                value = in.Tracks(1).StateParameters;
                validateattributes(value, {'struct'},{},...
                    'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).StateParameters']);
            end
            
            % Has ObjectClassID
            coder.internal.assert(isfield(in.Tracks,'ObjectClassID'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).ObjectClassID']);
            
            value = in.Tracks(1).ObjectClassID;
            validateattributes(value, {'numeric'},{'real','nonnegative','integer',...
                'nonsparse','scalar'}, ...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).ObjectClassID']);

            % Has IsConfirmed
            coder.internal.assert(isfield(in.Tracks,'IsConfirmed'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).IsConfirmed']);
            
            value = in.Tracks(1).IsConfirmed;
            validateattributes(value, {'logical','numeric'},{'real','nonsparse','scalar','binary'}, ...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).IsConfirmed']);
            
            % Has IsCoasted
            coder.internal.assert(isfield(in.Tracks,'IsCoasted'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).IsCoasted']);
            
            value = in.Tracks(1).IsCoasted;
            validateattributes(value, {'logical','numeric'},{'real','nonsparse','scalar','binary'}, ...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).IsCoasted']);
            
            % Has IsSelfReported
            coder.internal.assert(isfield(in.Tracks,'IsSelfReported'),'shared_tracking:TrackConcatenation:missingField',['In' codegenInt2Str(port) '.Tracks(:).IsSelfReported']);
            
            value = in.Tracks(1).IsSelfReported;
            validateattributes(value, {'logical','numeric'},{'real','nonsparse','scalar','binary'}, ...
                'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).IsSelfReported']);
       
            
            % Optional: Has ObjectAttributes
            if isfield(in.Tracks,'ObjectAttributes')
                value = in.Tracks(1).ObjectAttributes;
                validateattributes(value, {'struct','cell'},{}, ...
                    'TrackConcatenation',['In' codegenInt2Str(port) '.Tracks(:).ObjectAttributes']);
            end
            
            if isfield(in.Tracks,'StateSize')
                value = in.Tracks(1).StateSize;
                validateattributes(value,{'numeric'},{'real','nonnegative','integer',...
                'nonsparse','scalar'},'TrackConcatenation', ...
                ['In' codegenInt2Str(port) '.Tracks(:).StateSize']);
            end
            
            if isfield(in.Tracks,'LogicSize')
                value = in.Tracks(1).LogicSize;
                validateattributes(value,{'numeric'},{'real','nonnegative','integer',...
                'nonsparse','scalar'},'TrackConcatenation', ...
                ['In' codegenInt2Str(port) '.Tracks(:).LogicSize']);
            end
        end
         
        function out = optionalTrsFields(obj, in)
            % Removes optional fields from the output Tracks struct
            % (StateParameters and ObjectAttributes) when they are
            % not provided in the input Tracks.
            
            if obj.pHasStateParameters && obj.pHasObjectAttributes
                out = in;
            else
                % Remove ObjectAttributes and StateParameters from
                % from struct

                % Copy over all fields except Tracks sub-bus
                out = struct();
                flds = fieldnames(in);
                for iFld = 1:numel(flds)
                    thisFld = flds{iFld};
                    if ~strcmp(thisFld,'Tracks')
                        out.(thisFld) = in.(thisFld);
                    end
                end
                
                % Add the tracks sub-bus
                trsIn = in.Tracks;
                if coder.target('MATLAB')
                    if ~obj.pHasStateParameters && isfield(trsIn,'StateParameters')
                        trsIn = rmfield(trsIn,'StateParameters');
                    end
                    if ~obj.pHasObjectAttributes && isfield(trsIn,'ObjectAttributes')
                        trsIn = rmfield(trsIn,'ObjectAttributes');
                    end
                    out.Tracks = trsIn;
                else
                    trsOut = struct;
                    flds = fieldnames(trsIn);
                    for m = 1:numel(flds)
                        thisFld = flds{m};
                        if ~obj.pHasStateParameters && strcmp(thisFld,'StateParameters')
                            continue
                        end
                        if ~obj.pHasObjectAttributes && strcmp(thisFld,'ObjectAttributes')
                            continue
                        end
                        trsOut.(thisFld) = trsIn(1).(thisFld);
                    end
                    trks = repmat(trsOut,size(trsIn));
                    flds = fieldnames(trsOut);
                    for m = 1:numel(flds)
                        thisFld = flds{m};
                        for n = 1:numel(trsIn)
                            trks(n).(thisFld) = trsIn(n).(thisFld);
                        end
                    end
                    out.Tracks = trks;
                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);
            num = numel(flds);
            for m = 1:num
                thisFld = flds{m};
                if ~obj.pHasStateParameters && strcmp(thisFld,'StateParameters')
                    continue
                end
                if ~obj.pHasObjectAttributes && strcmp(thisFld,'ObjectAttributes')
                    continue
                end
                if any(strcmp(thisFld,{'Extent','MeasurementRate'}))
                    continue
                end
                thisVal = in.(thisFld);
                if isstruct(thisVal)
                    setVal = setStructVals(obj,out.(thisFld),thisVal);
                else
                    if strcmpi(thisFld,'TrackLogicState')
                        if  isequal(numel(out.(thisFld)), numel(thisVal))
                            % set only when the trackLogicState size are
                            % same.
                            setVal = thisVal;
                        else
                            % Keep the default trackLogicState 
                            setVal =  out.(thisFld);
                        end
                    else
                        setVal = thisVal;
                    end
                end
                ind = getLogicalIndices(setVal, out.(thisFld));
                out.(thisFld)(ind) = setVal;
               
                % StateSize and LogicSize are appended fields use to keep
                % track of the valid number of values in the corresponding
                % State and TrackLogicState fields.
                
                % Set StateSize to the size of the state. 1:StateSize are
                % valid values, remaining values are unused.
                if ~any(strcmpi('StateSize',flds))
                    if isfield(out,'StateSize') && ...
                            strcmpi(thisFld,'State')
                        out.StateSize(:) = numel(setVal);
                    end
                end
                % Set LogicSize to the size of the track logic state.
                % 1:LogicSize are valid values, remaining values are
                % unused.
                if ~any(strcmpi('LogicSize',flds))
                    if isfield(out,'LogicSize') && ...
                            strcmpi(thisFld,'TrackLogicState')
                        out.LogicSize(:) = numel(setVal);
                    end
                end
            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, hasStateParams, hasAttribs, hasIsValidTime] = createObjTrsStructTemplate(obj,in1,in2,port)
            % Creates a template for an objectTrack structure which includes the
            % combination of all of the fields defined on the objectTrack structs
            % in1 and in2.
            % - The length of the Tracks struct array on the returned
            %   objectTrack struct is the sum of the length of the Track struct
            %   arrays on in1 and in2
            % - The returned objectTrack struct has not been nullified
            
            out.NumTracks = in1.NumTracks;

            hasIsValidTime = false;
            if isfield(in1,'IsValidTime')
                out.IsValidTime = in1.IsValidTime;
                hasIsValidTime = true;
            elseif isfield(in2,'IsValidTime')
                out.IsValidTime = in2.IsValidTime;
                hasIsValidTime = true;
            end
            
            % State & StateCovariance (grow length if needed)
            state1 = in1.Tracks(1).State;
            state2 = in2.Tracks(1).State;
            len = max(numel(state1),numel(state2));
            state = zeros(len,1,'like',state1);
            stateCov = zeros(len,len,'like',state1);
              
            if isfield(in1.Tracks,'TrackLogic') && ...
                    isfield(in1.Tracks,'TrackLogicState')
            % when track logic and track logic state is present in the
            % input struct.
               stSz1 = in1.Tracks(1).TrackLogicState;
               trsLogic = in1.Tracks(1).TrackLogic;
            else
             % if these are not present by default we are considering the
             % track logic as trackLogicType(1) 'History' and track logic
             % state to be false(1).
                 stSz1 = false(1);
                trsLogic = trackLogicType(1);
            end
            % Only get the track logic state size from input track in2,
            % track logic is already set from the in1.
            if isfield(in2.Tracks,'TrackLogic') && ...
                    isfield(in2.Tracks,'TrackLogicState')
                stSz2 = in2.Tracks(1).TrackLogicState;
            else
                stSz2 = false(1);
            end
            if (islogical(stSz1) && islogical(stSz2))
                % TrackLogicState (grow length if needed)
                len = max(numel(stSz1),numel(stSz2));
                trsLogicState = false(1,len,'like',stSz1);
            else
                trsLogicState = stSz1;
            end
            
            [stateParams, hasStateParams] = getOptionalTrsField(obj,'StateParameters',in1.Tracks(1),in2.Tracks(1),port);
            [attribs, hasAttribs] = getOptionalTrsField(obj,'ObjectAttributes',in1.Tracks(1),in2.Tracks(1),port);
            
            % StateParameters and ObjectAttributes provide max struct with
            % common values in output structure
            len = 1;
            if isfield(in1.Tracks,'StateParameters')
                len = max(len,numel(in1.Tracks(1).StateParameters));
            end
            if isfield(in2.Tracks,'StateParameters')
                len = max(len,numel(in2.Tracks(1).StateParameters));
            end
            stPram = repmat(stateParams,len,1);
            
            len = 1;
            if isfield(in1.Tracks,'ObjectAttributes')
                len = max(len,numel(in1.Tracks(1).ObjectAttributes));
            end
            if isfield(in2.Tracks,'ObjectAttributes')
                len = max(len,numel(in2.Tracks(1).ObjectAttributes));
            end
            attributes = repmat(attribs,len,1);
            if isfield(in1.Tracks(1),'BranchID')
                branchID = in1.Tracks(1).BranchID;
            elseif isfield(in2.Tracks(1),'BranchID')
                branchID = in2.Tracks(1).BranchID;
            else
                branchID = zeros(1,'like',in1.Tracks(1).TrackID);
            end
            trs0 = struct(...
                'TrackID',          in1.Tracks(1).TrackID,...
                'BranchID',         branchID,...
                'SourceIndex',      in1.Tracks(1).SourceIndex,...
                'UpdateTime',       in1.Tracks(1).UpdateTime,...
                'Age',              in1.Tracks(1).Age,...
                'State',            state,...
                'StateCovariance',  stateCov,...
                'StateParameters',  stPram,...
                'ObjectClassID',    in1.Tracks(1).ObjectClassID,...
                'TrackLogic',       trsLogic,...
                'TrackLogicState',  trsLogicState,...
                'IsConfirmed',      in1.Tracks(1).IsConfirmed,...
                'IsCoasted',        in1.Tracks(1).IsCoasted,...
                'IsSelfReported',   in1.Tracks(1).IsSelfReported,...
                'ObjectAttributes', attributes, ...
                'StateSize', zeros(1,1,'like',in1.Tracks(1).Age), ...
                'LogicSize', zeros(1,1,'like',in1.Tracks(1).Age));
            
            numTrs1 = numel(in1.Tracks);
            numTrs2 = numel(in2.Tracks);
            numTrs = numTrs1+numTrs2;
            out.Tracks = repmat(trs0,numTrs,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:TrackConcatenation: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] = getOptionalTrsField(obj,fld,trs1,trs2,port)
            hasFld = true;
            if isfield(trs1,fld) && isfield(trs2,fld) && ...
                    hasFields(trs1.(fld)) && hasFields(trs2.(fld))
                out = combineScalarStructs(obj,trs1.(fld),trs2.(fld),port);
            elseif isfield(trs1,fld) && hasFields(trs1.(fld))
                out = trs1.(fld);
            elseif isfield(trs2,fld) && hasFields(trs2.(fld))
                out = trs2.(fld);
            else
                hasFld = false;
                out = struct;
            end
        end
    end
    
    methods(Static, Access=protected)
        function groups = getPropertyGroupsImpl
            pList = matlabshared.tracking.internal.getPropertyDescription(...
                'shared_tracking', 'TrackConcatenation', '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','shared_tracking:blocks:trackConcatTitle',...
                'Text','shared_tracking:blocks:trackConcatDesc');
        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);

sz = getMinSize(x, y);
ind = reshape(tagIndicies(zeros(szy), sz),szy);

indOut = false(szy);
numOut = prod(szy);
indOut(1:numOut) = indOut(1:numOut) | ind(1:numOut)==numel(szy);
end

function z = tagIndicies(x, sz, depth)
% Tags a subset, sz, of active indices in the array x by incrementing
% their values by 1. This is a helper function for getLogicalIndices
if nargin<3
    depth = 1;
end
if depth~=ndims(x)
    y = tagIndicies(shiftdim(x,1),circshift(sz,-1),depth+1);
else
    y = x;
end
y(1:sz(1),:) = y(1:sz(1),:)+1;
if depth>1
    z = shiftdim(y,ndims(x)-1);
else
    z = y;
end
end

function [sz,scalarVal] = getMinSize(in1, in2)
% Return the minimum size and maximum dimension between inputs in1 and in2,
% sz. Returns a scalar value that is representative of the data stored in
% in1 and in2, scalarVal.

maxDims = max(ndims(in1),ndims(in2));
minDims = min(ndims(in1),ndims(in2));
sz = inf(1,maxDims);

sz1 = size(in1);
sz(1:minDims) = min(sz(1:minDims),sz1(1:minDims));

sz2 = size(in2);
sz(1:minDims) = min(sz(1:minDims),sz2(1:minDims));

sz(isinf(sz)) = 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 = 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

% LocalWords:  trks nonsparse sz
