{
    This file is part of the Free Pascal run time library.
    Copyright (c) 1998 - 2005 by the Free Pascal development team.

    This file implements platform independent routines for Crt.
    It should be modified later to use routines from Keyboard and
    Video instead of code in platform-specific crt.pas.

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

var
  ScanCode: byte;
  SpecialKey: boolean;

procedure GotoXY (X: tcrtcoord; Y: tcrtcoord);
begin
 GotoXY32 (X, Y);
end;

procedure Window (X1, Y1, X2, Y2: byte);
begin
 Window32 (X1, Y1, X2, Y2);
end;

function WhereX: tcrtcoord;
var
 X1: dword;
begin
 X1 := WhereX32;
 if X1 > 255 then
  WhereX := 255
 else
  WhereX := X1;
end;

function WhereY: tcrtcoord;
var
 Y1: dword;
begin
 Y1 := WhereY32;
 if Y1 > 255 then
  WhereY := 255
 else
  WhereY := Y1;
end;


procedure ClrScr;
{Clears the current window.}
begin
 RemoveLines (0, Succ (WindMaxY - WindMinY));
 GotoXY32 (1, 1);
end;


procedure GotoXY32 (X, Y: dword);
(* Positions cursor on (X, Y) (1-based) relative to window origin; for TP/BP
   compatibility call completely ignored in case of incorrect parameters.    *)
begin
 if (X > 0) and (Y > 0) then
  begin
   Dec (X);
   Dec (Y);
   if (X <= WindMaxX - WindMinX) and (Y <= WindMaxY - WindMinY) then
    SetScreenCursor (X + WindMinX, Y + WindMinY);
  end;
end;


function WhereX32: dword;
(* Returns the X position of the cursor (1-based). *)
var
 X, Y: dword;
begin
 GetScreenCursor (X, Y);
 WhereX32 := Succ (X - WindMinX);
end;


function WhereY32: dword;
(* Returns the Y position of the cursor (1-based). *)
var
 X, Y: dword;
begin
 GetScreenCursor (X, Y);
 WhereY32 := Succ (Y - WindMinY);
end;


procedure ClrEol;
(* Clears the line where cursor is located from current position up to end. *)
var
 X, Y: dword;
begin
 GetScreenCursor (X, Y);
 ClearCells (X, Y, Succ (WindMaxX - X));
end;


procedure DelLine;
(* Deletes the line at cursor. *)
begin
 RemoveLines (Pred (WhereY32), 1);
end;


procedure TextMode (Mode: word);
{ Use this procedure to set-up a specific text-mode.}
begin
 TextAttr := $07;
 LastMode := Mode;
 SetScreenMode (Mode);
 WindMin := 0;
 WindMaxX := Pred (ScreenWidth);
 WindMaxY := Pred (ScreenHeight);
 if WindMaxX >= 255 then
  WindMax := 255
 else
  WindMax := WindMaxX;
 if WindMaxY >= 255 then
  WindMax := WindMax or $FF00
 else
  WindMax := WindMax or (WindMaxY shl 8);
 ClrScr;
end;


procedure TextColor (Color: byte);
{All text written after calling this will have Color as foreground colour.}
begin
 TextAttr := (TextAttr and $70) or (Color and $f);
 if Color > 15 then
  TextAttr := TextAttr or 128;
end;


procedure TextBackground (Color: byte);
{All text written after calling this will have Color as background colour.}
begin
 TextAttr := (TextAttr and $8F) or ((Color and $7) shl 4);
end;


procedure NormVideo;
{Changes the text-background to black and the foreground to white.}
begin
 TextAttr := $7;
end;


procedure LowVideo;
{All text written after this will have low intensity.}
begin
 TextAttr := TextAttr and $F7;
end;


procedure HighVideo;
{All text written after this will have high intensity.}
begin
 TextAttr := TextAttr or $8;
end;


procedure Window32 (X1, Y1, X2, Y2: dword);
{Change the write window to the given coordinates.}
begin
 if (X1 > 0) and (Y1 > 0) and (X2 <= ScreenWidth) and (Y2 <= ScreenHeight)
                                             and (X1 <= X2) and (Y1 <= Y2) then
  begin
   WindMinX := Pred (X1);
   WindMinY := Pred (Y1);
   if WindMinX >= 255 then
    WindMin := 255
   else
    WindMin := WindMinX;
   if WindMinY >= 255 then
    WindMin := WindMin or $FF00
   else
    WindMin := WindMin or (WindMinY shl 8);
   WindMaxX := Pred (X2);
   WindMaxY := Pred (Y2);
   if WindMaxX >= 255 then
    WindMax := 255
   else
    WindMax := WindMaxX;
   if WindMaxY >= 255 then
    WindMax := WindMax or $FF00
   else
    WindMax := WindMaxX or (WindMaxY shl 8);
   GotoXY32 (1, 1);
  end;
end;


threadvar
 CurrX, CurrY: dword;


procedure WriteChar (C: char);
begin
 case C of
  #7: WriteBell;
  #8: if CurrX >= WindMinX then
       Dec (CurrX);
{      #9: x:=(x-lo(windmin)) and $fff8+8+lo(windmin);}
  #10: Inc (CurrY);
  #13: CurrX := WindMinX;
  else
   begin
    WriteNormal (C, CurrX, CurrY);
    Inc (CurrX);
   end;
 end;
 if CurrX > WindMaxX then
  begin
   CurrX := WindMinX;
   Inc (CurrY);
  end;
 if CurrY > WindMaxY then
  begin
   RemoveLines (0, 1);
   CurrY := WindMaxY;
  end;
end;


function CrtWrite (var F: TextRec): integer;
var
 I: dword;
{Write a series of characters to the console.}
begin
 if F.BufPos > 0 then
  begin
   GetScreenCursor (CurrX, CurrY);
   for I := 0 to Pred (F.BufPos) do
    WriteChar ((PChar (F.BufPtr) + I)^);
   SetScreenCursor (CurrX, CurrY);
   F.BufPos := 0;
  end;
 CrtWrite := 0;
end;


function CrtRead (var F: TextRec): integer;
{Read a series of characters from the console.}
var
 C: char;
begin
 GetScreenCursor (CurrX, CurrY);
 F.BufPos := 0;
 F.BufEnd := 0;
 repeat
  if F.BufPos > F.BufEnd then
   F.BufEnd := F.BufPos;
  SetScreenCursor (CurrX, CurrY);
  C := ReadKey;
  case C of
   #0: ReadKey;
(* The following code to support input editing is incomplete anyway
   - no handling of line breaks, no possibility to insert characters
   or delete characters inside the string, etc.

   #0 : case readkey of
          #71 : while f.bufpos>0 do
                 begin
                   dec(f.bufpos);
                   WriteChar(#8);
                 end;
          #75 : if f.bufpos>0 then
                 begin
                   dec(f.bufpos);
                   WriteChar(#8);
                 end;
          #77 : if f.bufpos<f.bufend then
                 begin
                   WriteChar(f.bufptr^[f.bufpos]);
                   inc(f.bufpos);
                 end;
          #79 : while f.bufpos<f.bufend do
                 begin
                   WriteChar(f.bufptr^[f.bufpos]);
                   inc(f.bufpos);
                 end;
         end;
*)

   #8: if (F.BufPos > 0) and (F.BufPos = F.BufEnd) then
        begin
{$WARNING CrtRead doesn't handle line breaks correctly (same bug as TP/BP)!}
         WriteChar (#8);
         WriteChar (' ');
         WriteChar (#8);
         Dec (F.BufPos);
         Dec (F.BufEnd);
        end;
   #13: begin
         WriteChar(#13);
         WriteChar(#10);
         F.BufPtr^ [F.BufEnd] := #13;
         Inc (F.BufEnd);
         F.BufPtr^ [F.BufEnd] := #10;
         Inc (F.BufEnd);
         break;
        end;
   #26: if CheckEOF then
         begin
          F.BufPtr^ [F.BufEnd] := #26;
          Inc (F.BufEnd);
          break;
         end;
   #32..#255: if F.BufPos < F.BufSize - 2 then
               begin
                F.BufPtr^ [F.BufPos] := C;
                Inc (F.BufPos);
                WriteChar (C);
               end;
  end
 until false;
 CrtRead := 0;
end;


function CrtReturn (var F: TextRec): integer;
begin
 CrtReturn:=0;
end;


function CrtClose (var F: TextRec): integer;
begin
 F.Mode := fmClosed;
 CrtClose := 0;
end;


function CrtOpen (var F: TextRec): integer;
begin
 if F.Mode = fmOutput then
  begin
   TextRec(F).InOutFunc := @CrtWrite;
   TextRec(F).FlushFunc := @CrtWrite;
  end
 else
  begin
   F.Mode := fmInput;
   TextRec(F).InOutFunc := @CrtRead;
   TextRec(F).FlushFunc := @CrtReturn;
  end;
 TextRec(F).CloseFunc := @CrtClose;
 CrtOpen := 0;
end;


procedure AssignCrt (var F: text);
{Assigns a file to the crt console.}
begin
 Assign (F, '');
 TextRec (F).OpenFunc := @CrtOpen;
end;


{$IFNDEF HAS_SOUND}
procedure Sound (Hz: word);
(* Dummy Sound implementation - for platforms requiring both frequence
   and duration at the beginning instead of start and stop procedures. *)
begin
end;
{$ENDIF HAS_SOUND}


{$IFNDEF HAS_NOSOUND}
procedure NoSound;
(* Dummy NoSound implementation - for platforms requiring both frequence
   and duration at the beginning instead of start and stop procedures.   *)
begin
end;
{$ENDIF HAS_NOSOUND}


var
  PrevCtrlBreakHandler: TCtrlBreakHandler;


function CrtCtrlBreakHandler (CtrlBreak: boolean): boolean;
begin
(* Earlier registered handlers (e.g. FreeVision) have priority. *)
  if Assigned (PrevCtrlBreakHandler) then
    if PrevCtrlBreakHandler (CtrlBreak) then
      begin
        CrtCtrlBreakHandler := true;
        Exit;
      end;
(* If Ctrl-Break was pressed, either ignore it or allow default processing. *)
  if CtrlBreak then
    CrtCtrlBreakHandler := not (CheckBreak)
  else (* Ctrl-C pressed *)
{$IFDEF FPC_CRT_CTRLC_TREATED_AS_KEY}
 (* If Ctrl-C is really treated as a key, the following branch should never *)
 (* be executed, but let's stay on the safe side and ensure predictability. *)
   CrtCtrlBreakHandler := false;
{$ELSE FPC_CRT_CTRLC_TREATED_AS_KEY}
    begin
      if not (SpecialKey) and (ScanCode = 0) then
        ScanCode := 3;
      CrtCtrlBreakHandler := true;
    end;
{$ENDIF FPC_CRT_CTRLC_TREATED_AS_KEY}
end;


procedure CrtInit;
(* Common part of unit initialization. *)
begin
 TextAttr := LightGray;
 WindMin := 0;
 WindMaxX := Pred (ScreenWidth);
 WindMaxY := Pred (ScreenHeight);
 if WindMaxX >= 255 then
  WindMax := 255
 else
  WindMax := WindMaxX;
 if WindMaxY >= 255 then
  WindMax := WindMax or $FF00
 else
  WindMax := WindMax or (WindMaxY shl 8);
 ScanCode := 0;
 SpecialKey := false;
 AssignCrt (Input);
 Reset (Input);
 AssignCrt (Output);
 Rewrite (Output);
 PrevCtrlBreakHandler := SysSetCtrlBreakHandler (@CrtCtrlBreakHandler);
 if PrevCtrlBreakHandler = TCtrlBreakHandler (pointer (-1)) then
   PrevCtrlBreakHandler := nil;
 CheckBreak := true;
end;
