function [line, ltout] = fgets(fileID, nchar)
%#codegen
%MATLAB Code Generation Library Function

% Limitations:
% - when EOF is hit, portions of the last fread might be truncated if there
% are null bytes in that section

%   Copyright 2019-2022 The MathWorks, Inc.


hasNchar = coder.const(nargin > 1);

if hasNchar
    %matlab allows non-integer sizes, just truncates down
    realNchar = real(nchar);
    coder.internal.assert(realNchar >= 0 , 'MATLAB:fgets:InvalidSize');
    coder.internal.assert(isinf(realNchar) || realNchar < intmax(coder.internal.indexIntClass), ...
        'Coder:toolbox:FgetsTooBig')
    if isinf(realNchar) %used to be reccomended by 'MATLAB:fgets:InvalidSize' (fixed by g1962521)
        numAlloc = MAXNUMEL;
        hasNchar = false;
    else
        numAlloc = coder.internal.indexInt(realNchar);
    end
else
    numAlloc = MAXNUMEL;
end

if isFileIOExtrinsic && hasNchar
    [line, ltout]  = matlabFGETS(fileID, true, nchar);
elseif isFileIOExtrinsic
    [line, ltout]  = matlabFGETS(fileID, false);
else
    [line, ltout] = cFgets(fileID, hasNchar, numAlloc);
end

end


function [out, endChars] = matlabFGETS(fileID, providedNchar, nchar)
coder.internal.prefer_const(providedNchar, nchar);
if providedNchar
    [matLine, matLT] = feval('fgets', fileID, nchar);
else
    [matLine, matLT] = feval('fgets', fileID);
end
failed = false; %#ok<NASGU>
failed = isa(matLine, 'double');

coder.internal.errorIf(failed, 'Coder:toolbox:NothingToRead');

%FIXME: is there any way to use nchar(when available) to limit the size
%of the output here?
if ~failed
    out = coder.nullcopy(blanks(coder.internal.ignoreRange(ONE))); %#ok<NASGU>
    matLine = reshape(matLine, 1, []);
    out = matLine;
    if ~isempty(matLT)
        endChars = coder.nullcopy(zeros(1,coder.internal.ignoreRange(2))); %#ok<NASGU>
        endChars = matLT;
    else
        endChars = zeros(1,0);
    end
    coder.internal.errorIf(providedNchar && coder.internal.isConst(nchar) && numel(out) > nchar, ...
        'Coder:builtins:Explicit', 'This check should never trigger - just bounding output size')
else
    out = blanks(ZERO);
    endChars = zeros(1,0);
end
end


function [line, ltout] = cFgets(fileID, providedNchar, numAlloc)
headerFile = coder.internal.getCHeaderName('stdio');
coder.cinclude(headerFile);

filestar = getFileStar(fileID);

numAlloc = numAlloc + ONE; % add space for null byte at the end
if providedNchar
    coder.varsize('outBuff');
    outBuff = blanks(numAlloc);
else
    outBuff = coder.nullcopy(eml_expand(blanks(1), ONE, coder.internal.ignoreRange(ZERO)));
end

if(filestar == NULL)
    ltout = zeros(1,0);
    line =  outBuff;
    return;
end
ltout = zeros(1,0);%this should always be overwritten, but infrence can't seem to figure that out. maybe it would be safer to use a worse default value?
while true
    if providedNchar
        coder.varsize('ReadBuff');
    end
    ReadBuff = coder.nullcopy(blanks(numAlloc));
    cOut = coder.opaquePtr('char');
    libMethodName = coder.internal.getCLibName('fgets');
    cOut = coder.ceval(libMethodName, coder.ref(ReadBuff), coder.internal.indexInt(numAlloc), filestar);%includes newline (if not eof) and null terminator
        
    readNewline = false;
    reachedEndOfFile = feof(fileID); %TODO: could refactor feof to make it possible to pass filestar in directly, skipping some steps (many of these may happen at compile time anyway?), created geck 2782976.
    
        coder.internal.errorIf(cOut == coder.opaquePtr('char', coder.internal.null) &&  isempty(outBuff), 'Coder:toolbox:NothingToRead'); %doesnt happen in standalone
    
    if cOut == coder.opaquePtr('char', coder.internal.null) %happens only when at EOF and there was nothing to be read.
        break;
    end
    
    idx = ONE;
    carriageReturnAt = ZERO;
    
    if reachedEndOfFile
        for i=ONE:numAlloc
            curChar = ReadBuff(i);
            if curChar == char(0)
                idx = i;
                break;
            end
            coder.internal.errorIf(carriageReturnAt == ZERO &&...
                (int32(curChar) < 0 || int32(curChar) > 127),...
                'Coder:toolbox:fgetslIllegalCharacter', int32(curChar));
            if carriageReturnAt == ZERO  && curChar == sprintf('\r')
                carriageReturnAt = i;
            end
        end
        if ReadBuff(idx) == char(0)
            idx = idx-1;
        end
    else
        for i=ONE:(numAlloc+ONE)
            if i > numAlloc %read the whole thing without a new line, trim off the trailing null byte
                idx=numAlloc-1;
                break;
            end
            curChar = ReadBuff(i);
            if curChar == newline
                idx = i;
                break;
            end
            coder.internal.errorIf(carriageReturnAt == ZERO &&...
                (int32(curChar) < 0 || int32(curChar) > 127),...
                'Coder:toolbox:fgetslIllegalCharacter', int32(curChar));
            if carriageReturnAt == ZERO  && curChar == sprintf('\r')
                carriageReturnAt = i;
            end
        end
        if ReadBuff(idx) == newline
            readNewline = true;
        end
    end
    if carriageReturnAt > ZERO && carriageReturnAt < numAlloc
        newLineAfterCarriageReturn = ReadBuff(carriageReturnAt+ONE) == newline;
        fileEndAfterCarriageReturn = reachedEndOfFile && ReadBuff(carriageReturnAt+ONE) == char(0);
    else
        newLineAfterCarriageReturn = false;
        fileEndAfterCarriageReturn = false;
    end
    if providedNchar
        if carriageReturnAt == ZERO || newLineAfterCarriageReturn || fileEndAfterCarriageReturn
            %MATLAB and C fgets agree, no need to adjust
            outBuff = coder.internal.reallocDynamicMatrix(ReadBuff, [1,idx]);
        else
            outBuff = coder.internal.reallocDynamicMatrix(ReadBuff, [1,carriageReturnAt]);
            fseek(fileID, carriageReturnAt - idx,  'cof');
        end
    else
        if carriageReturnAt == ZERO || newLineAfterCarriageReturn || fileEndAfterCarriageReturn
            %MATLAB and C fgets agree, no need to adjust
            outBuff = [outBuff, ReadBuff(ONE:idx)]; %#ok<AGROW>
        else
            outBuff = [outBuff, ReadBuff(ONE:carriageReturnAt)]; %#ok<AGROW>
            fseek(fileID, carriageReturnAt - idx,  'cof');
        end
    end
    if providedNchar || readNewline || reachedEndOfFile || carriageReturnAt > ZERO
        
        if newLineAfterCarriageReturn
            ltout = [13, 10];
        elseif carriageReturnAt > ZERO
            ltout = 13;
        elseif reachedEndOfFile
            ltout = zeros(1,0);
        else
            ltout = 10;
        end
        
        break;
    end
    
end
assert(size(outBuff,2) < numAlloc || ~providedNchar);


%make sure that we bump the file pointer forward when there is nothing else
%to read. This let's you do things like while(~feof){fgets}, as you can in
%MATLAB
if  ~feof(fileID)
    fread(fileID,1);
    if ~feof(fileID)
        fseek(fileID, -1, 'cof');
    end
end

line = outBuff;

end

%--------------------------------------------------------------------------

function N = MAXNUMEL
coder.inline('always');
N = coder.internal.indexInt(1023);
end

%--------------------------------------------------------------------------

function N = ONE
coder.inline('always');
N = ones(coder.internal.indexIntClass());
end

%--------------------------------------------------------------------------

function N = ZERO
coder.inline('always');
N = zeros(coder.internal.indexIntClass());
end

%--------------------------------------------------------------------------

function y = NULL
coder.inline('always');
cHeader = coder.internal.getCHeaderName('stdio');
cLibName = coder.internal.getCLibName('FILE');
y = coder.opaquePtr(cLibName,coder.internal.null,'HeaderFile',cHeader);
end


%--------------------------------------------------------------------------

function filestar = getFileStar(fileID)
% FGETS not supported on stdin, stdout or stderr.  Return NULL on error and
% report the error if possible

%this function was cribbed from fread - TODO: refactor to shared location

% Get the FILE * and assert validity
filestar = coder.internal.fileManager('filestar', fileID);
if (coder.internal.hasRuntimeErrors() && filestar == NULL)
    coder.internal.error('MATLAB:FileIO:InvalidFid');
end

% FREAD not supported for stdin, stdout or stderr
if (~isImplementedFilestar(fileID))
    filestar = NULL;
end
end

%--------------------------------------------------------------------------

function p = isImplementedFilestar(fileID)
p = fileID ~= 0 && fileID ~= 1 && fileID ~= 2;
end
