% CHAIN2 Input-Output connects an ordered set of system by given input and output names. Other input and output names
% will occur in order of appearance and system position.
%
% SPDX-FileCopyrightText: 2023 Deutsches Elektronen-Synchrotron DESY, MSK, LbSync <lbsync-expert@desy.de>
% SPDX-License-Identifier: GPL-3.0-or-later
%
% Author: Maximilian Schütte <maximilian.schuette@desy.de>

function sys = chain2(sys, opts)
    arguments (Repeating)
        sys
    end
    arguments
        opts.InputName string = "r"
        opts.OutputName string = "y"
        opts.AnalysisPoints (1,:) string = ""
    end

    %#ok<*AGROW>

    % collect input and output names
    in_names = cellfun(@(x) string(x.InputName), sys, UniformOutput=false); in_names = vertcat(in_names{:});
    in_names = unique(extractBefore(in_names + "(", "("), "stable");
    out_names = cellfun(@(x) string(x.OutputName), sys, UniformOutput=false); out_names = vertcat(out_names{:});
    out_names = unique(extractBefore(out_names + "(", "("), "stable");

    % prepare usage of connect function
    idx_in_cnt = zeros(size(in_names));
    idx_out_cnt = zeros(size(out_names));
    for i = 1:numel(sys)
        idx_in = arrayfun(@(x) startsWith(sys{i}.InputName, x), in_names, UniformOutput=false); % output system input indicies
        idx_out = arrayfun(@(x) startsWith(sys{i}.OutputName, x), out_names, UniformOutput=false); % output system output indicies

        for j = 1:numel(in_names)
            sys{i}.InputName(idx_in{j}) = compose(char(in_names(j) + "(%d)"), idx_in_cnt(j) + (1:sum(idx_in{j})));
            idx_in_cnt(j) = idx_in_cnt(j) + sum(idx_in{j});
        end

        if i > 1
            sys{i-1}.OutputName(startsWith(sys{i-1}.OutputName, opts.OutputName)) = ...
                sys{i}.InputName(idx_in{in_names == opts.InputName});
        end

        for j = 1:numel(out_names)
            sys{i}.OutputName(idx_out{j}) = compose(char(out_names(j) + "(%d)"), idx_out_cnt(j) + (1:sum(idx_out{j})));
            idx_out_cnt(j) = idx_out_cnt(j) + sum(idx_out{j});
        end
    end

    if opts.AnalysisPoints == ""
        sys = connect(sys{:}, ...
        [sys{1}.InputName(startsWith(sys{1}.InputName, opts.InputName)); in_names(in_names ~= opts.InputName)], ...
        [sys{end}.OutputName(startsWith(sys{end}.OutputName, opts.OutputName)); out_names(out_names ~= opts.OutputName)]);
    else
        sys = connect(sys{:}, ...
        [sys{1}.InputName(startsWith(sys{1}.InputName, opts.InputName)); in_names(in_names ~= opts.InputName)], ...
        [sys{end}.OutputName(startsWith(sys{end}.OutputName, opts.OutputName)); out_names(out_names ~= opts.OutputName)], ...
        opts.AnalysisPoints);
    end

    idx_out = startsWith(sys.OutputName, opts.OutputName);
    sys.OutputName(idx_out) = compose(char(opts.OutputName + "(%d)"), 1:sum(idx_out));
end
