%DisplayHelperDense - knows how to display partial dense arrays.

% Copyright 2009-2019 The MathWorks, Inc.
classdef DisplayHelperDense < parallel.internal.shared.DisplayHelper
    
    properties ( Access = private )
        % A cell array length ndims of [e f] arrays.
        ShowingSizesCell
        
        % How many elements we actually have in each dimension of the value - simply
        % a cache of size of the value in the full dimensionality.
        NumGotInEachDim
        
        % The values to display
        Value
    end
    
    %% Static helpers used in construction
    methods ( Static, Access = private )
        
        % assumption is that we're starting at element 1 in each dimension
        function c = deriveShowingSizesCellFromValue( value, globalNdims )
            % szGot := [2, 3, 4]
            szGot = size( value );
            % Ensure szGot is the right length (might be shorter than global size)
            szGot = [ szGot, ones( 1, globalNdims - length( szGot ) ) ];
            % oneToSzGot := [ 1, 2; 1, 3; 1, 4 ];
            oneToSzGot = [ ones( 1, length( szGot ) ); szGot ] .';
            % ShowingSizesCell is one cell per row of oneToSzGot
            c = mat2cell( oneToSzGot, ones( 1, length( szGot ) ), 2 );
        end
        
        function n = deriveNumGotInEachDim( showSzCell )
            n = zeros( 1, length( showSzCell ) );
            for ii=1:length( showSzCell )
                if isnumeric( showSzCell{ii} ) && length( showSzCell{ii} ) == 2
                    % Got an [e f] vector, work out how many elements that corresponds to.
                    ef = showSzCell{ii};
                    n(ii) = 1 + ( ef(2) - ef(1) );
                else
                    error(message('parallel:array:DisplayHelperInvalidInput'));
                end
            end
        end
    end
    
    %% Public API
    methods
        
        % Construct with:
        % name: name of the variable;
        % classname: the "distributed" or whatever class;
        % value: the (truncated) value to be displayed;
        % fullsz: the full size of the underlying object;
        % showingsz: (optional arg) and a cell array describing the size of the
        % value. This cell array should be the same length as the full size,
        % and contain entries which are either ':' (implying no truncation),
        % or [e f] (which are the first and last indices in that dimension).
        %
        % 'showingsz' may be omitted if the value is a simple truncation of
        % the full array (i.e. from 1:size(value,dim) in each dimension)
        function obj = DisplayHelperDense( name, classname, value, fullsz, showingsz )
            % Set up base class properties.
            issparse = false;
            obj@parallel.internal.shared.DisplayHelper( ...
                name, classname, class(value), fullsz, issparse);
            
            % Now fill local properties.
            obj.Value = value;
            
            if nargin < 5
                obj.ShowingSizesCell = parallel.internal.shared.DisplayHelperDense.deriveShowingSizesCellFromValue( ...
                    value, length( obj.FullSize ) );
            else
                obj.ShowingSizesCell = showingsz;
                
                % Check that showingsz is the same length as FullSize
                if length( obj.ShowingSizesCell ) ~= length( obj.FullSize )
                    error(message('parallel:array:DisplayHelperInvalidArgument'));
                end
                
                % Resolve ':' in ShowingSizesCell
                for ii = 1:length( obj.ShowingSizesCell )
                    if ischar( obj.ShowingSizesCell{ii} ) && strcmp( obj.ShowingSizesCell{ii}, ':' )
                        obj.ShowingSizesCell{ii} = [1 obj.FullSize(ii)];
                    else
                        % Error check here - this is the only place where we've got user-supplied
                        % ShowingSizesCell to verify.
                        ef = obj.ShowingSizesCell{ii};
                        nThisDim = 1 + ( ef(2) - ef(1) );
                        if nThisDim == size( obj.Value, ii ) && nThisDim <= obj.FullSize(ii)
                            % ok
                        else
                            error(message('parallel:array:DisplayHelperInvalidArgument'));
                        end
                    end
                end
            end
            
            % Derive the NumGotInEachDim
            obj.NumGotInEachDim = parallel.internal.shared.DisplayHelperDense.deriveNumGotInEachDim( ...
                obj.ShowingSizesCell );
            
            % Finally, derive the IsTruncated flag.
            obj.IsTruncated = any( obj.NumGotInEachDim < obj.FullSize );
        end
        
        function doDisp( obj )
            %DODISP - public method to implement "disp"
            if ~obj.IsEmpty
                obj.denseDisplay( 'disp' );
            end
        end
        
        function doDisplay( obj )
            %DODISPLAY - public method to implement "display"
            if obj.IsEmpty
                obj.emptyDisplay();
            else
                obj.denseDisplay( 'display' );
            end
        end
    end
    
    %% Protected methods needed by superclass implementation
    methods ( Access = protected )
        function res = formatEndTruncationMsg( obj )
            % formatEndTruncationMsg - build up the message to show after the completion
            % of disp(lay).
            if obj.IsTruncated
                % Format the string describing what we're actually showing
                showingDescCell = repmat( {':'}, 1, length( obj.FullSize ) );
                for ii=1:length( obj.FullSize )
                    if obj.NumGotInEachDim(ii) ~= obj.FullSize(ii)
                        % XXX:SIMPLIFICATION - assuming elements of ShowingSizesCell are length 2
                        showing = obj.ShowingSizesCell{ii};
                        showingDescCell{ii} = sprintf( '%d:%d', showing(1), showing(2) );
                    end
                end
                % showingStr := '1:2, 1:3, 1:4, '
                showingStr = sprintf( '%s, ', showingDescCell{:} );
                showingStr = showingStr( 1:end-2 );
                
                % Format as M-by-N-by-P etc.
                fullSizeStr = parallel.internal.shared.dimensionDisplayHelper( obj.FullSize );
                
                % Format the whole truncation message
                res = getString(message('parallel:array:DisplayTruncatedSuffix', showingStr, fullSizeStr));
            else
                res = '';
            end
        end
    end
    
    %% Private implementation methods
    methods ( Access = private )
        function denseDisplay( obj, displayType )
            % Display what we've got by pages. If there are 3 or more dimensions in the
            % full array, we'll do the "d(...) = " line; otherwise, we'll do the
            % "d = " line.
            
            fullValueIsMultipage = ( length( obj.FullSize ) >= 3 );
            truncationMsg = obj.formatPageTruncationMsg();
            
            if fullValueIsMultipage
                % For multi-page output the type line comes first
                addSeparatorBeforeType = ~isempty( obj.Name );
                obj.typeLine( displayType, addSeparatorBeforeType, false );
                % Now display each page, including the name line
                truncatedValuePagesGot = prod( obj.NumGotInEachDim(3:end) );
                for localPageIdx = 1:truncatedValuePagesGot
                    page = obj.Value( :, :, localPageIdx );
                    obj.nameLine( displayType, obj.formatPageRange( localPageIdx ), truncationMsg );
                    obj.displayOnePage( displayType, page );
                end
            else
                % For a single page we display the name line first then
                % the type line.
                noPageRange = '';
                obj.nameLine( displayType, noPageRange, truncationMsg );
                if ~(ischar( obj.Value ) && isempty( obj.Name ))
                    % char display for anonymous single-page arrays skips the type line.
                    obj.typeLine( displayType, false, true );
                end
                obj.displayOnePage( displayType, obj.Value );
            end
            obj.showEndTruncationMessage();
        end
        
        function displayOnePage( obj, displayType, pageValue )
            % displayOnePage - print the values for one page
            
            % For type==display, modify pageValue for chars and scalar strings
            if isequal( displayType, 'display' )
                if isstring( pageValue ) && isscalar( pageValue ) && ~ismissing( pageValue )
                    pageValue = iIndentScalarStringText( obj, pageValue );
                elseif ischar( pageValue ) && ~isempty(obj.Name)
                    pageValue = iIndentCharText( obj, pageValue );
                end
            end
            disp( pageValue );
        end
        
        function globalPageRange = formatPageRange( obj, localPageIdx )
            % Given: a page index into the local value, figure out the global page
            
            % For example: if the global size is [3, 4, 5, 6]; and the ShowingSizesCell
            % is something like: { [2, 3], [2, 4], [2, 4], [1, 2] }, then the
            % local value has 6 pages, the first of which corresponds to the
            % global page (2:3,2:4,2,1).
            
            % SO: What we need to do is work out the local subscripts of the top-left
            % of this local page, and then offset that back up to the global indices
            
            % XXX:SIMPLIFICATION: consider only 2-element ShowingSizesCell
            % entries.
            localIndexOfPageStart = 1 + ( ( localPageIdx - 1 ) * prod( obj.NumGotInEachDim(1:2) ) );
            
            localSubscriptsOfPageStart = cell( 1, length( obj.NumGotInEachDim ) );
            [localSubscriptsOfPageStart{:}] = ind2sub( obj.NumGotInEachDim, localIndexOfPageStart );
            % Convert from cell array to column vector of local subscripts
            localSubscriptsOfPageStart = vertcat( localSubscriptsOfPageStart{:} );
            
            % Now that we know the subscripts of the start of the page we're showing in
            % local co-ordinates, we need to offset that to work out the
            % subscripts in the global array.
            
            % Concatenate all the [e f] tiny vectors to an overall offset matrix - the
            % offset we want is all the "e"s as a column vector.
            showingSzsAsMatrix = vertcat( obj.ShowingSizesCell{:} );
            
            % We need to add (e-1) to each local subscript to get the global subscript
            offsetsLocalToGlobal = showingSzsAsMatrix(:,1) - 1;
            
            % Now we can calculate the global subscripts of the top-left of the page
            globalSubscriptsOfPageStart = localSubscriptsOfPageStart + offsetsLocalToGlobal;
            
            % Finally, build the resulting string. NB: here, I'm hard-wiring the first
            % two dimensions to be ':'
            pageRange3ToEnd = sprintf( '%d,', globalSubscriptsOfPageStart(3:end) );
            pageRange3ToEnd = pageRange3ToEnd( 1:end-1 ); % remove trailing comma
            globalPageRange = sprintf( '(:,:,%s)', pageRange3ToEnd );
        end
        
        function res = formatPageTruncationMsg( obj )
            if any( obj.FullSize(1:2) > obj.NumGotInEachDim(1:2) )
                % XXX:SIMPLIFICATION - assuming elements of ShowingSizesCell are length 2
                ssc1 = obj.ShowingSizesCell{1};
                ssc2 = obj.ShowingSizesCell{2};
                res = getString(message('parallel:array:DisplayTruncatedPrefix', ...
                    ssc1(1), ssc1(2), ssc2(1), ssc2(2), ...
                    obj.FullSize(1), obj.FullSize(2) ));
            else
                % Not truncated
                res = '';
            end
        end
        
        function emptyDisplay( obj )
            % Must be doing "display", so always show the name.
            noTruncMessage = '';
            noRangeStr     = '';
            obj.nameLine( 'display', noRangeStr, noTruncMessage );
            % Special case for []
            if obj.IsSquareEmpty
                emptyMsg = '   []';
                fprintf( 1, '  %s\n%s', emptyMsg, obj.separator() );
            else
                % Always display type line for empties other than []
                obj.typeLine( 'display', false, true );
            end
        end
    end
end

function indentedText = iIndentScalarStringText(obj, stringText)
% Add leading spaces to each line for a scalar string. Non-scalar strings work
% fine using the "disp" version of the data.
indentedText = sprintf('    "%s"%s', ...
    strrep(stringText, newline, sprintf('\n     ')), obj.separator());
end

function indentedText = iIndentCharText(obj, charText)
% Indent char data for display. "disp" output is no use here.
if isrow(charText)
    % A single char vector - in this case, we simply need to indent the text and add
    % quotes.
    indentedText = sprintf('    ''%s''%s', ...
        strrep(charText, newline, sprintf('\n     ')), obj.separator());
else
    % Start by converting the char array into a string array, using NUM2CELL to
    % reduce each row into a cell.
    stringRows   = string(num2cell(charText, 2)); %#ok<NASGU> used in EVALC
    
    % Here we are relying on string/disp to do the right thing in terms of adding
    % linefeed and arrow characters.
    indentedText = evalc('disp(stringRows)');
    % evalc('disp()') adds a newlines that needs to be removed
    indentedText = indentedText(1:end-1);
    
    % Fix up quotes - change " to '.
    indentedText = regexprep(indentedText, '^(\s+)"', '$1''', 'lineanchors');
    indentedText = regexprep(indentedText, '"$', '''', 'lineanchors');
    
    % Fix up whitespace added by string display to match what should be emitted by
    % char display.
    indentedText = regexprep(indentedText, '= $', '=', 'lineanchors');
end
end
