%% Import System Composer Architecture using Model Builder.
% This example shows how to import architecture specifications into System Composer using the systemcomposer.io.modelBuilder() utility class.
% These architecture specifications can be defined in external source such
% as Excel file.
%
%
% In system composer, an architecture is fully defined by three sets of information:
%
            % * Components and its position in architecture hierarchy
            % * Ports and its mapping to components
            % * Connections between the components through ports
% In this example, we also import interface data definitions from external
% source.
            % * Interfaces in architecture models and its mapping to ports 
%
% This example uses systemcomposer.modelBuilder class to pass all of the above architecture information and
% import a System Composer model.
%
%
% In this example, architecture information of a small UAV system is
% defined in an Excel spreadsheet and is used to create a 
% System Composer architecture model.
%
%% External Source Files
%
% * Architecture.xlsx : This Excel file contains hierarchical information of the architecture model.
%       This example maps the external source data to System Composer model
%       elements. Below is the mapping of information in column names to
%       System Composer model elements.
% 
%       # Element    : Name of the element. Either can be component or port name.
%       # Parent     : Name of the parent element.
%       # Class      : Can be either component or port(Input/Output direction of the port).
%       # Domain     : Mapped as component property. Property "Manufacturer" defined in the
%                      profile UAVComponent under Stereotype PartDescriptor maps to Domain values in excel source file.
%       # Kind       : Mapped as component property. Property "ModelName" defined in the
%                      profile UAVComponent under Stereotype PartDescriptor maps to Kind values in excel source file.
%       # InterfaceName : If class is of port type. InterfaceName maps to name of the interface linked to port.
%       # ConnectedTo : In case of port type, it specifies the connection to
%                       other port defined in format "ComponentName::PortName".
% 
% * DataDefinitions.xlsx : This excel file contains interface data definitions of the model.
%       This example assumes the below mapping between the data definitions in the source excel file
%       and interfaces hierarchy in System Composer :
% 
%       # Name        : Name of the interface or element.
%       # Parent      : Name of the parent interface Name(Applicable only for elements) .
%       # Datatype    : Datatype of element. Can be another interface in format
%                       Bus: InterfaceName
%       # Dimensions  : Dimensions of the element.
%       # Units       : Unit property of the element.
%       # Minimum     : Minimum value of the element.
%       # Maximum     : Maximum value of the element.
%   
%
%% Step 1.  Instantiate the model builder class
% You can instantiate the model builder class with a profile name.
%
% Make sure the current directory is writable because this example will be
% creating files.
[stat, fa] = fileattrib(pwd);
if ~fa.UserWrite
    disp('This script must be run in a writable directory');
    return;
end
% Name of the model to build.
modelName = 'scExampleModelBuider';
% Name of the profile.
profile = 'UAVComponent';
% Name of the source file to read architecture information.
architectureFileName = 'Architecture.xlsx';

% Instantiate the ModelBuilder 
builder = systemcomposer.io.ModelBuilder(profile);

%% Step 2. Build Interface Data Definitions.
% Reading the information in external source file DataDefinitions.xlsx, we
% build the interface data model.
%
% Create MATLAB tables from source Excel file.
opts = detectImportOptions('DataDefinitions.xlsx');
opts.DataRange = 'A2'; % force readtable to start reading from the second row.
definitionContents = readtable('DataDefinitions.xlsx', opts);

% systemcomposer.io.IdService class generates unique ID for a
% given key
idService = systemcomposer.io.IdService();

for rowItr =1:numel(definitionContents(:,1))
    parentInterface = definitionContents.Parent{rowItr};
    if isempty(parentInterface)
        % In case of interfaces adding the interface name to model builder.
        interfaceName = definitionContents.Name{rowItr};
        % Get unique interface ID. getID(container,key) generates 
        % or returns(if key is already present) same value for input key
        % within the container.
        interfaceID = idService.getID('interfaces',interfaceName);
        % Builder utility function to add interface to data
        % dictionary.
        builder.addInterface(interfaceName,interfaceID);   
    else
        % In case of element read element properties and add the element to
        % parent interface.
        elementName  = definitionContents.Name{rowItr};
        interfaceID = idService.getID('interfaces',parentInterface);
        % ElementID is unique within a interface.
        % Appending 'E' at start of ID for uniformity. The generated ID for
        % input element is unique within parent interface name as container. 
        elemID = idService.getID(parentInterface,elementName,'E');
        % Datatype, dimensions, units, minimum and maximum properties of
        % element.
        datatype = definitionContents.DataType{rowItr};
        dimensions = string(definitionContents.Dimensions(rowItr));
        units = definitionContents.Units(rowItr);
        % Make sure that input to builder utility function is always a
        % string.
        if ~ischar(units)
            units = '';
        end
        minimum = definitionContents.Minimum{rowItr};
        maximum = definitionContents.Maximum{rowItr};
        % Builder function to add element with properties in interface.
        builder.addElementInInterface(elementName, elemID, interfaceID, datatype, dimensions, units, 'real', maximum, minimum);
    end
end

%% Step 3. Build Architecture Specifications.
% Architecture specifications de
% Create MATLAB tables from source Excel file.
excelContents = readtable(architectureFileName);
% Iterate over each row in table.
for rowItr =1:numel(excelContents(:,1))
% Read each row of the excel file and columns.
    class = excelContents.Class(rowItr);
    Parent = excelContents.Parent(rowItr);
    Name = excelContents.Element{rowItr};
    % Populating the contents of table using the builder.
    if strcmp(class,'component')
        ID = idService.getID('comp',Name);
        % Root ID is by default set as zero.
        if strcmp(Parent,'scExampleSmallUAV')
            parentID = "0";
        else
            parentID = idService.getID('comp', Parent);
        end
        % Builder utility function to add component.
        builder.addComponent(Name,ID,parentID);
        % Reading the property values
        kind = excelContents.Kind{rowItr};
        domain = excelContents.Domain{rowItr};
        % *Builder to set stereotype and property values*
        builder.setComponentProperty(ID, 'StereotypeName','UAVComponent.PartDescriptor','ModelName',kind,'Manufacturer',domain);
    else
        % In this example, concatenation of port name and parent component name 
        % is used as key to generate unique IDs for ports. 
        portID = idService.getID('port',strcat(Name,Parent));
        % For ports on root architecture. compID is assumed as "0".
        if strcmp(Parent,'scExampleSmallUAV')
            compID = "0";
        else
            compID = idService.getID('comp',Parent);
        end
        % Builder utility function to add port.
        builder.addPort(Name, class, portID, compID );
        
        % InterfaceName specifies the name of the interface linked to port.
        interfaceName = excelContents.InterfaceName{rowItr};
        
        % Get interface ID. getID() will return the same IDs already
        % generated while adding interface in Step 2.
        interfaceID = idService.getID('interfaces',interfaceName);
        % Builder to map interface to port.
        builder.addInterfaceToPort(interfaceID, portID);
        
        % Reading the connectedTo information to build connections between
        % components.
        connectedTo = excelContents.ConnectedTo{rowItr};
        % connectedTo is in format -:
        % (DestinationComponentName::DestinationPortName).
        % For this example, considering the current port as source of the connection. 
        if ~isempty(connectedTo)
            connID = idService.getID('connection',connectedTo);
            splits = split(connectedTo,'::');
            % Get the port ID of the connected port.
            % In this example, port ID is generated by concatenating
            % port name and parent component name. If port id is already
            % generated getID() function returns the same id for input key.
            connectedPortID = idService.getID('port',strcat(splits(2),splits(1))); 
            % Using builder to populate connection table.
            sourcePortID = portID;
            destPortID = connectedPortID;
            % Builder to add connections.
            builder.addConnection(connectedTo,connID,sourcePortID,destPortID);
        end 
    end
end
%% Step 3. Builder build method imports model from populated tables.
[model, importReport] = builder.build(modelName);

%% Close Model
bdclose(modelName);



