%DisplayHelperSparse - knows how to display partial sparse arrays.

% Copyright 2009-2019 The MathWorks, Inc.
classdef DisplayHelperSparse < parallel.internal.shared.DisplayHelper
    
    properties ( Access = private )
        % Store both nnz of the full distributed sparse, and the number of non-zeros
        % we're going to display.
        FullNNZ
        ShowingNZ
        
        % The values to display
        Value
    end
    
    methods
        function obj = DisplayHelperSparse( name, classname, value, fullSize, fullNNZ )
            % Sparse display works by trimming non-zeros, not changing the size of
            % distributed array. Perhaps this might need to change for
            % codistributed?
            
            % Set up base class properties.
            issparse = true;
            if iscell(value)
                underlyingClassName = class(value{3});
            else
                underlyingClassName = class(value);
            end
            obj@parallel.internal.shared.DisplayHelper( ...
                name, classname, underlyingClassName, fullSize, issparse);
            
            % Now fill local properties.
            obj.Value = value;
            obj.FullNNZ = fullNNZ;
            
            % Notice that VALUE is either a sparse array (if not
            % truncated) or a cell array containing the truncated
            % {i,j,value}-triplet.
            if iscell(obj.Value)
                obj.ShowingNZ   = numel( obj.Value{1} );
            else
                obj.ShowingNZ   = numel( obj.Value );
            end
            obj.IsTruncated = ( obj.ShowingNZ < obj.FullNNZ );
        end
    end
    
    methods ( Access = protected )
        function res = formatStartTruncationMsg( obj )
            if obj.IsTruncated
                res = getString(message('parallel:array:DisplayTruncatedPrefixSparse', ...
                    obj.ShowingNZ, obj.FullNNZ));
            else
                res = '';
            end
        end
        
        function res = formatEndTruncationMsg( obj )
            if obj.IsTruncated
                res = getString(message('parallel:array:DisplayTruncatedSuffixSparse', ...
                    obj.ShowingNZ, obj.FullNNZ));
            else
                res = '';
            end
        end
    end
    
    methods
        function doDisp( obj )
            if ~obj.IsEmpty
                obj.sparseDisplay( 'disp' );
            end
        end
        function doDisplay( obj )
            if obj.IsEmpty
                obj.emptyDisplay();
            else
                obj.sparseDisplay( 'display' );
            end
        end
    end
    
    methods ( Access = private )
        function emptyDisplay( obj )
            noPage         = '';
            noTruncMsg     = '';
            typeIsDisplay  = 'display';
            obj.nameLine( typeIsDisplay, noPage, noTruncMsg );
            obj.typeLine( typeIsDisplay, false, true );
        end
        function sparseDisplay( obj, displayType )
            % sparseDisplay - handle overall disp(lay) of sparse
            if strcmp( displayType, 'display' )
                noPage     = '';
                truncMsg = obj.formatStartTruncationMsg();
                obj.nameLine( displayType, noPage, truncMsg );
                obj.typeLine( displayType, false, true );
            end
            % OBJ.VALUE is either a sparse matrix (if not truncated) or a
            % cell array containing the truncated {i,j,value}-triplet. The
            % latter case uses the helper iDispSparseTruncated to replicate
            % the behavior of DISP on a sparse matrix.
            if iscell(obj.Value)
                iDispSparseTruncated( obj );
            else
                disp( obj.Value );
            end
            obj.showEndTruncationMessage();
        end
    end
end

function iDispSparseTruncated( obj )
% Helper that replicates behavior of
% matlab/src/services/io/printmat.cpp
% for sparse matrices if the matrix is given as {i,j,v}-triplet

ijvValueCell = obj.Value;
assert(all(cellfun(@iscolumn, ijvValueCell)), 'i, j, and v must be columns.');

% Print values into variable
txt = evalc('disp(ijvValueCell{3})');
lines = strsplit(txt, newline);

% Determine largest indices
m = max(ijvValueCell{1});
n = max(ijvValueCell{2});

% Deal with prepended scaling
value_offset = 0;
if contains(lines{1},'*')
    fprintf('%s\n%s', lines{1}, obj.separator());
    value_offset = 1;
end

% As described in matlab/src/services/io/printmat.cpp:1486ff
% lens is the length of the (i,j) string
% allow either 4 or 10 or 19 spaces each for i and j,
% plus 3 for (,), for a total of 11 or 23 or 41.
if m<1e4 && n<1e4
    lens = 11;
elseif m<1e5 && n<1e5
    lens = 23;
else
    lens = 41;
end

i_strings = string(ijvValueCell{1});
j_strings = string(ijvValueCell{2});
i_szs = strlength(i_strings);
j_szs = strlength(j_strings);

% Max number of digits in index i is 19 (2^64-1)
assert(max(i_szs)<=19, 'Max number of digits for sparse row index has been exceeded.');

% Loop over all lines and prepend the (i,j) coordinates to the value
for i=1:numel(ijvValueCell{1})
    % Calculate the leading and following spaces
    leading_spaces = (lens-1)/2 - i_szs(i) -1;
    following_spaces = lens - (leading_spaces + i_szs(i) + j_szs(i) + 3);
    
    % Print each line with the data, the (,) and all necessary spaces
    fprintf( '%s(%s,%s)%s%s\n', ...
        repmat(' ', 1, leading_spaces), ...
        i_strings(i), j_strings(i), ...
        repmat(' ', 1, following_spaces), ...
        lines{i+value_offset} );
end

% Add obj.separator() at the end
fprintf('%s', obj.separator());
end
