function str = mat2str(matrix, varargin)
%MAT2STR Represent matrix as character vector in MATLAB syntax
%   STR = MAT2STR(MAT) represents the matrix MAT as a character
%   vector so that EVAL(STR) produces the original matrix (to
%   within 15 digits of precision). Conversions of non-scalar matrices 
%   contain brackets [].
%
%   STR = MAT2STR(MAT,N) uses N digits of precision.
%
%   STR = MAT2STR(MAT, 'class') creates a character vector with the name of 
%   the class of MAT included.  This option ensures that the result of evaluating 
%   STR will also contain the class information.
%
%   STR = MAT2STR(MAT, N, 'class') uses N digits of precision and includes
%   the class information.
%
%   Example
%       mat2str(magic(3)) produces the character vector '[8 1 6; 3 5 7; 4 9 2]'.
%       a = int8(magic(3))
%       mat2str(a,'class') produces the character vector
%                  'int8([8 1 6; 3 5 7; 4 9 2])'.
%
%   See also NUM2STR, INT2STR, SPRINTF, CLASS, EVAL.

%   Copyright 1984-2017 The MathWorks, Inc.

if nargin > 1
    [varargin{:}] = convertStringsToChars(varargin{:});
end

narginchk(1,3);

precision = 15;
precisionSpecified = false;
useclass  = false;

for i = 1:numel(varargin)
    
    if ischar(varargin{i})
        switch lower(varargin{i})
        case 'class'
            useclass = true; 
        otherwise
            error(message('MATLAB:mat2str:InvalidOptionString', varargin{ i }));
        end
    elseif isnumeric(varargin{i})

        precision = varargin{i};
        
        if ~isscalar(precision) || ~isreal(precision) || ~isfinite(precision) || floor(precision) ~= precision || precision < 1
            error(message('MATLAB:mat2str:InvalidPrecision'));   
        end
        
        precisionSpecified = true;
    else
        error(message('MATLAB:mat2str:InvalidOptionType'));    
    end
end

if ~ismatrix(matrix)
    error(message('MATLAB:mat2str:TwoDInput'));
end

enumerationFlag = isenumeration(matrix);

if ~(isnumeric(matrix) || ischar(matrix) || islogical(matrix) || enumerationFlag || isstring(matrix))
    error(message('MATLAB:mat2str:NumericInput'));
end

if enumerationFlag || isstring(matrix) || ischar(matrix)
    useclass = false;
end

[rows, cols] = size(matrix);

if issparse(matrix)
    [i,j,s] = find(matrix);
    str = ['sparse(' mat2str(i) ', ' mat2str(j), ', '];
    if useclass
        str = [str mat2str(s, precision, 'class')];
    else
        str = [str mat2str(s, precision)];
    end
    str = [str ', ' mat2str(rows) ', ' mat2str(cols) ')'];
    return;
end

str = '';

if useclass
    str = [class(matrix), '('];
elseif ischar(matrix) && ~isempty(matrix)
    values = cell(rows,1); 
    for row=1:rows
        values{row} = matrix(row,:);
    end
    needsConcatenation = rows > 1;

    dangerousPattern  =  '[\0\n-\r]';
    hasDangerousChars = regexp(values, dangerousPattern, 'once');

    needsConcatenation = needsConcatenation | ~isempty([hasDangerousChars{:}]);

    values = replace(values, "'", "''");
    if ~isempty([hasDangerousChars{:}])
        values = regexprep(values, dangerousPattern, "' ${sprintf('char(%d)',$0)} '");
    end

    if needsConcatenation
        str = '[';
    else
        str = '';
    end

    str = [str '''' values{1} ''''];

    for row = 2:rows
        str = [str ';''' values{row} '''']; %#ok 
    end

    if needsConcatenation
        str = [str ']'];
    end

    return;
elseif isstring(matrix)
    
    specialValues  = ["\0"; "\n"; "\v"; "\f"; "\r"];
    
    needsEscape = contains(matrix, compose(specialValues));
    needsEscape = any(needsEscape,'all');
    
    if needsEscape
        specialValues = [specialValues; "\\"];
    else
        specialValues = [];
    end
    
    finalValues = [specialValues; '""'];
    composedValues = compose([specialValues; '"']);
    
    matrix = replace(matrix, composedValues, finalValues);
    matrix = '"' + matrix + '"';
    
    matrix(ismissing(matrix)) = "string(missing)";
    
    if isempty(matrix)
        matrix = "strings(" + join(string(size(matrix)),',') + ")"; 
    elseif ~isscalar(matrix)
        matrix = join(matrix,' ',2);
        matrix = join(matrix,';',1);
        matrix = "[" + matrix + "]"; 
    end
    
    if needsEscape
       matrix = "compose(" + matrix + ")"; 
    end
    
    str = char(matrix);
    return; 
end

if isempty(matrix)
    if enumerationFlag
        str = [str class(matrix) '.empty(' int2str(rows) ',' int2str(cols) ')'];
    elseif (rows==0) && (cols==0)
        if ischar(matrix)
            str = [str ''''''];
        else
            str = [str '[]'];
        end
    else
        str = [str 'zeros(' int2str(rows) ',' int2str(cols) ')'];
    end
    if useclass
        str = [str, ')'];
    end
    return;
end

if isfloat(matrix) && ~enumerationFlag
    matrix = 0+matrix;  % Remove negative zero
end

if ~isscalar(matrix)
    str = str + "[";
end

numericFormat = "%." + precision + "g";
if class(matrix) == "uint64" && formatNotPreciseEnough(matrix, precision, precisionSpecified)
    numericFormat = "%u";
elseif class(matrix) == "int64" && formatNotPreciseEnough(matrix, precision, precisionSpecified)
    numericFormat = "%d";
end

if enumerationFlag
    values = class(matrix) + "." + string(matrix);
elseif islogical(matrix)
    values = string(matrix);
elseif isreal(matrix)
    values = compose(numericFormat, matrix);
else
    realVal = real(matrix);
    imagVal = imag(matrix);

    isImagNegative = imagVal < 0;
    
    imagVal = abs(imagVal);

    realStr = compose(numericFormat, realVal);
    imagStr = compose(numericFormat, imagVal);

    isFinite = isfinite(imagVal);

    imagStr( isFinite) = imagStr(isFinite) + "i";
    imagStr(~isFinite) = "1i*" + imagStr(~isFinite);

    imagStr( isImagNegative) = "-" + imagStr( isImagNegative);
    imagStr(~isImagNegative) = "+" + imagStr(~isImagNegative);

    imagStr(imagVal == 0) = "";
    
    values = realStr + imagStr;
end

values = join(values,' ',2);
values = join(values,';',1);

str = str + values;

% clean up the end of the string
if ~isscalar(matrix)
    str = str + "]";
end

if useclass
    str = str + ")";
end

str = char(str);

end

function b = isenumeration(m)
    b = ~isempty(enumeration(class(m)));
end

function n = getNumOfDigits(x)
    pows = uint64(10).^uint64(1:20);
    n = find(x <= pows, 1);
end

function tf = formatNotPreciseEnough(matrix, precision, precisionSpecified)
    tf = ~precisionSpecified || precision >= getNumOfDigits(max(matrix, [],'all'));
end