classdef DataInterface < matlab.mixin.Heterogeneous & handle
%DATAINTERFACE Represents data (possibly structured) to be
%              transferred between host and target.        

%   Copyright 2010-2020 The MathWorks, Inc.


    properties(Abstract, Constant)        
        % Is this a description of data that forms part of the host-target
        % communications protocol: used for byte reordering test mode
        IsProtocolData
    end

    properties (GetAccess = 'private', SetAccess = 'private')
        % RTW.DataInterface
        RTWDataInterface;        
        % coder.types/embedded.type description for Implementation
        ImplementationType;
        % Arrays of contiguous DataElements
        TargetDataElements; % target type data elements
        HostDataElements; % host type data elements
        SkipDataElementValidation = false;
        % ClientInterface
        ClientInterface;
    end
    
    properties (GetAccess = 'public', SetAccess = 'protected')
       DimsPreserved = false; % default is that processing of 
                              % DataInterface does not 
                              % preserve array dimensions
                              % (i.e. dimensions are flattened by 
                              % default)
       NeedsIOTargetTransform = false; % To support more complicated interface
                                       % types, codeInfo will capture
                                       % transform functions to simplify
                                       % I/O processing in the SIL
                                       % infrastructure. This property
                                       % captures whether an interface will
                                       % use a target side transform rather
                                       % than the direct codeInfo
                                       % implementation.
       HasTargetSideOpaqueRegion = false;
       StaticVarMap = containers.Map();
       StaticVarList;
       StaticVarIndexList;
       StaticVarPlainElements;
    end
        
    methods (Abstract)        

        % get a string representation
        string = getString(this, silPILWrapperUtils)                
        
        % get the target-side type identifier
        targetBaseTypeIdentifier = getTargetBaseTypeIdentifier(this, dataElement)           
        
        % write a PILIOData table entry
        writePILIODataTableEntry(this, dataElement, tableName, writer, targetInterface)     
                       
        % is the data a bit field member
        isBitFieldMember = isBitFieldMember(this)       
        
        % returns the embedded.type that should be expanded into
        % dataElements
        %
        % with respect to host / target type separation, this is the
        % target type
        type = getType(this)
        
        % Control whether struct optimization is allowed
        isStructOptimizationAllowed = isStructOptimizationAllowed(this)
    end             
    
    methods           
        function this = DataInterface(rtwDataInterface, clientInterface)
            this.RTWDataInterface = rtwDataInterface;
            if exist('clientInterface','var')
                %Only needed by CodeInfoData, and these will always be
                %created with a clientInterface
                this.ClientInterface = clientInterface;
            end
        end    
        
        function rtwDataInterface = getRTWDataInterface(this)
            rtwDataInterface = this.RTWDataInterface;            
        end
        
        function rtwDataImplementation = getRTWDataInterfaceImplementation(this)
           rtwDataImplementation = this.RTWDataInterface.Implementation;
           %This could be extended to match Typed Collection elements.
        end
                
        
        % support host / target type separation by returning a type for
        % host-side processing
        function type = getHostType(this)
            % default implementation returns the same as the target type
            % subclasses can override as necessary
            type = this.getType;            
        end
        
        function setSkipDataElementValidation(this, skipDataElementValidation)
            this.SkipDataElementValidation = skipDataElementValidation;
        end
        
        function skipDataElementValidation = getSkipDataElementValidation(this) 
            skipDataElementValidation = this.SkipDataElementValidation;
        end
        
        function clientInterface = getClientInterface(this) 
            clientInterface = this.ClientInterface;
        end
        
        function storageSize = getSymbolicNumElements(~, dataElement)
           storageSize = dataElement.getSymbolicNumElements;
        end
       
        function symbolicDims = getSymbolicDimensions(~, dataElement)
            symbolicDims = dataElement.getSymbolicDimensions; 
        end
    end   
    
    methods (Sealed)
        % return data elements for the target type
        function dataElements = getTargetDataElements(this)
            if isempty(this.TargetDataElements)
                % derive data elements from type
                this.TargetDataElements = this.getDataElementsInternal(this.getType);
            end
            dataElements = this.TargetDataElements;
        end
        
        % return data elements for the host type
        function dataElements = getHostDataElements(this)
            if isequal(this.getType, this.getHostType)
                % reuse the target data elements
                dataElements = this.getTargetDataElements;
            else
                if isempty(this.HostDataElements)
                    % derive data elements from host type
                    this.HostDataElements = this.getDataElementsInternal(this.getHostType);
               end
                dataElements = this.HostDataElements;
           end
        end                               
        end
        
    methods (Access = 'private')
        % helper to create data elements for a set of types
        function dataElements = getDataElementsInternal(this, types)
            dataElements = rtw.pil.DataElement.empty;
            for i=1:length(types)
                curDataElements = rtw.pil.DataInterface.initDataElements(this, types(i),[]);
                dataElements(end+1 : end+numel(curDataElements)) = curDataElements;
            end
        end
    end        
    
    methods (Static, Access = 'protected')
        % recursive function to determine the set of DataElements associated
        % with a type
        function dataElements = initDataElements(dataInterface, type, data, dataElements)                                   
            defaultProcessing = false;
            if isempty(data)
                % init root level
                data.IsComplex = false;
                data.Ancestors = [];
                data.ReplacementVar = [];
                
                %Determine if we're generating default elements
                if nargin==4
                    defaultProcessing = true;                    
                end

                dataElements = rtw.pil.DataElement.empty;
                rtwDI = dataInterface.getRTWDataInterfaceImplementation;
                useOpaqueRegion = rtw.connectivity.CodeInfoUtils.shouldUseOpaqueRegion(rtwDI);
                if dataInterface.NeedsIOTargetTransform && ~defaultProcessing && useOpaqueRegion
                    dataInterface.HasTargetSideOpaqueRegion = true;                    
                end
                if type.isStructure && dataInterface.NeedsIOTargetTransform && ~defaultProcessing                    
                    if useOpaqueRegion
                        %Bypass additional processing if there is an
                        %OpaqueRegion

                    elseif ~isempty(rtwDI.DynamicMemory) && ~isempty(rtwDI.DynamicMemory.StaticVariables)
                        elements = rtw.connectivity.CodeInfoUtils.getArray(rtwDI.DynamicMemory.StaticVariables.Elements);
                        dataInterface.StaticVarList = elements;
                        dataInterface.StaticVarIndexList = 1:numel(elements);
                        %Construct list of default data elements
                        %(non-transformed) We need these to construct the
                        %full element paths.
                        fullListPlainElements = rtw.pil.DataInterface.initDataElements(dataInterface, type, [], true);
                        %Prune any elements that aren't strings.
                        dataInterface.StaticVarPlainElements = ...
                            fullListPlainElements(strcmpi({fullListPlainElements.SimulinkType},'char'));
                    end                    
                end
            end
            if isempty(type)
                %Return early and bypass this data element if the
                %underlying type is empty. This has only been seen to
                %happen in one case (see G1034542), but this early return
                %means no DataElement is created for this item.
                return;
            end
            if type.isStructure    
                % default init
                ancestor = rtw.pil.DataElementAncestor;           
                ancestor.TypeIdentifier = type.Identifier;
                ancestor.IsStructure = true;                
                
                transformElementTypes = dataInterface.NeedsIOTargetTransform;                
                
                elements = rtw.connectivity.CodeInfoUtils.getArray(type.Elements);
                
                for i=1:length(elements)                  
                    newData = data;                               
                    element = elements(i);
                    % grow ancestors
                    ancestor.ElementIndex = i;
                    ancestor.ElementIdentifier = element.Identifier;
                    
                    ancestor.IsComplex = newData.IsComplex;
                    newData.Ancestors = [newData.Ancestors ancestor];
                    
                    elementType = element.Type;
                    if transformElementTypes
                        if strcmp(elementType.Identifier,'std::string') && ...
                                ~isempty(dataInterface.StaticVarList) && ...
                                ~isempty(dataInterface.StaticVarPlainElements)
                            
                            elementType = dataInterface.StaticVarList(1).Type;
                            %Construct full element path for strings.
                            %Prune off std::string element. Then use this
                            %full element path for later use.
                            index = 0;
                            isPointerTypeSupported = true;
                            isLeafPointerAllowed = true;                            
                            fullElementId = regexprep( ...
                                dataInterface.StaticVarPlainElements(1).getElementExpression(index, ...
                                isPointerTypeSupported, ...
                                isLeafPointerAllowed), ...
                                '\.c_str$','');
                            dataInterface.StaticVarMap(dataInterface.StaticVarList(1).Identifier) = ...
                                {fullElementId, dataInterface.StaticVarIndexList(1)};
                            assert(isempty(newData.ReplacementVar), 'Only one replacement variable supported');
                            newData.ReplacementVar = dataInterface.StaticVarList(1);
                            dataInterface.StaticVarList(1) = [];
                            dataInterface.StaticVarIndexList(1) = [];
                            dataInterface.StaticVarPlainElements(1) = [];
                        end
                    end
                    
                    dataElements = rtw.pil.DataInterface.initDataElements(dataInterface, ...
                        elementType, newData, dataElements);                    
                end                
            elseif type.isComplex
                % replace complex with Struct 
                mf0ModelInstance = dataInterface.getMF0ModelInstance;
                if isempty(mf0ModelInstance)
                    re = coder.types.AggregateElement;
                    re.Identifier = 're';
                    re.Type = type.BaseType;
                    im = coder.types.AggregateElement;
                    im.Identifier = 'im';
                    im.Type = type.BaseType;
                    stype = coder.types.Struct;
                    stype.Elements = [re, im];
                else
                    re = coder.descriptor.types.AggregateElement(mf0ModelInstance);
                    re.Identifier = 're';
                    re.Type = rtw.pil.DataInterface.getMF0BaseType(type, mf0ModelInstance);
                    im = coder.descriptor.types.AggregateElement(mf0ModelInstance);
                    im.Identifier = 'im';
                    im.Type = rtw.pil.DataInterface.getMF0BaseType(type, mf0ModelInstance);
                    stype = coder.descriptor.types.Struct(mf0ModelInstance);
                    stype.Elements.add(re)
                    stype.Elements.add(im);
                end
                stype.Identifier = type.Identifier;
                stype.Name = type.Name;
                stype.ReadOnly = type.ReadOnly;
                stype.Volatile = type.Volatile;
                
                % flag complex
                data.IsComplex = true;
                % recurse
                dataElements = rtw.pil.DataInterface.initDataElements(dataInterface, stype, data, dataElements);                      
            elseif type.isMatrix || type.isPointer
                ancestor = rtw.pil.DataElementAncestor; 
                ancestor.TypeIdentifier = type.Identifier;                
                ancestor.IsMatrix = type.isMatrix;
                ancestor.Dimensions = rtw.connectivity.CodeInfoTypeUtils.getDimensions(type);                
                ancestor.SymbolicNumElements = rtw.connectivity.CodeInfoTypeUtils.getSymbolicWidth(type);
                ancestor.SymbolicDims = rtw.connectivity.CodeInfoTypeUtils.getSymbolicDimensions(type);
                ancestor.HasSymbolicDims = rtw.connectivity.CodeInfoTypeUtils.hasSymbolicDimensions(type);
                ancestor.IsPointer = type.isPointer;
                
                % grow ancestors
                data.Ancestors = [data.Ancestors ancestor];                
                % recurse
                dataElements = rtw.pil.DataInterface.initDataElements(dataInterface, type.BaseType, data, dataElements);                                        
            else
                %
                % done - create new DataElement   
                %
                
                % keep track of the index
                index = length(dataElements) + 1;               
                % keep track of an accumulated index required for variable signal
                % size bus handling
                if ~isempty(dataElements)
                    newVarDimIndex = dataElements(end).getVarDimIndex + dataElements(end).getNumElements;
                else
                    % start from 0
                    newVarDimIndex = 0;
                end
                dataElements(end+1) = rtw.pil.DataElement(dataInterface, ...
                                                          type, ...
                                                          index, ...
                                                          newVarDimIndex, ...
                                                          data.Ancestors, ...
                                                          data.ReplacementVar);
                
                if(length(dataElements) > 1 && dataElements(end-1).isStructElement && dataElements(end).isStructElement)
                    if dataElements(end-1).areAncestorsEqual(dataElements(end)) && dataInterface.isStructOptimizationAllowed
                        if(~dataElements(end-1).SkipForCommonAncestorProcessing)
                            dataElements(end-1).SkipForCommonAncestorProcessing = true;
                        end
                        dataElements(end).NumPrevSameAncestorElements = dataElements(end-1).NumPrevSameAncestorElements + 1;
                    end
                end
            end
        end                                                
    end
    
    methods(Static, Access = private)
        function baseType = getMF0BaseType(type, mf0ModelInstance)
            if isa(type, 'coder.types.Type')
                baseType = type.BaseType.serializeMF0(mf0ModelInstance);
            elseif isa(type, 'coder.descriptor.types.Type')
                baseType = type.BaseType;
            else
                assert(false, 'unexpected class');
            end
        end
    end
end
