function writelines(lines, filename, options)
%WRITELINES Write lines to a file
%   WRITELINES(LINES, FILENAME) writes a plain text file named FILENAME
%                               with the contents of LINES as the lines of
%                               the file.
%
%   WRITELINES(___, Name, Value) specifies additional parameters using one
%                                or more name-value pair arguments.
%
%   LINES can be a string array, character vector, or cell array of
%   character vectors. Each element of the array LINES is written as a
%   separate line to the output file in column order.
%   I.e. as LINES = join(LINES(:), LineEnding)
%
%
%   FILENAME can be one of these:
%
%       - For local files, FILENAME can be an absolute path that contains
%         a filename and file extension. FILENAME can also be a relative
%         path from the current directory or a directory on the MATLAB
%         path. For example, to write to a file in pwd:
%
%            writelines("Sample string", "temp.txt");
%
%       - For files from an Internet URL or stored at a remote location,
%         FILENAME must be a full path using a Uniform Resource Locator
%         (URL). For example, to write a remote file to Amazon S3,
%         specify the full URL for the file:
%
%            writelines("Sample string", "s3://bucketname/path_to_file/temp.txt");
%
%         For more information on accessing remote data, see "Work with
%         Remote Data" in the documentation.
%
%   Name-Value Pairs:
%   --------------------------------------------------------
%   "LineEnding"             - The line ending for the file.
%                              Default is "\r\n" on Windows(R) or "\n" on
%                              UNIX(R) platforms.
%
%   "Encoding"               - The encoding to use when creating the file.
%                              Default is "UTF-8".
%
%   "WriteMode"              - Append to an existing file or overwrite an
%                              existing file.
%                                - "overwrite" - Overwrite the file, if it
%                                                exists. This is the default
%                                                option.
%                                - "append"    - Append to the bottom of the
%                                                file, if it exists. This
%                                                will start a new line in
%                                                the file if the existing
%                                                file doesn't already end
%                                                with the LineEnding
%                                                characters.
%
%   "TrailingLineEndingRule" - Behavior to describe handling trailing line
%                              endings in a file.
%                                - "auto"   - Ensures the file will end
%                                             with a trailing line ending.
%                                             If the TEXT would not
%                                             otherwise have one, an extra
%                                             line is appended.
%                                - "always" - Always append a line ending
%                                             to the file.
%                                - "never"  - Never append a line ending to
%                                             the file.
%
%  See also readlines, writematrix, fileread

% Copyright 2021-2023 The MathWorks, Inc.

arguments
    % Required input.
    lines {mustBeText};
    filename {mustBeTextScalar, mustBeNonzeroLengthText};

    % Name-value pair inputs.
    options.LineEnding {mustBeTextScalar, mustBeNonzeroLengthText} = getDefaultLineEnding();
    options.Encoding {mustBeTextScalar} = "UTF-8";
    options.WriteMode {mustBeTextScalar} = "overwrite";
    options.TrailingLineEndingRule {mustBeTextScalar} = "auto" ;
end

import matlab.io.internal.utility.validateAndEscapeCellStrings
import matlab.io.text.internal.write.FprintfErrorHandler
import matlab.io.internal.utility.FileHandleGuard

writeMode = validateWriteMode(options.WriteMode);
lineEnding = string(validateAndEscapeCellStrings(options.LineEnding));
trailingLineEndRule = validateTrailingLineEndingRule(options.TrailingLineEndingRule);

% open the file for writing (or appending)

guard = FileHandleGuard(filename, writeMode, "n", options.Encoding);

if ~guard.openSucceeded()
    error(message("MATLAB:textio:writelines:InvalidFileIdentifier"));
end

lines = handleTrailingLines(lines,...
    guard.FileID,trailingLineEndRule,writeMode,...
    filename,lineEnding);
if ~isempty(lines)
    lines = join(lines(:),lineEnding);
end

fprintf(guard.FileID, "%s", lines);
[msg, errNum] = ferror(guard.FileID);

if ~isempty(msg) || errNum
    FprintfErrorHandler(guard.FileID, filename, errNum);
end
end

function lines = handleTrailingLines(lines,fid,trailingLineEndRule,writeMode,filename,lineEnding)
% handle the adding of new-lines
import matlab.io.text.internal.write.appendNewlineToEOFIfNeeded

lines = string(lines);
lines = lines(:);
lines(ismissing(lines)) = "";

% if append mode, add a newline to the end of the file so writes start
% from the next line
if writeMode == "a+" && ~(trailingLineEndRule == "never" && numel(lines) == 0)
    % If "WriteMode" == "append", ensure the text file ends
    % with a line ending character so that new rows are appended
    % beneath the existing file contents, rather than starting on the last
    % line.
    isEmpty = appendNewlineToEOFIfNeeded(filename, fid, ...
        "append", lineEnding);
else
    isEmpty = true;
end

if numel(lines) == 0
    % An empty file won't get a new line added
    % A non-empty file that doesn't end in a new line will have one added.
    if trailingLineEndRule == "always" ...
            || (trailingLineEndRule == "auto" && isEmpty)
        lines = lineEnding;
    end
else
    if trailingLineEndRule == "always" ...
            || (trailingLineEndRule == "auto" ...
            && needsTrailingLines(lines,lineEnding))
        lines(end+1) = "";
    end
end
end

function writeMode = validateWriteMode(writeMode)
writeMode = validatestring(writeMode, ["overwrite", "append"]);
if writeMode == "overwrite"
    writeMode = "W";
elseif writeMode == "append"
    writeMode = "a+";
end
end

function trailLineEndRule = validateTrailingLineEndingRule(trailLineEndRule)
trailLineEndRule = validatestring(trailLineEndRule,["auto", "always", "never"]);
end

function lineEnding = getDefaultLineEnding()
if ispc
    lineEnding = "\r\n";
else
    lineEnding = "\n";
end
end

function tf = needsTrailingLines(lines,lineEnding)
% check if there is a line ending at end of input, if not, add
% one. For LineEnding "\n", it gets converted to "" (special
% case)
n = strlength(lines(end));
tf = (n > 0  && ~endsWith(lines(end),lineEnding)) ...
    || (numel(lines) == 1  && n == 0);
end