MODULE editor;

(* based on the kilo editor building course*)

(* Step 28 *)

(******************** INCLUDES ******************)

IMPORT FIO, IO, SYSTEM, termios, NumberIO;
IMPORT CharClass, libc, ASCII;
IMPORT errno;
IMPORT VT100;
FROM Strings IMPORT Concat, String1, Length, Delete;
IMPORT SplitV1;
IMPORT Delay;

(******************** DATA ******************)

VAR 
    c               : CHAR;
    p               : POINTER TO CHAR;
    
    result          : INTEGER;
    number          : CARDINAL;
    editorConfig    : RECORD
                        screenrows : CARDINAL;
                        screencols : CARDINAL;
                        theTermios : termios.TERMIOS;
                        raw        : termios.TERMIOS;
                      END;
    (* testing *)
    test        : BOOLEAN;
    clearScreenStr : ARRAY[0..6] OF CHAR;
    cursorHomeStr  : ARRAY[0..2] OF CHAR;
    cursorposStr             : ARRAY[0..9] OF CHAR;
    requestCursorPosStr : ARRAY[0..15] OF CHAR;
    adresse : SYSTEM.ADDRESS;
    X,Y : CARDINAL;


(******************** Utilities *****************)

PROCEDURE WriteString( str : ARRAY OF CHAR);
    (* IO procedures : direct to libc in unbuffered mode *)
    VAR
        i         : CARDINAL;            

    BEGIN
        FOR i := 1 TO Length(str) DO
            IO.Write(str[i])
        END;
    END WriteString;


(******************** TERMINAL ******************)

     PROCEDURE CtrlKey(c: CHAR) : CARDINAL;
        (* return a value < ORD(" ") for the control keys *)
        VAR
            n : BITSET;

    BEGIN
        n := BITSET(ORD(c));
        n := n * BITSET(1FH);
        RETURN CARDINAL(n)
    END CtrlKey;

    PROCEDURE die (s : ARRAY OF CHAR);
    BEGIN
        libc.perror(s);
        WriteString(clearScreenStr);
        HALT
    END die;

    PROCEDURE disablerawMode() : INTEGER;
        VAR
            TCSAFLUSH : INTEGER;
            result    : INTEGER; 
        
    BEGIN
        IO.BufferedMode(0,TRUE);
        IO.BufferedMode(1,TRUE);
        TCSAFLUSH := termios.tcsflush ();
        IF termios.tcsetattr(FIO.StdIn, TCSAFLUSH, editorConfig.theTermios) = -1 THEN
            die("tcsetattr")
        END;
        RETURN 1
    END disablerawMode;

    PROCEDURE enablerawMode;

        VAR
            TCSAFLUSH   : INTEGER;
            result      : INTEGER;
            bresult     : BOOLEAN;
    BEGIN
        TCSAFLUSH := termios.tcsflush (); 
        libc.atexit(disablerawMode);
       
        bresult := termios.SetFlag(editorConfig.raw,termios.cs8, TRUE);
        IF termios.tcsetattr(FIO.StdIn, TCSAFLUSH, editorConfig.raw) = -1 THEN
            die("tcsetattr")
        END;
        IO.UnBufferedMode(0,TRUE);
        IO.UnBufferedMode(1,TRUE);
    END enablerawMode;

    PROCEDURE  editorReadKey() : CHAR;
        VAR
            c : CHAR;
    BEGIN
        IO.Read(c);
        RETURN c
    END editorReadKey; 

(******************** output ******************)

    PROCEDURE editorDrawRows;

        VAR
            y : CARDINAL;

    BEGIN
        FOR y := 1 TO 24 DO
            IO.Write("~");
            IO.Write(CHR(13));
            IO.Write(CHR(10));
        END;
    END editorDrawRows;

    PROCEDURE editorRefreshScreen;

        VAR
            i : CARDINAL;

    BEGIN
        (* modif *)
        (* WriteString(clearScreenStr);
        WriteString(cursorHomeStr); *)
        FOR i := 0 TO 3 DO
            IO.Write(clearScreenStr[i])
        END;
        FOR i := 0 TO 3 DO
            IO.Write(cursorHomeStr[i])
        END;
        editorDrawRows;
        (* WriteString(cursorHomeStr); *)
        FOR i := 0 TO 3 DO
            IO.Write(cursorHomeStr[i])
        END;
    END editorRefreshScreen;

PROCEDURE Card2Str ( x : CARDINAL; VAR str : ARRAY OF CHAR);

BEGIN
IF x < 10 THEN
        NumberIO.CardToStr(x,1,str);
    ELSIF x < 100 THEN
        NumberIO.CardToStr(x,2,str);
    ELSE
        NumberIO.CardToStr(x,3,str);
    END;
END Card2Str;

PROCEDURE requestCursorPosition(VAR x,y : CARDINAL);
(* ESC[6n 	request cursor position (reports as ESC[#;#R) *)

VAR
    str          : ARRAY [0..15] OF CHAR;
    theStructure : SplitV1.Structure;
    c            : CHAR;
    cc           : String1;
    i            : CARDINAL;

BEGIN
    str := "";
    SplitV1.InitStructure(theStructure);
    (* sending the request *)
    (* WriteString(requestCursorPosStr); *)
    Concat(str,ASCII.esc,str);
    Concat(str,"[6n", str);
    FOR i := 0 TO Length(str) DO
        IO.Write(str[i])
    END;
    (* quering the answer *)
    str := "";
    REPEAT
        IO.Read(c);
        cc[0] := c;
        Concat(str,cc,str);
    UNTIL c = "R";

    (* extracting coodinates from the answer *)
    Delete(str,0,2);
    Delete(str,Length(str) -1 ,1);
    SplitV1.SplitStr(str, ";", theStructure);
    NumberIO.StrToCard(theStructure[0].element,x);
    NumberIO.StrToCard(theStructure[1].element,y);
END requestCursorPosition;


PROCEDURE cursorpos(x,y : CARDINAL);

VAR
    tmpString : ARRAY[0..15] OF CHAR;
    tmpString1: ARRAY[0..15] OF CHAR;
    tmpString2: String1;
    i         : CARDINAL;

BEGIN
    cursorposStr := "";
    Concat(cursorposStr, ASCII.esc,cursorposStr);
    Concat(cursorposStr, "[",cursorposStr);
    tmpString := "";
    Concat(tmpString,cursorposStr, tmpString);
    tmpString1 := "";
    Card2Str(y,tmpString1);
    Concat(tmpString,tmpString1, tmpString);
    tmpString2[0] := ";";
    Concat(tmpString, tmpString2,tmpString);
    tmpString1 := "";
    Card2Str(x,tmpString1);
    Concat(tmpString,tmpString1, tmpString);
    tmpString2[0] := "H";
    Concat(tmpString, tmpString2,tmpString);
    FOR i := 0 TO Length(tmpString) DO
        IO.Write(tmpString[i])
    END;
END cursorpos;

    PROCEDURE getWindowSize(VAR y : CARDINAL; x: CARDINAL);
    
    BEGIN
        cursorpos(999,999);
        requestCursorPosition(x,y);
    END getWindowSize;


(******************** input  ******************)

    PROCEDURE editorProcessKeypress;
        VAR
            c       : CHAR;
            b       : BOOLEAN;
    BEGIN
        c := editorReadKey();
        CASE ORD(c) OF
          17 : HALT;
        END
    END editorProcessKeypress;

(********************  INIT  ******************)

PROCEDURE InitScreenEscapes;

VAR temp : ARRAY[0..10] OF CHAR;
BEGIN
    cursorHomeStr := "";
    Concat(cursorHomeStr,ASCII.esc,cursorHomeStr);
    Concat(cursorHomeStr,"[H",cursorHomeStr);

    clearScreenStr := "";
    Concat(clearScreenStr,ASCII.esc,clearScreenStr);
    Concat(clearScreenStr,"[2J",clearScreenStr);

    cursorposStr := "";
    Concat(cursorposStr,ASCII.esc,cursorposStr);
    Concat(cursorposStr,"[",cursorposStr);
   
    requestCursorPosStr := "";
    Concat(requestCursorPosStr,ASCII.esc,requestCursorPosStr);
    Concat(requestCursorPosStr,"[6n",requestCursorPosStr);
END InitScreenEscapes;

PROCEDURE InitEditor;

BEGIN
    getWindowSize(editorConfig.screenrows, editorConfig.screencols)
END InitEditor;


BEGIN
    InitScreenEscapes;
    (* termios state saved *)
    editorConfig.theTermios := termios.InitTermios();
    (* a copy of it *)
    editorConfig.raw := termios.InitTermios();
    IF termios.tcgetattr(FIO.StdIn, editorConfig.theTermios) = -1 THEN
        die("tcgetattr")
    END;
    IF termios.tcgetattr(FIO.StdIn, editorConfig.raw) = -1 THEN
        die("tcgetattr")
    END;
    (* end termios *)

    (* enablerawMode;

    InitEditor;

    LOOP
        editorRefreshScreen;
        editorProcessKeypress;
        editorRefreshScreen;
    END; *)

    cursorpos(30,30);
    requestCursorPosition(X,Y);
    result := disablerawMode();
    NumberIO.WriteCard(X, 5);
    NumberIO.WriteCard(Y, 5);

    (* cleaning ...*)

    adresse := termios.KillTermios(editorConfig.raw);
END editor.