//
// This function include is meant to be used as an inner function!
//
// It requires the following variables in the outer scope:
//  var byteBuffer: AnsiString; bufByteCount, byteBufChPos: Integer;
// 
// These variables must be initialized as follows in the outer code before calling ReadNextLine:
//   SetLength(byteBuffer, cDfltCopyBufferSize);
//   bufByteCount := 0; 
//   byteBufChPos := 1;
//

function ReadNextLine(const ASrcStream: ISequentialStream; const ACharKind: TGMCharKind; var ALine: TGMString; var ALineEnd: EGMLineEndKind; const ACaller: TObject): Boolean;
const cLineEndLF: array [Boolean] of EGMLineEndKind = (lekLF, leLFCR);
      cLineEndCR: array [Boolean] of EGMLineEndKind = (lekCR, lekCRLF);
var byteLine: RawByteString; unicodeLine: TGMString; readMore: Boolean;

  //
  // Does NOT recognize single CR as a line break! All other kinds of line breaks will be recognized: CRLF | LFCR | LF
  //
  //function ReadAnsiLine: AnsiString;
  //var lnStart, lnEnd: PAnsiChar; lenInBytes, addOffs: PtrInt;
  //  procedure StripLastChar; // (ACharCount: Integer);
  //  begin
  //    Inc(addOffs, SizeOf(AnsiChar));
  //    Dec(lenInBytes, SizeOf(AnsiChar));
  //  end;
  //begin
  //  addOffs := 0;
  //  lnStart := @byteBuffer[byteBufChPos];
  //  lnEnd := GMStrLScanA(lnStart, #10, Length(byteBuffer) - byteBufChPos + 1);
  //  if lnEnd = nil then
  //   begin
  //    lenInBytes := Length(byteBuffer) - byteBufChPos + 1;
  //    //if (lnStart + (lenInBytes div SizeOf(AnsiChar)) - 1)^ = #13 then StripLastChar; // <- line end chars split by buffer size
  //    if (lnStart + lenInBytes - 1)^ = #13 then StripLastChar; // <- line end chars split by buffer size
  //    readMore := True;
  //   end
  //  else
  //   begin
  //    lenInBytes := lnEnd - lnStart;
  //    addOffs := SizeOf(AnsiChar);
  //    if (lnEnd > lnStart) and ((lnEnd - 1)^ = #13) then StripLastChar else
  //      if ((lnEnd + 1)^ = #13) then Inc(addOffs, SizeOf(AnsiChar));
  //   end;
  //
  //  SetLength(Result, lenInBytes);
  //  if lenInBytes > 0 then System.Move(byteBuffer[byteBufChPos], Result[1], lenInBytes);
  //  Inc(byteBufChPos, lenInBytes + addOffs);
  //end;

  //
  // Recognizes all kinds of line endings, not slower than "GMStrLScanA" version
  //
  function ReadByteLine: RawByteString;
  const cLineEndSize: array [Boolean] of PtrInt = (SizeOf(AnsiChar), 2 * SizeOf(AnsiChar));
  var lnStart, lnEnd: PAnsiChar; lenInBytes, addOffs: PtrInt; i: LongWord; twoChs: Boolean;
  begin
    lnStart := Pointer(@byteBuffer[byteBufChPos]);
    lnEnd := lnStart; addOffs := 0;

    //while True do
    for i:=0 to High(i) do
     case lnEnd^ of
      #0: begin lnEnd := nil; Break; end;
      #10: begin twoChs := (lnEnd + 1)^ = #13; Inc(addOffs, cLineEndSize[twoChs]); ALineEnd := cLineEndLF[twoChs]; Break; end;
      #13: begin twoChs := (lnEnd + 1)^ = #10; Inc(addOffs, cLineEndSize[twoChs]); ALineEnd := cLineEndCR[twoChs]; Break; end;
      else Inc(lnEnd);
     end;

    if lnEnd <> nil then lenInBytes := (lnEnd - lnStart) * SizeOf(AnsiChar) else
     begin
      lenInBytes := Length(byteBuffer) - byteBufChPos + 1;
      readMore := True;
     end;

    SetLength(Result, lenInBytes);
    if lenInBytes > 0 then System.Move(byteBuffer[byteBufChPos], Result[1], lenInBytes);
    Inc(byteBufChPos, lenInBytes + addOffs);
  end;

  //
  // Does NOT recognize single CR as a line break! All other kinds of line breaks will be recognized: CRLF | LFCR | LF
  //
  //function ReadUnicodeLine: UnicodeString;
  //var lnStart, lnEnd: PWideChar; lenInBytes, addOffs: PtrInt;
  //  procedure StripLastChar; // (ACharCount: Integer);
  //  begin
  //    Inc(addOffs, SizeOf(UnicodeChar));
  //    Dec(lenInBytes, SizeOf(UnicodeChar));
  //  end;
  //begin
  //  addOffs := 0;
  //  lnStart := Pointer(@byteBuffer[byteBufChPos]);
  //  lnEnd := GMStrLScanW(lnStart, #10, (Length(byteBuffer) - byteBufChPos + 1) div SizeOf(UnicodeChar));
  //  if lnEnd = nil then
  //   begin
  //    lenInBytes := Length(byteBuffer) - byteBufChPos + 1;
  //    if (lnStart + (lenInBytes div SizeOf(UnicodeChar)) - 1)^ = #13 then StripLastChar; // <- line end chars split by buffer size
  //    readMore := True;
  //   end
  //  else
  //   begin
  //    lenInBytes := (lnEnd - lnStart) * SizeOf(UnicodeChar);
  //    addOffs := SizeOf(UnicodeChar);
  //    if (lnEnd > lnStart) and ((lnEnd - 1)^ = #13) then StripLastChar else
  //      if ((lnEnd + 1)^ = #13) then Inc(addOffs, SizeOf(UnicodeChar));
  //   end;
  //
  //  SetLength(Result, lenInBytes div SizeOf(UnicodeChar));
  //  if lenInBytes > 0 then System.Move(byteBuffer[byteBufChPos], Result[1], lenInBytes);
  //  Inc(byteBufChPos, lenInBytes + addOffs);
  //end;

  //
  // Recognizes all kinds of line endings, not slower than "GMStrLScanW" version
  //
  function ReadUnicodeLine: UnicodeString;
  const cLineEndSize: array [Boolean] of PtrInt = (SizeOf(UnicodeChar), 2 * SizeOf(UnicodeChar));
  var lnStart, lnEnd: PWideChar; lenInBytes, addOffs: PtrInt; i: LongWord; twoChs: Boolean;
  begin
    lnStart := Pointer(@byteBuffer[byteBufChPos]);
    lnEnd := lnStart; addOffs := 0;

    //while True do
    for i:=0 to High(i) do
     case lnEnd^ of
      #0: begin lnEnd := nil; Break; end;
      #10: begin twoChs := (lnEnd + 1)^ = #13; Inc(addOffs, cLineEndSize[twoChs]); ALineEnd := cLineEndLF[twoChs]; Break; end;
      #13: begin twoChs := (lnEnd + 1)^ = #10; Inc(addOffs, cLineEndSize[twoChs]); ALineEnd := cLineEndCR[twoChs]; Break; end;
      else Inc(lnEnd);
     end;

    if lnEnd <> nil then lenInBytes := (lnEnd - lnStart) * SizeOf(UnicodeChar) else
     begin
      lenInBytes := Length(byteBuffer) - byteBufChPos + 1;
      readMore := True;
     end;

    SetLength(Result, lenInBytes div SizeOf(UnicodeChar));
    if lenInBytes > 0 then System.Move(byteBuffer[byteBufChPos], Result[1], lenInBytes);
    Inc(byteBufChPos, lenInBytes + addOffs);
  end;

begin
  if ASrcStream = nil then begin Result := False; Exit; end;
  byteLine := ''; unicodeLine := '';
  repeat
   readMore := False;
   if byteBufChPos > bufByteCount then
    begin
     GMHrCheckObj(ASrcStream.Read(PAnsiChar(byteBuffer), Length(byteBuffer), Pointer(@bufByteCount)), ACaller, {$I %CurrentRoutine%});
     byteBufChPos := 1;
    end;
   Result := byteBufChPos <= bufByteCount;
   if Result then
     case ACharKind of
      ckAnsi, ckUtf8: byteLine := byteLine + ReadByteLine;
      ckUtf16LE: unicodeLine := unicodeLine + ReadUnicodeLine;
     end;
   until not readMore or not Result;

//if Result then
   case ACharKind of
    ckAnsi: ALine := byteLine;
    ckUtf8: ALine := Utf8Decode(byteLine);
    ckUtf16LE: ALine := unicodeLine;
    else ALine := '';
   end;

  Result := (bufByteCount > 0) or (Length(ALine) > 0);
end;