classdef (Hidden = true) AccessFunctions < rtw.connectivity.CodeInfoWriterProject
% ACCESSFUNCTIONS creates implementation of access functions defined by CodeInfo
%
% This is an undocumented class. Its methods and properties are likely to
% change without warning from one release to the next

% Copyright 2015-2018 The MathWorks, Inc.
    
    properties (SetAccess = 'private', GetAccess = 'protected')
        SILPILWrapperUtils;
        AccessFunctionExpressions = struct('implementation',{},'isInport',[]); % Store Access function interface information
        AccessFunctionData;
    end

    methods
        
        % Constructor 
        function this = AccessFunctions(...
            silPILWrapperUtils,...
            accessFcnsFile,...
            accessFcnsData)
            
            narginchk(3, 3);
            rtw.connectivity.Utils.validateArg(...
                silPILWrapperUtils,...
                'rtw.pil.SILPILWrapperUtils');
            
            % call super class constructor
            this@rtw.connectivity.CodeInfoWriterProject(...
                silPILWrapperUtils.getCodeInfoUtils, ...
                accessFcnsFile);
            
            % store SILPILWrapperUtils
            this.SILPILWrapperUtils = silPILWrapperUtils;
            
            % iterate over inports, outports and parameters where
            % access functions are and store them
            
            dataElements = [rtw.pil.CodeInfoData.createTargetDataElements(this.SILPILWrapperUtils, ...
                1:length(this.codeInfo.Inports), 'input'), ...
                rtw.pil.CodeInfoData.createTargetDataElements(this.SILPILWrapperUtils, ...
                this.SILPILWrapperUtils.getSupportedTunableWorkspaceParams, 'parameter')];
                            
            isInport = true;
            for i = 1 : length(dataElements)
                dataInterfaces = dataElements(i).getDataInterface;
                implementation = dataInterfaces.getRTWDataInterfaceImplementation();
                if rtw.connectivity.CodeInfoUtils.isa(implementation, 'BasicAccessFunctionExpression') && ...
                        (isempty(this.AccessFunctionExpressions) || ~ismember(implementation,[this.AccessFunctionExpressions.implementation])) && ...
                        ~dataInterfaces.NeedsIOTargetTransform
                    this.AccessFunctionExpressions(end+1).implementation = implementation;
                    this.AccessFunctionExpressions(end).isInport = isInport;
                end
            end
            
            dataElements = rtw.pil.CodeInfoData.createTargetDataElements(this.SILPILWrapperUtils, ...
                1:length(this.codeInfo.Outports), 'output');
            
            isInport = false;
            for i = 1 : length(dataElements)                
                dataInterfaces = dataElements(i).getDataInterface;
                implementation = dataInterfaces.getRTWDataInterfaceImplementation();
                if rtw.connectivity.CodeInfoUtils.isa(implementation, 'BasicAccessFunctionExpression') && ...
                        (isempty(this.AccessFunctionExpressions) || ~ismember(implementation,[this.AccessFunctionExpressions.implementation])) && ...
                        ~dataInterfaces.NeedsIOTargetTransform
                    this.AccessFunctionExpressions(end+1).implementation = implementation;
                    this.AccessFunctionExpressions(end).isInport = isInport;
                end
            end
            
            this.AccessFunctionData = accessFcnsData;
        end
    end
    
    methods (Access = 'protected')
        function writeOutputBody(this)
            this.writeSectionHeader;
            this.writeSectionIncludes;
            this.writeSectionData;
            this.writeSectionFunctions;
        end
        
        function writeSectionHeader(this)
            this.writer.writeCellLines({'/*';
                [' * File: ' this.SILPILWrapperUtils.getSILPILInterface.getAccessFcnsSourceInfo];
                ' * ';
                [' * ' this.SILPILWrapperUtils.getSILPILInterface.getSimModeAcronym ...
                ' generated interface for code: "' this.codeInfo.Name '"'];
                ' *';
                ' */'});
            this.writer.newLine;
        end
        
        function writeSectionIncludes(this)
            % include model.h
            headerList = {[this.SILPILWrapperUtils.getComponentName '.h']};
            % include all header files specified in the prototypes
            headerList = [headerList, this.getAllProtypesHeaderFiles];
            % remove duplication, keep model.h at the top of the list
            headerList = unique(headerList, 'Stable');
            for idx =1:numel(headerList)
                this.writer.wLine('#include "%s"', headerList{idx});
            end
            this.writer.wLine('#include <string.h>'); % needed to use memcpy
            this.writer.newLine;
        end
            
        function writeSectionData(this)
            % write header
            this.writer.wComment('Interface data');
            
            for i = 1 : length(this.AccessFunctionData)
                data = this.AccessFunctionData{i};
                
                this.writer.wLine('extern %s', data);
            end
            
            this.writer.newLine;
        end
        
        % Access functions
        function writeSectionFunctions(this)
            this.writer.wComment('Access functions');
            for i = 1 : length(this.AccessFunctionExpressions)
                accessFcn = this.AccessFunctionExpressions(i).implementation;
                isInport = this.AccessFunctionExpressions(i).isInport;
                
                % Writing a function prototype
                accessFcnPrototype = accessFcn.Prototype;
                
                % return type                                
                accessFcnReturn = accessFcnPrototype.Return;
                hasReturnArg = false;
                switch length(accessFcnReturn)
                    case 0
                        retTypeIdentifier = 'void ';
                    case 1                
                        accessFcnReturnType = accessFcnReturn(1).Type;
                        retTypeIdentifier =...
                            this.writeReturnString(...
                                accessFcnReturnType);
                            hasReturnArg = true;
                    otherwise
                        assert(false, ...
                               sprintf('%s: There should be zero or one return argument',...
                                       mfilename('class')));
                end
                
                % variable name used by this access function
                varName = rtw.pil.AccessFunctions.getExpression(accessFcnPrototype);
                        
                % arguments
                accessFcnArguments = rtw.connectivity.CodeInfoUtils.getArray(accessFcnPrototype.Arguments);
                switch length(accessFcnArguments)
                    case 0               
                        this.writer.wBlockStart('%s %s(void)', retTypeIdentifier, accessFcnPrototype.Name);
                
                        % function body
                        isArray = rtw.connectivity.CodeInfoTypeUtils.isVector(accessFcn.Type, ...
                                                                  this.SILPILWrapperUtils.getClientInterface);
                
                        storageSizeStr = '';
                        addressOperator = '';
                        if isArray
                            storageSizeStr = '[0]';
                            addressOperator = '&';
                        elseif accessFcnReturnType.isPointer
                            addressOperator = '&';
                        end
                                                                              
                        this.writer.wLine('return %s%s%s;', addressOperator, varName, storageSizeStr);                
                        
                    case 1
                        argument = accessFcnArguments;
                        isArray = rtw.connectivity.CodeInfoTypeUtils.isVector(...
                            accessFcn.Type,...
                            this.SILPILWrapperUtils.getClientInterface) && ...
                            argument.Type.isPointer;
                        [argString, storageType] = this.getArgumentString(argument.Type, argument.Name);
                                                
                        this.writer.wBlockStart('%s %s(%s)', ...
                                                retTypeIdentifier,...
                                                accessFcnPrototype.Name,...
                                                argString);
                        
                        % use type utilities extract dimensions information
                        dimensions = rtw.connectivity.CodeInfoTypeUtils.getDimensions(accessFcn.Type);                        
                        
                        if isInport
                            % single argument function for getting an input
                            if isArray
                                this.writeArrayCopy( argument.Name,varName, storageType.Identifier, dimensions);
                            else
                                if argument.Type.isPointer
                                    this.writer.wLine('*%s = %s;', argument.Name, varName);
                                else
                                    if hasReturnArg
                                        % return the requested input
                                        % element
                                        this.writer.wLine('return %s[%s];', varName, argument.Name);
                                    else
                                        % assign by value scalar input
                                        % value
                                        this.writer.wLine('%s = %s;', argument.Name, varName);
                                    end
                                end
                            end
                        else
                            % single argument function for setting an output

                            if isArray
                                this.writeArrayCopy( varName, argument.Name, storageType.Identifier, dimensions);
                            else
                                if argument.Type.isPointer
                                    this.writer.wLine('%s = *%s;', varName, argument.Name);
                                else
                                    this.writer.wLine('%s = %s;', varName, argument.Name);
                                end
                            end
                        end
                    case 2
                        % Two argument function is assumed to be:
                        % set(i, T)
                        % this sets, a single element for an output.
                        argumentIndex = accessFcnPrototype.Arguments(1);
                        argument = accessFcnArguments(2);
                        
                        indexArgString = this.getArgumentString(argumentIndex.Type, argumentIndex.Name);
                        argString = this.getArgumentString(argument.Type, argument.Name);
                        
                        
                        this.writer.wBlockStart('%s %s(%s, %s)', ...
                            retTypeIdentifier,...
                            accessFcnPrototype.Name,...
                            indexArgString, ...
                            argString);
                        
                        
                        if isInport
                            assert(false,...
                               sprintf('%s: It is not expected that SIL/PIL will encounter a two input get function',...
                                       mfilename('class')));                            
                        else
                            this.writer.wLine('%s[%s] = %s;', varName, argumentIndex.Name, argument.Name);
                        end
                        
                    otherwise
                        assert(false,...
                               sprintf('%s: Number of arguments should be either zero, one or two',...
                                       mfilename('class')));
                end
                this.writer.wBlockEnd;
            end
        end

            
    end
    
    methods (Access = 'private')
        function typeIdentifier = writeReturnString(~, type)
            typeIdentifier = {'', ''};
            
            % extract base type
            baseType = rtw.connectivity.CodeInfoTypeUtils.getCStorageType(type);
            
            % is it volatile
            if rtw.connectivity.CodeInfoTypeUtils.isVolatile(baseType)
                assert(false, ...
                    sprintf('%s: SIL/PIL doesn''t expect to encounter volatile return arguments',...
                    mfilename('class')));
            end
                    
            % is it read only
            if rtw.connectivity.CodeInfoTypeUtils.isReadOnly(baseType)
                typeIdentifier{1} = 'const ';
            end
            
            if type.isPointer
                typeIdentifier{2} = '*';
            end
                                        
            storageType =...
                rtw.connectivity.CodeInfoTypeUtils.getCStorageType(...
                    baseType);
                        
            typeIdentifier =...
                sprintf('%s%s%s ',...
                        typeIdentifier{1},...
                        storageType.Identifier,...
                        typeIdentifier{2});                        
        end
        
        function [typeIdentifier, storageType] = getArgumentString(~, type, name) 
                        
            typeIdentifier = { '', '', ''};
            if rtw.connectivity.CodeInfoTypeUtils.isReadOnly(type)
                typeIdentifier{1} = 'const ';
            end
            
            storageType =...
                rtw.connectivity.CodeInfoTypeUtils.getCStorageType(type);

            if type.isPointer         
                typeIdentifier{2} = '* ';
                if type.ReadOnly
                    typeIdentifier{3} = 'const ';
                end
            end
                                    
            typeIdentifier =...
                sprintf('%s%s %s%s%s',...
                        typeIdentifier{1},...
                        storageType.Identifier,...
                        typeIdentifier{2},...
                        typeIdentifier{3},...
                        name);
            
        end
        
        function writeArrayCopy(this, srcArray, destArray, arrayType, arraySize)
            this.writer.wLine('memcpy(%s, &%s[0], sizeof(%s)*%d);',...
                                       srcArray,...
                                       destArray,...                                       
                                       arrayType,...
                                       arraySize);
            
        end
        
        function headerFiles = getAllProtypesHeaderFiles(this)
            numAccessFncs = numel(this.AccessFunctionExpressions);
            headerFiles = cell(1, numAccessFncs);
            for idx = 1:numAccessFncs
                currHeader = ...
                    this.AccessFunctionExpressions(idx).implementation.Prototype.HeaderFile;
                if isempty(currHeader)
                    % As of 18a, GetSet CSC is the only AccessFunction
                    % available to customers, so it makes sense to have a
                    % GetSet specific error.
                    name = this.AccessFunctionExpressions(idx).implementation.Prototype.Name;
                    DAStudio.error('PIL:pil:GetSetHeaderFileMissing', name);  
                end
                headerFiles(idx) = {currHeader};
            end
                
        end

    end
        
    methods (Static)
        
        
        % Strips out prefix and suffix from the access function name in order 
        % to get variable name for that will be used for SIL/PIL data         
        function expression = getExpression(prototype)
            expression = regexprep(prototype.Name, '^(get_|set_)', '');
            expression = regexprep(expression, '(_reference|_value)$', '');
            expression = strcat('xil_', expression);
        end
        
        % For a passed codeInfo it checks whether there are any access function        
        function flag = exist(codeInfo)
            flag = false;
            dataInterfaces = [...
                codeInfo.Inports,...
                codeInfo.Outports, ...
                codeInfo.Parameters];
            for i = 1 : length(dataInterfaces)
                if rtw.connectivity.CodeInfoUtils.isa(dataInterfaces(i).Implementation, 'BasicAccessFunctionExpression')
                    flag = true;
                    break;
                end
            end

        end
    end
    
end

