classdef (HandleCompatible, Abstract, Hidden) CustomCompactDisplayProvider
    % CustomCompactDisplayProvider Display customization for
    % objects displaying within a container or under space constraints.
    % This class provides a display customization interface for customizing  
    % the appearance of a class display when instances of the class are 
    % shown within a container or under space constraints.
    %
    % Derive your class from CustomCompactDisplayProvider to add a custom 
    % compact display for your class. 
    %
    % "Compact display" refers to the way objects are displayed under space
    % constraints or within a MATLAB container such as a struct or table
    % When the object is displayed in one of these situations, MATLAB
    % calls one of the methods of this utility class
    % to determine how to display your object.
    %
    % CustomCompactDisplayProvider provides methods 
    % that can be implemented as needed to provide a customized compact
    % display representation. By default, compact object display uses a
    % dimensions and class name object representation. 
    %
    % Compact display can be categorized based on the shape constraints
    % that are imposed on the object to be displayed.  
    % Tooltips, cell arrays, and structs require that the object is
    % displayed on a single line.  The following
    % shape constraint (or layout) is supported:
    %   - single line layouts. The object is required to display within a
    %                          single line. This is the case for struct,
    %                          cell and object display
    %
    %  Override one of the following methods to provide a customized 
    %  compact display representation for your class, and to specify  
    %  how much space you are requesting to display 
    %  your object.
    %
    %  CustomCompactDisplayProvider methods:
    %    compactRepresentationForSingleLine - Display for row-based data 
    %                                         structures (struct, cell, other objects)
    %
    % The compactRepresentationForSingleLine method must return a
    % matlab.display.internal.CompactDisplayRepresentation object specifiying 
    % the display representation that the class supports based on the 
    % available width.
    % MATLAB containers use the information in the CompactDisplayRepresentation 
    % object to build the string used to represent your object.
    % 
    % compactRepresentationForSingleLine takes three inputs:
    % - obj: The instance of your class that needs to be displayed
    % - displayConfiguration: a matlab.display.internal.DisplayConfiguration object 
    %             that describes the current display 
    %             environment, such as the current numeric display format 
    %             and whether hyperlinks are enabled.
    % - width: available width to display the contained object
    %
    % See also matlab.display.internal.CompactDisplayRepresentation,
    % matlab.display.internal.DisplayConfiguration
    %
    
    % Copyright 2020 The MathWorks, Inc.   
    
    methods  
        function rep = compactRepresentationForSingleLine(obj, displayConfiguration, width)
        % compactRepresentationForSingleLine Single-line display representation of an object
        %
        % rep = compactRepresentationForSingleLine(obj, displayConfiguration, width)
        % is called by a consumer (e.g. struct, table) to obtain the 
        % compact display representation for MATLAB object OBJ when the 
        % shape constraints require that a single line be used.
        % REP is a scalar
        % matlab.display.CompactDisplayRepresentation object specifying
        % the display representation for the class
        % 
        % The default single-line display representation for an object is  
        % dimensions plus class name.  Override 
        % compactRepresentationForSingleLine to provide 
        % a custom single-line compact display.  This function is
        % primarily used when your object is displayed within a MATLAB
        % struct, cell array, or another object.
        %
        % See also
        % matlab.mixin.CustomCompactDisplayProvider/compactRepresentationForColumn
        arguments
            obj matlab.mixin.internal.CustomCompactDisplayProvider
            displayConfiguration (1,1) matlab.display.internal.DisplayConfiguration
            width (1,1) double
        end
            import matlab.display.internal.DimensionsAndClassNameRepresentation;

            rep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, 'UseSimpleName', true);
        end
    end
    
    methods(Sealed)    
        function displayRep = fullDataRepresentation(obj, displayConfiguration, optionalParams)
        % fullDataRepresentation builds the full data representation of the input
        % object, OBJ
        %
        % displayRep = fullDataRepresentation(obj, displayConfiguration, p)
        % Builds the full data string representation for the input object, 
        % OBJ, by getting its underlying textual representation using either 
        % the string converter or the specified optional input parameter,
        % StringArray.
            arguments
                obj matlab.mixin.internal.CustomCompactDisplayProvider
                displayConfiguration (1,1) matlab.display.internal.DisplayConfiguration
                % Optional parameters
                % Underlying textual representation of the input object
                optionalParams.StringArray string = string(obj)
                % Optional text description displayed next to the object
                % data
                optionalParams.Annotation (1,1) string = ""
            end
            
            import matlab.display.internal.PlainTextRepresentation;
            import matlab.display.internal.DimensionsAndClassNameRepresentation;
            
            if isrow(obj) && ~isempty(obj)
                % Input object, OBJ, should have at most two dimensinos and 
                % be a row vector with at least one element to display to
                % build a valid PlainTextRepresentation object
                stringRep = "";
                last = numel(optionalParams.StringArray);
                pad = displayConfiguration.InterElementPadding;
                for i = 1:last
                    if i == last
                        pad = "";  % no spaces after last element
                    end
                    temp = optionalParams.StringArray(i);
                    stringRep = stringRep + temp + pad ;
                end
                displayRep = PlainTextRepresentation(stringRep, displayConfiguration, 'Annotation',optionalParams.Annotation);
            else
                % If input object, OBJ, has more than 2 dimensions, or more
                % than one row, or is empty
                % fullDataRepresentation will default to
                % DimensionsAndClassNameRepresentation
                displayRep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, 'Annotation',optionalParams.Annotation);
            end
        end
        
        function displayRep =  partialDataRepresentation(obj, displayConfiguration, first, last, optionalParams)
        % partialDataRepresentation builds the partial data string for the input
        % string arrays
        %
        % displayRep = partialDataRepresentation(obj, displayConfiguration,
        % first, last, p) builds the partial data for the input string arrays
        % by  first invoking fullDataRepresentation on both, first and last input 
        % arguments and concatenating them together with an ellipsis in 
        % between. first and last correspond to the string arrays of the 
        % first and last sub-set of elements to be displayed. If only one 
        % input string array is provided, partialDataRepresentation adds the appropriate 
        % padding to the input string array and includes an ellipsis at
        % the end.
        %
        % The following is an output example from this function:
        % 
        % partialDataRepresentation(obj, displayConfiguration, ["cat" "dog"], ["rat"])
        % 
        % displayRep = "cat  dog  ...  rat"
        %
        % partialDataRepresentation(obj, displayConfiguration, ["cat" "dog"])
        %
        % displayRep = "cat  dog  ..."
            arguments
                obj matlab.mixin.internal.CustomCompactDisplayProvider
                displayConfiguration (1,1) matlab.display.internal.DisplayConfiguration
                % Textual representation of the sub-set of data from the
                % input object that needs to be displayed before the
                % ellipsis character
                first (1, :) string
                % Textual representation of the sub-set of data from the
                % input object that needs to be displayed after the
                % ellipsis character
                last string = ""
                % Optional text description displayed next to the object
                % data
                optionalParams.Annotation (1,1) string = ""
            end
        
            import matlab.display.internal.PlainTextRepresentation;
            import matlab.display.internal.DimensionsAndClassNameRepresentation;
        
            ellipsisWithSpaces = displayConfiguration.InterElementPadding + displayConfiguration.Ellipsis + displayConfiguration.InterElementPadding;
            
            if isrow(obj) && ~isempty(obj) && ~isempty(first) && all(strlength(first) ~= 0) && all(~ismissing(first))
                % Input object, OBJ, should have at most two dimensinos and 
                % be a row vector with at least one element to display to
                % build a valid PlainTextRepresentation object
                if ~isempty(last) && all(strlength(last) ~= 0) && all(~ismissing(last))
                    firstRep = fullDataRepresentation(obj, displayConfiguration, 'StringArray', first);
                    lastRep = fullDataRepresentation(obj, displayConfiguration, 'StringArray', last);
            
                    stringRep = firstRep.Representation + ellipsisWithSpaces + lastRep.Representation;
                else
                    firstRep = fullDataRepresentation(obj, displayConfiguration, 'StringArray', first);
            
                    stringRep = firstRep.Representation + ellipsisWithSpaces;
                end
                displayRep = PlainTextRepresentation(stringRep, displayConfiguration, 'Annotation',optionalParams.Annotation);
            else
                % If input object, OBJ, has more than 2 dimensions, or more
                % than one row, or is empty
                % partialDataRepresentation will default to
                % DimensionsAndClassNameRepresentation
                displayRep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, 'Annotation',optionalParams.Annotation);
            end
        end
        
        function [displayRep, numElemsUsed] = widthConstrainedDataRepresentation(obj, displayConfiguration, width, optionalParams)
        % widthConstrainedDataRepresentation builds the full/ partial 
        % data string representation of the input object based on the
        % available width
        %
        % [displayRep, numElemsUsed] =
        %                        widthConstrainedDataRepresentation
        %                        (obj, displayConfiguration, width, p)
        % Builds the full/ partial data representation for the input 
        % object, OBJ, by getting the underlying string array using the string
        % converter and adding the appropriate padding in between each 
        % element. The function attempts to fit as many elements of the 
        % array as possible based on the input width. If it cannot fit all the 
        % elements, it adds an ellipsis at the end of the output string. 
        % Otherwise, the output string shows all the element in the string 
        % array with the appropriate padding.
        % 
        % The following are two sample outputs for the function:
        % 
        % widthConstrainedDataRepresentation
        % (obj, displayConfiguration, ["dog" "cat" "bird"], 8)
        % 
        % displayRep.paddedDataString() = "dog  ..."
        % numElemsUsed = 1
        %
        % widthConstrainedDataRepresentation
        % (obj, displayConfiguration, ["dog "cat" "bird"], 14)
        % 
        % displayRep.paddedDataString = "dog  cat  bird"
        % numElemsUsed = 3
            arguments
                obj matlab.mixin.internal.CustomCompactDisplayProvider
                displayConfiguration (1,1) matlab.display.internal.DisplayConfiguration
                width (1,1) double
                % Optional parameters
                % Textual representation of the input object if string
                % method is not sufficient
                optionalParams.StringArray string = string(obj)
                % Optional text description displayed next to object data
                optionalParams.Annotation (1,1) string = ""
                % Flag to indicate if widthConstrainedDataRepresentation
                % should truncate scalar object based on available space
                optionalParams.TruncateScalarObject (1,1) logical = false
                % Minimum number of elements that must be displayed before
                % defaulting to dimensions and class name
                optionalParams.MinimumElementsToDisplay (1,1) uint8 = 1;
            end
            import matlab.display.internal.PlainTextRepresentation;
            import matlab.display.internal.DimensionsAndClassNameRepresentation;
            
            if ~isequal(optionalParams.Annotation, "") && ~isempty(optionalParams.Annotation) ...
                    && ~ismissing(optionalParams.Annotation)
                % Compute annotation character width if it is set to
                % a non-empty string
                annotationWidth = columnCount(displayConfiguration, optionalParams.Annotation) + ...
                    columnCount(displayConfiguration, displayConfiguration.AnnotationPadding) + ...
                    columnCount(displayConfiguration, displayConfiguration.AnnotationDelimiters);
            else
                annotationWidth = 0;
            end
            
            if isrow(obj) && ~isempty(obj)
                % Input object, OBJ, should have at most two dimensinos and 
                % be a row vector with at least one element to display to
                % build a valid PlainTextRepresentation object
                
                pad = displayConfiguration.InterElementPadding;
                padWidth = columnCount(displayConfiguration, pad);
                dataDelimitersWidth = columnCount(displayConfiguration, displayConfiguration.DataDelimiters);
                ellipsisWithSpaces = displayConfiguration.Ellipsis + pad;
                ellipsisWithSpacesWidth = columnCount(displayConfiguration, ellipsisWithSpaces);
                numElemsUsed = 0;
                
                if optionalParams.TruncateScalarObject && isscalar(obj)
                    % If the input object to display is a scalar and
                    % TruncateScalarObject flag is set to true, attempt to
                    % truncate the string representation of the object to
                    % fit as much as possible and append an ellipsis at the
                    % end if the object string cannot be displayed in full
                    
                    % Attempt to fit the entire object in the available
                    % width
                    displayRep = PlainTextRepresentation(optionalParams.StringArray, displayConfiguration, "Annotation", optionalParams.Annotation);
                    if displayRep.CharacterWidth > width
                        % If the scalar object does not fit in available
                        % width, attempt to truncate it
                        effectiveWidth = width - annotationWidth - padWidth - ellipsisWithSpacesWidth - dataDelimitersWidth;
                        if effectiveWidth > 0
                            charObj = char(optionalParams.StringArray);
                            stringRep = string(charObj(1:effectiveWidth)) + pad + ellipsisWithSpaces;
                            numElemsUsed = 1;
                            displayRep = PlainTextRepresentation(stringRep, displayConfiguration, "Annotation", optionalParams.Annotation);
                        else
                            % If there is not enough space to fit at least
                            % one character of the object textual
                            % representation, default to dimensions and
                            % class name
                            displayRep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, "Annotation", optionalParams.Annotation);
                        end
                    end
                else
                    % If the input object is not scalar or 
                    % TruncateScalar object is not set to true, attempt to fit as
                    % many elements as possible of the array in the
                    % available width without truncating any of them
                    stringRep = "";
                    last = numel(optionalParams.StringArray);
                    for i = 1:last
                        if i == last
                            % If we are at the last element, no ellipsis is
                            % required at the end of the output string
                            temp = stringRep + optionalParams.StringArray(i);
                            effectiveWidth = width - annotationWidth - dataDelimitersWidth;
                        else
                            % If this is not the last element of the object
                            % string array, an ellipsis needs to be appended at
                            % the end of the output string
                            temp = stringRep + optionalParams.StringArray(i) + pad;
                            effectiveWidth = width - annotationWidth - ellipsisWithSpacesWidth - dataDelimitersWidth;
                        end
                        if columnCount(displayConfiguration, temp) <= effectiveWidth 
                            % If the width of the resulting display string,
                            % stored in temp, is less than the available width
                            % overwrite the resulting output display string,
                            % stringRep, and continue iterating over the string
                            % array
                            stringRep = temp;
                            numElemsUsed = i;
                        else
                            % If the width of the resulting display string in
                            % temp has exceeded the available width stop the
                            % for loop and append an ellipsis at the end if
                            % applicable
                            if i ~= last || stringRep ~= char.empty
                                stringRep = stringRep + ellipsisWithSpaces;
                            end
                            break;
                        end
                    end
                    if numElemsUsed < optionalParams.MinimumElementsToDisplay
                        % Default to dimensions and class name if none of the
                        % elements fit in the available width or the number of
                        % elements fitting is less than the specified minimum
                        displayRep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, 'Annotation', optionalParams.Annotation);
                    else
                        displayRep = PlainTextRepresentation(stringRep, displayConfiguration, 'Annotation',optionalParams.Annotation);
                    end
                end
            else
                % If input object, OBJ, has more than 2 dimensions, or more
                % than one row, or is empty
                % partialDataRepresentation will default to
                % DimensionsAndClassNameRepresentation
                displayRep = DimensionsAndClassNameRepresentation(obj, displayConfiguration, 'Annotation',optionalParams.Annotation);
                numElemsUsed = 0;
            end
        end
    end
end

