{}

procedure OpenStdout(var f: TextRec); forward;
procedure WriteStdout(var f: TextRec); forward;
procedure FlushStdout(var f: TextRec); forward;
procedure CloseStdout(var f: TextRec); forward;

procedure OpenStdin(var f: TextRec); forward;
procedure ReadStdin(var f: TextRec); forward;
procedure CloseStdin(var f: TextRec); forward;



procedure AssignStdout(var f: Text);
begin
  Assign(f, '');
  TextRec(f).OpenFunc := @OpenStdout;
  Rewrite(f);
end;

procedure OpenStdout(var f: TextRec);
begin
  TextRec(f).InOutFunc := @WriteStdout;
  TextRec(f).FlushFunc := @FlushStdout;
  TextRec(f).CloseFunc := @CloseStdout;
end;

procedure WriteStdout(var f: TextRec);
var
  msg: String;
begin
  msg := StrPas(PChar(f.bufptr));
  SetLength(msg, f.bufpos);
  f.bufpos := 0;
  Konsole.Write(msg);
end;

procedure FlushStdout(var f: TextRec);
begin
  WriteStdout(f);
  Konsole.Flush;
end;

procedure CloseStdout(var f: TextRec);
begin
end;



procedure AssignStdin(var f: Text);
begin
  Assign(f, '');
  TextRec(f).OpenFunc := @OpenStdin;
  Reset(f);
end;

procedure OpenStdin(var f: TextRec);
begin
  TextRec(f).InOutFunc := @ReadStdin;
  TextRec(f).FlushFunc := nil;
  TextRec(f).CloseFunc := @CloseStdin;
end;

procedure ReadStdin(var f: TextRec);
var
  max, curpos: Longint;
  c: Longint;
begin
  max := f.bufsize - Length(LineEnding);
  curpos := 0;
  repeat
    c := 13{l4_getc()};
    case c of
      13:
      begin
        {f.bufptr^[curpos] := LineEnding;}
        Inc(curpos);
        f.bufpos := 0;
        f.bufend := curpos;
        {l4_putc(Longint(LineEnding));}
        break;
      end;
      32..126: if curpos < max then
      begin
        f.bufptr^[curpos] := Char(c);
        Inc(curpos);
        {l4_putc(c);}
      end;
    end;
  until False;
end;

procedure CloseStdin(var f: TextRec);
begin
end;


{ TKonsole }

procedure KonsoleThreadMain(Console: PKonsole);
{Рабочий цикл консоли}
var
  ThreadInfo: TKosThreadInfo;
  Message: ShortString;
  Event: DWord;
begin
  kos_maskevents(ME_PAINT or ME_KEYBOARD or ME_IPC);
  kos_threadinfo(@ThreadInfo);
  Console^.FThreadSlot := kos_getthreadslot(ThreadInfo.ThreadID);

  kos_initipc(Console^.FIPCBuffer, Console^.FIPCBufferSize);

  {сразу отобразить и активировать окно}
  Console^.Paint();
  {$ifndef EMULATOR}
  kos_setactivewindow(Console^.FThreadSlot);
  {$endif}

  {готов к обработке событий}
  Console^.FOpened := True;
  while not Console^.FTerminate do
  begin
    Event := kos_getevent();
    Console^.FOnAir := True;
    if not Console^.FTerminate then
    case Event of
      SE_PAINT: Console^.Paint();
      SE_KEYBOARD: Console^.ProcessKeyboard(kos_getkey());
      SE_IPC: while Console^.ReceiveMessage(Message) do Console^.ProcessMessage(Message);
    end;
    Console^.FOnAir := False;
  end;
  Console^.FOpened := False;
end;

constructor TKonsole.Init(ACaption: String);
const
  IPC_SIZE = 4096;
var
  ThreadInfo: TKosThreadInfo;
begin
  if ACaption <> '' then
    FCaption := ACaption else
  begin
    kos_threadinfo(@ThreadInfo);
    FCaption := StrPas(ThreadInfo.AppName);
  end;
  SetLength(FLines, 1);
  FLines[0] := '';
  FCursor.X := 1;
  FCursor.Y := 0;
  FMaxLines := 150;
  FTerminate := False;
  FOpened := False;
  FOnAir  := False;
  FIPCBufferSize := SizeOf(TKosIPC) + IPC_SIZE;
  FIPCBuffer := GetMem(FIPCBufferSize);
  FIPCBuffer^.Lock := False;
  FIPCBuffer^.Size := 0;
  FThreadSlot := -1;
  FThreadID := BeginThread(TThreadFunc(@KonsoleThreadMain), @Self);
  if FThreadID <> 0 then
    {XXX: может зависнуть}
    while not FOpened do ThreadSwitch;
end;

destructor TKonsole.Done();
begin
  FTerminate := True;
  if FOpened then begin Self.Write(#0); kos_delay(01); end;
  if FOpened then begin Self.Write(#0); kos_delay(10); end;
  if FOpened then begin Self.Write(#0); kos_delay(20); end;
  if FOpened then
  begin
    FOpened := False;
    FOnAir  := False;
    KillThread(FThreadID);
  end;
  {FreeMem(FIPCBuffer);
  SetLength(FLines, 0);}
end;

function TKonsole.ReceiveMessage(var Message: ShortString): Boolean;
{Извлечь первое сообщение из буфера}
var
  PMsg: PKosMessage;
  Size: Longword;
begin
  FIPCBuffer^.Lock := True;

  if FIPCBuffer^.Size > 0 then
  begin
    PMsg := Pointer(Longword(FIPCBuffer) + SizeOf(TKosIPC));
    {TODO: проверка PMsg^.SenderID}
    {Size := PMsg^.Size;
    Dec(FIPCBuffer^.Size, Size + SizeOf(TKosMessage));
    if Size > 255 then Size := 255;
    SetLength(Message, Size);
    Move(Pointer(Longword(PMsg) + SizeOf(TKosMessage))^, Message[1], Size);
    if FIPCBuffer^.Size > 0 then
      Move(Pointer(Longword(PMsg) + SizeOf(TKosMessage) + PMsg^.Size)^, PMsg^, FIPCBuffer^.Size);}

    {XXX}
    Size := FIPCBuffer^.Size;
    Dec(FIPCBuffer^.Size, Size);
    if Size > 255 then Size := 255;
    SetLength(Message, Size);
    Move(PMsg^, Message[1], Size);

    Result := True;
  end else
  begin
    Message := '';
    Result := False;
  end;

  {FIXME: если FIPCBuffer^.Size = 0, то FIPCBuffer^.Lock все равно > 0}
  FIPCBuffer^.Lock := False;
end;

procedure TKonsole.ProcessMessage(Message: ShortString);
{ Вывести сообщение на консоль }
var
  OnlyBottomLine: Boolean = True;

  procedure PutChar(C: Char);
  var
    LinesCount: Longint;
    PLine: PShortString;
    I: Longint;
  begin
    { перевод коретки на позицию влево }
    if C = #8 then
    begin
      if FCursor.X > 1 then
        Dec(FCursor.X);
    end else

    { перевод коретки на следующую строку }
    if C = #10 then
    begin
      OnlyBottomLine := False;
      Inc(FCursor.Y);
      LinesCount := Length(FLines);
      while FCursor.Y >= FMaxLines do Dec(FCursor.Y, FMaxLines);
      if FCursor.Y < LinesCount then FLines[FCursor.Y] := '';
      while FCursor.Y >= LinesCount do
      begin
        SetLength(FLines, LinesCount + 1);
        FLines[LinesCount] := '';
        Inc(LinesCount);
      end;
    end else

    { перевод коретки в начало строки }
    if C = #13 then
      FCursor.X := 1 else

    { помещение символа в строку }
    begin
      if FCursor.X > 200 then
      begin
        PutChar(#13);
        PutChar(#10);
      end;

      { FIXME: Если в PascalMain только один Write/Ln, то зависон.
        см. FPC_DO_EXIT, InternalExit }
      PLine := @FLines[FCursor.Y];
      I := Length(PLine^);
      if FCursor.X > I then
      begin
        SetLength(PLine^, FCursor.X);
        Inc(I);
        while I < FCursor.X do
        begin
          PLine^[I] := ' ';
          Inc(I);
        end;
      end;
      FLines[FCursor.Y][FCursor.X] := C;

      Inc(FCursor.X);
    end;
  end;

var
  I: Longint;
begin
  for I := 1 to Length(Message) do
    PutChar(Message[I]);
  Paint(OnlyBottomLine);
end;

procedure TKonsole.ProcessKeyboard(Key: Word);
begin
  FKeyPressed := Key;
end;

function TKonsole.GetRect(): TKosRect;
var
  ThreadInfo: TKosThreadInfo;
begin
  kos_threadinfo(@ThreadInfo, FThreadSlot);
  Result := ThreadInfo.WindowRect;
end;

function TKonsole.GetKeyPressed(): Word;
begin
  Result := FKeyPressed;
  FKeyPressed := 0;
end;

procedure TKonsole.Paint(BottomRow: Boolean);
var
  Buffer: array[Byte] of Char;
  Rect: TKosRect;
  J: Longint;
  Width, Height, Row: Longint;
  CaptionHeight, BorderWidth, FontWidth, FontHeight: Longint;
begin
  CaptionHeight := 16;
  BorderWidth := 5;
  FontWidth := 6;
  FontHeight := 9;

  kos_begindraw();

  if not BottomRow then
  begin
    {отрисовка окна}
    kos_definewindow(60, 60, 400, 400, $63000000);
    {вывод заголовка}
    Move(FCaption[1], Buffer, Length(FCaption));
    Buffer[Length(FCaption)] := #0;
    kos_setcaption(Buffer);
  end;

  {подготовка к выводу строк}
  Rect := GetRect();
  Dec(Rect.Width, BorderWidth * 2);
  Dec(Rect.Height, CaptionHeight + BorderWidth * 2);
  Width  := Rect.Width div FontWidth;
  Height := Rect.Height - FontHeight;
  Row := FCursor.Y;

  while Height > 0 do
  begin
    {вывод одной строки}
    J := Length(FLines[Row]);
    if J > Width then J := Width;
    kos_drawtext(0, Height, Copy(FLines[Row], 1, J), $00DD00, $FF000000);
    {заливка оставшегося пространства в строке}
    J := J * FontWidth;
    kos_drawrect(J, Height, Rect.Width - J + 1, FontHeight, $000000);
    {подготовка к выводу следующей строки}
    Dec(Height, FontHeight);
    Dec(Row);
    if BottomRow or ((Row < 0) and (Length(FLines) < FMaxLines)) then Break;
    while Row < 0 do Inc(Row, FMaxLines);
  end;
  if FCursor.X <= Width then
    {отрисовка курсора}
    kos_drawrect((FCursor.X - 1) * FontWidth, Rect.Height - 2, FontWidth, 2, $FFFFFF);
  if not BottomRow then
    {заливка оставшейся части окна}
    kos_drawrect(0, 0, Rect.Width + 1, Height + FontHeight, $000000);

  kos_enddraw();
end;

procedure TKonsole.Write(Message: ShortString);
var
  I: Integer;
begin
  {XXX: возможна ситуация при которой сообщение не будет отправлено}
  if FOpened then
  begin
    I := 100;
    while (kos_sendmsg(FThreadID, @Message[1], Length(Message)) = 2) and (I > 0) do
    begin
      Dec(I);
      ThreadSwitch;
    end;
  end;
end;

procedure TKonsole.Flush();
begin
  while FOnAir do ThreadSwitch;
end;
