classdef Library < handle & bioinfo.pipeline.internal.VersionMixin

    properties
        Name(1,1) string {mustBeValidVariableName} = "Untitled"
        Description(1,1) string 
    end

    properties (Access = private)
        BlocksDict(1,1) dictionary
    end

    properties (Dependent)
        BlockNames
        Blocks
    end

    properties (Hidden)
        UUID(1,1) string = matlab.lang.internal.uuid;
    end

    methods
        function lib = Library(sourcePipeline, NVPArgs)
            arguments
                sourcePipeline bioinfo.pipeline.Pipeline {mustBeScalarOrEmpty} = bioinfo.pipeline.Pipeline.empty
                NVPArgs.Name(1,1) string {mustBeValidVariableName} = "Untitled"
                NVPArgs.Description(1,1) string = "";
            end

            lib.Name = NVPArgs.Name;
            lib.Description = NVPArgs.Description;

            if isempty(sourcePipeline)
                lib.BlocksDict = dictionary(string.empty, bioinfo.pipeline.Block.empty);
            elseif isa(sourcePipeline, "bioinfo.pipeline.Pipeline")
                % Construct from Pipeline blocks and names
                lib.BlocksDict = dictionary(sourcePipeline.BlockNames, sourcePipeline.Blocks);
            end
        end

        function blks = get.Blocks(lib)
            blks = lib.BlocksDict.values();
        end

        function blkNames = get.BlockNames(lib)
            blkNames = lib.BlocksDict.keys();
        end

        function addBlock(lib, block, blkName)
            arguments
                lib(1,1) bioinfo.pipeline.library.Library
                block(:,1) bioinfo.pipeline.Block
                blkName(:,1) string {mustBeValidVariableName}
            end

            if numel(block) ~= numel(blkName)
                error(message("bioinfo:Library:InvalidNumNames"));
            end

            if numel(blkName) ~= numel(unique(blkName))
                error(message("bioinfo:Library:NonUniqueBlockNames"));
            end

            conflictingNames = intersect(lib.BlockNames, blkName);
            if ~isempty(conflictingNames)
                error(message("bioinfo:Library:BlockNameAlreadyExists", strjoin(conflictingNames, ', ')));
            end


            lib.BlocksDict(blkName) = block;
        end

        function removeBlock(lib, blkName)
            arguments
                lib(1,1) bioinfo.pipeline.library.Library
                blkName(:,1) string
            end


            blkNameIsKey = isKey(lib.BlocksDict, blkName);
            if ~all(blkNameIsKey)
                invalidNames = blkName(~blkNameIsKey);
                error(message("bioinfo:Library:BlockNameNotFound", strjoin(invalidNames, ',')));
            end

            lib.BlocksDict(blkName) = [];
        end

        function renameBlock(lib, oldBlkName, newBlkName)
            arguments
                lib(1,1) bioinfo.pipeline.library.Library
                oldBlkName(:,1) string
                newBlkName(:,1) string {mustBeValidVariableName}
            end

            % Number of names must match
            if numel(oldBlkName) ~= numel(newBlkName)
                error(message("bioinfo:Library:renameBlock:InvalidNumNames"));
            end

            % Both block name arguments must be unique
            if numel(oldBlkName) ~= numel(unique(oldBlkName)) || numel(newBlkName) ~= numel(unique(newBlkName))
                error(message("bioinfo:Library:renameBlock:NonUniqueBlockNames"));
            end

            % Old block names must exist in the library
            oldBlkNameIsKey = isKey(lib.BlocksDict, oldBlkName);
            if ~all(oldBlkNameIsKey)
                invalidNames = oldBlkName(~oldBlkNameIsKey);
                error(message("bioinfo:Library:renameBlock:BlockNameNotFound", strjoin(invalidNames, ',')));
            end

            % New block names must not conflict with existing names after removing old
            % names.
            conflictingNames = intersect(setdiff(lib.BlockNames, oldBlkName), newBlkName);
            if ~isempty(conflictingNames)
                error(message("bioinfo:Library:renameBlock:BlockNameAlreadyExists", strjoin(conflictingNames, ', ')));
            end

            % Finally, rename the blocks by removing and re-inserting with new names
            renamedBlocks = lib.BlocksDict(oldBlkName);
            lib.BlocksDict(oldBlkName) = [];
            lib.BlocksDict(newBlkName) = renamedBlocks;
        end

        function blks = findBlock(lib, blkName)
            arguments
                lib(1,1) bioinfo.pipeline.library.Library
                blkName string
            end

            blksSize = size(blkName);

            blkNameIsKey = isKey(lib.BlocksDict, blkName);
            if ~all(blkNameIsKey)
                invalidNames = blkName(~blkNameIsKey);
                error(message("bioinfo:Library:BlockNameNotFound", strjoin(invalidNames, ',')));
            end

            blks = reshape(lib.BlocksDict(blkName(:)), blksSize);
        end
    end

end
% Copyright 2023 The MathWorks, Inc.

