 classdef SILHWImplValidation < handle
    % SILHWIMPLVALIDATION
    %   Helper class for validation of hardware implementation parameters
    %   for SIL, and distinguishing between mismatches between host and
    %   configset parameters that should trigger a hard error and those
    %   that should trigger a warning.

    %   Copyright 2017-2018 The MathWorks, Inc.
   
    properties (Access = private)
        ConfigInterface;
        NeedsHardError logical = [];
        NeedsWarning   logical = [];
    end
    
    properties (SetAccess = private)
        IsPWS logical;
        SupportNonFinite logical;
        InvalidParams cell = {};
        HostVals cell = {};
        TargetVals cell = {};
        IsERTTarget logical;
    end
    
    properties (Dependent, SetAccess = private)
        InvalidParamsWithError;
        HostErrorVals;
        TargetErrorVals;
        
        InvalidParamsWithWarning;
        HostWarningVals;
        TargetWarningVals;
        
        ComponentName;
        EndianessNonFiniteError;
    end
    
    methods (Access = public)
        function obj = SILHWImplValidation(configInterface)
            % SILHWIMPLVALIDATION
            %   Construct an instance of rtw.pil.SILHWImplValidation
            rtw.connectivity.Utils.validateArg(configInterface,...
                'coder.connectivity.ConfigInterface');
            obj.ConfigInterface = configInterface;
            obj.IsPWS = strcmp(obj.ConfigInterface.getParam('PortableWordSizes'), 'on');
            obj.IsERTTarget = strcmp(obj.ConfigInterface.getParam('IsERTTarget'), 'on');
            obj.SupportNonFinite = strcmp(obj.ConfigInterface.getParam('SupportNonFinite'), 'on');
        end
        
        %--------------------------------------------------------
        % Comparison methods (these do not throw the actual error/warning)
        %--------------------------------------------------------
        function checkHostWordLengths(obj)
            % CHECKHOSTWORDLENGTHS
            if obj.IsPWS
                % If we are in PWS mode then we don't need to do any of
                % these checks, so just return.
                return;
            end
            prefix=obj.getProdOrTargetPrefix;
            [hostCharacteristics, paramList]=getHostWordLengths(obj);
            needsHardError = [true, ... % Char
                true, ...   % Short
                true, ...   % Int
                true, ...   % Long
                false, ...  % Pointer
                false, ...  % SizeT
                false, ...  % PtrDiffT
                false, ...  % WordSize
                true, ...   % Float
                true, ...   % Double
                true ...    % LongLong
                ];
            targetCharacteristics = ...
                cellfun(@(param)obj.ConfigInterface.getParam(param), paramList);
            
            % check hardware characteristics
            invalidSettings = (hostCharacteristics ~= targetCharacteristics);
            targetBitSizesParams = {[prefix 'BitPerChar'], [prefix 'BitPerShort'], ...
                [prefix 'BitPerInt'], [prefix 'BitPerLong'], [prefix 'BitPerLongLong']};
            typeInfo = rtw.connectivity.ExtendedHardwareConfig.buildTypeInfo( ...
                cellfun(@(param)obj.ConfigInterface.getParam(param), targetBitSizesParams), ...
                obj.ConfigInterface.getParam([prefix, 'WordSize']), ...
                obj.ConfigInterface.getParam([prefix, 'BitPerFloat']), ...
                obj.ConfigInterface.getParam([prefix, 'BitPerDouble']));
            isByteAddressable = rtw.connectivity.ExtendedHardwareConfig.staticIsByteAddressable(typeInfo);
            if ~isByteAddressable
                % If not byte addressable, then all errors must be hard
                % errors
                needsHardError = true(size(needsHardError));
            else
                % A hard error is needed if the host size is less than
                % the target size
                needsHardError = needsHardError | (hostCharacteristics < targetCharacteristics);
            end
            % Standardize format and store in object instance
            invalidParams = paramList(invalidSettings);
            hostVals = num2cell(hostCharacteristics(invalidSettings));
            targetVals = num2cell(targetCharacteristics(invalidSettings));
            needsHardError = needsHardError(invalidSettings);
            obj.InvalidParams = [obj.InvalidParams(:); invalidParams(:)];   % Store in column format
            obj.HostVals = [obj.HostVals(:); hostVals(:)];
            obj.TargetVals = [obj.TargetVals(:); targetVals(:)];
            obj.NeedsHardError = [obj.NeedsHardError(:); needsHardError(:)];
            obj.NeedsWarning = [obj.NeedsWarning(:); false(numel(invalidParams),1)];    % Nothing needs a warning
        end
        
        function prefix=getProdOrTargetPrefix(obj)
            prodEqTarget = strcmp(obj.ConfigInterface.getParam('ProdEqTarget'), 'on');
            if (prodEqTarget)
                prefix = 'Prod';
            else
                prefix = 'Target';
            end
        end
        
        function [hostCharacteristics, paramList]=getHostWordLengths(obj)
            % get word lengths
            host = obj.ConfigInterface.getHostWordLengths;          
            assert(host.LongLongMode, 'Only 64-bit platforms are supported - HostLongLong mode will be "on" on all platforms');
            hostCharacteristics = [host.CharNumBits ...
                host.ShortNumBits ...
                host.IntNumBits ...
                host.LongNumBits, ...
                host.PointerNumBits ...
                host.SizeTNumBits, ...
                host.PtrDiffTNumBits, ...
                host.WordSize, ...
                host.FloatNumBits, ...
                host.DoubleNumBits, ...
                host.LongLongNumBits];
            paramList = obj.getHostWordLengthsParamList;
        end
        
        function paramList = getHostWordLengthsParamList(this)
            prefix=this.getProdOrTargetPrefix;
            paramList = {'BitPerChar', ...
                'BitPerShort', ...
                'BitPerInt', ...
                'BitPerLong', ...
                'BitPerPointer', ...
                'BitPerSizeT', ...
                'BitPerPtrDiffT', ...
                'WordSize', ...
                'BitPerFloat', ...
                'BitPerDouble', ...
                'BitPerLongLong'};
            paramList=strcat(prefix, paramList);
        end
        
        function [hostImplementationProps, paramList]=getHostImplementationProps(obj)            
            host = obj.ConfigInterface.getHostImplProperties;
            if host.ShiftRightIntArith
                shiftRightValue = 'on';
            else
                shiftRightValue = 'off';
            end
            hostImplementationProps={shiftRightValue, ...
                host.IntDivRoundTo, ...
                host.Endianess};
            paramList = obj.getHostImplementationPropsParamList;
        end
        
        function paramList = getHostImplementationPropsParamList(this)
            prefix=this.getProdOrTargetPrefix;
            paramList={[prefix 'ShiftRightIntArith'], ...
                [prefix 'IntDivRoundTo'], ...
                [prefix 'Endianess']};
        end
        
        function checkHostImplProps(obj)
            % CHECKHOSTIMPLPROPS
            invalidParamProps = obj.compareHostImplPropsInner;
            obj.InvalidParams = [obj.InvalidParams(:); invalidParamProps.invalidParams(:)];   % Store in column format
            obj.HostVals = [obj.HostVals(:); invalidParamProps.hostVals(:)];
            obj.TargetVals = [obj.TargetVals(:); invalidParamProps.targetVals(:)];
            obj.NeedsHardError = [obj.NeedsHardError(:); invalidParamProps.needsHardError(:)];
            obj.NeedsWarning = [obj.NeedsWarning(:); invalidParamProps.needsWarning(:)];
        end
    end
   
    methods (Access = private)
        function invalidParamProps = compareHostImplPropsInner(obj)
            % COMPAREHOSTIMPLPROPSINNER
            prefix=obj.getProdOrTargetPrefix;
            isPWS = obj.IsPWS;
            % validate implementation properties even if PWS is on
            [hostProps, paramList] = obj.getHostImplementationProps;
            invalidParamProps=struct;
            invalidParamProps.invalidParams = {};
            invalidParamProps.hostVals = {};
            invalidParamProps.targetVals = {};
            invalidParamProps.needsHardError = [];
            invalidParamProps.needsWarning = [];
            currentParamNeedsWarning = true;   % Hard error always overrides warning
            % TargetIntDivRoundTo
            param = [prefix 'IntDivRoundTo'];            
            if isPWS
                % allow Undefined for PWS
                allowableValue = 'Undefined';
            else
                allowableValue = '';
            end
            currentParamNeedsHardError = true;
            hostPropValue=hostProps{strcmp(param, paramList)};
            if ~obj.isImplPropOk(getParam(obj.ConfigInterface, param), hostPropValue, allowableValue)
                invalidParamProps=obj.getInvalidParamProps(invalidParamProps, param, hostPropValue, currentParamNeedsHardError, currentParamNeedsWarning);
            end
            % TargetEndianess
            param = [prefix 'Endianess'];   
            if isPWS
                % allow Unspecified for PWS
                allowableValue = 'Unspecified';
            else
                allowableValue = '';
            end
            % We can permit mismatched endianess if non-finite numbers are
            % not supported, however, the code to deal with non-finite
            % numbers is endianess specific and will lead to incorrect
            % numerical results, so we must hard error on mismatch of
            % endianess when SupportNonFinite is on.
            supportNonFinite = obj.SupportNonFinite;
            currentParamNeedsHardError = supportNonFinite || isPWS;
            hostPropValue=hostProps{strcmp(param, paramList)};
            if ~obj.isImplPropOk(getParam(obj.ConfigInterface, param), hostPropValue, allowableValue)
                invalidParamProps=obj.getInvalidParamProps(invalidParamProps, param, hostPropValue, currentParamNeedsHardError, currentParamNeedsWarning);
            end
            % ShiftRightIntArith always generates fully portable code
            % so never warn for PWS.   However, for non-PWS,
            % enforce an exact match with the host.
            if ~isPWS                
                param = [prefix 'ShiftRightIntArith'];
                allowableValue = '';
                hostVal=hostProps{strcmp(param, paramList)};
                currentParamNeedsHardError = true;
                if ~obj.isImplPropOk(getParam(obj.ConfigInterface, param), hostVal, allowableValue)
                    invalidParamProps=obj.getInvalidParamProps(invalidParamProps, param, hostVal, currentParamNeedsHardError, currentParamNeedsWarning);
                end
            end   
        end
        
        function invalidParamProps=...
                getInvalidParamProps(obj, invalidParamProps, param, hostPropValue, currentParamNeedsHardError, currentParamNeedsWarning)          
            invalidParamProps.invalidParams{end+1} = param;
            invalidParamProps.targetVals{end+1} = getParam(obj.ConfigInterface, param);
            invalidParamProps.hostVals{end+1} = hostPropValue;
            invalidParamProps.needsHardError(end+1) = currentParamNeedsHardError;
            invalidParamProps.needsWarning(end+1) = currentParamNeedsWarning;
        end
    end
    
    
    methods
        function params = get.InvalidParamsWithError(obj)
            % GET.INVALIDPARAMSWITHERROR
            params = obj.InvalidParams(obj.NeedsHardError);
        end
        
        function vals = get.HostErrorVals(obj)
            % GET.HOSTERRORVALS
            vals = obj.HostVals(obj.NeedsHardError);
        end
        
        function vals = get.TargetErrorVals(obj)
            % GET.TARGETERRORVALS
            vals = obj.TargetVals(obj.NeedsHardError);
        end
        
        function params = get.InvalidParamsWithWarning(obj)
            % GET.INVALIDPARAMSWITHWARNING
            params = obj.InvalidParams(~obj.NeedsHardError & obj.NeedsWarning);
        end
        
        function vals = get.HostWarningVals(obj)
            % GET.HOSTWARNINGVALS
            vals = obj.HostVals(~obj.NeedsHardError & obj.NeedsWarning);
        end
        
        function vals = get.TargetWarningVals(obj)
            % GET.TARGETWARNINGVALS
            vals = obj.TargetVals(~obj.NeedsHardError & obj.NeedsWarning);
        end
        
        function componentName = get.ComponentName(obj)
            % GET.COMPONENTNAME
            componentName = obj.ConfigInterface.getCodeGenComponent;
        end
        
        function retVal = get.EndianessNonFiniteError(obj)
            % GET.ENDIANESSONLYERROR
            %   Returns true if:
            %       The only error is about endianess
            %       SupportNonFinite is enabled
            %       PWS is disabled
            lInvalidParamsWithError = obj.InvalidParamsWithError;
            retVal = numel(lInvalidParamsWithError) == 1 && ...
                     endsWith(lInvalidParamsWithError{1}, 'Endianess') && ...
                     obj.SupportNonFinite && ...
                     ~obj.IsPWS;
        end
    end
    
    methods (Static, Access = public)     
        function settingOk = isImplPropOk(configValue, hostValue, allowableValue)
            % ISIMPLPROPOK
            settingOk = strcmp(configValue, hostValue);
            if ~isempty(allowableValue)
                settingOk = settingOk || strcmp(configValue, allowableValue);
            end
        end
        
        function infoStr = createInfoStr(hostVals, targetVals, invalidParams)
            % CREATEINFOSTR
            %   Utility method to build up string containing mismatched
            %   host/configset hardware implementation parameters
            infoStr = '';                        
            for i=1:length(invalidParams)
                if i<length(invalidParams)
                    addComma = ', ';
                else
                    addComma = '';
                end
                currSettingText = message('PIL:pil:HWImplSpecifiedSetting');
                reqSettingText = message('PIL:pil:HWImplRequiredSetting');
                if isnumeric(hostVals{i})
                    stringTemplate = '%s%s (%s: %d, %s: %d)%s';
                else
                    stringTemplate = '%s%s (%s: %s, %s: %s)%s';
                end
                infoStr = sprintf(stringTemplate, ...
                    infoStr, ...
                    invalidParams{i}, ...
                    currSettingText.getString, ...
                    targetVals{i}, ...
                    reqSettingText.getString, ...
                    hostVals{i}, ...
                    addComma);
            end
        end
    end
end
