MODULE editor1;

(* based on the kilo editor building course*)

(* Step 28 *)

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

IMPORT IO, Strings, ASCII, NumberIO, termios, FIO, Delay, SplitV1, STextIO;
IMPORT libc, SYSTEM, Storage, MemUtils;

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

TYPE
    abufType            = RECORD
                            b   : SYSTEM.ADDRESS;
                            len : CARDINAL;
                          END;
    abufTypePtr         = POINTER TO abufType;

VAR
    editorConfig        : RECORD
                            screenrows : CARDINAL;
                            screencols : CARDINAL;
                            theTermios : termios.TERMIOS;
                         END;

    TCSAFLUSH           : INTEGER;

    theStructure        : SplitV1.Structure;

    cursorposStr        : ARRAY [0..15] OF CHAR;
    clearScreenStr      : ARRAY[0..6] OF CHAR;
    cursorHomeStr       : ARRAY[0..2] OF CHAR;
    requestCursorPosStr : ARRAY[0..15] OF CHAR;

    resultStr           : ARRAY [0..15] OF CHAR;
    tmpString           : ARRAY[0..15] OF CHAR;
    tmpString1          : ARRAY[0..15] OF CHAR;
    tmpString2          : Strings.String1;
    cc                  : Strings.String1;
    i                   : CARDINAL;
    str                 : ARRAY [0..15] OF CHAR;

    abuf                : abufType;
    abufInit            : abufType;
 
(******************** Utilities *****************)

    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;

    (******************** 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);
        FOR i := 0 TO Strings.Length(s) DO
            IO.Write(s[i])
        END;
        HALT
    END die;

    PROCEDURE disablerawMode() : INTEGER;

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

    PROCEDURE enablerawMode;
    BEGIN
        libc.atexit(disablerawMode);

        editorConfig.theTermios := termios.InitTermios();
        (* raw := termios.InitTermios(); *)
        IF termios.tcgetattr(FIO.StdIn, editorConfig.theTermios) = -1 THEN
            die("tcgetattr")
        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; 

    (******************** Buffer ******************)

    PROCEDURE abAppend (ptr : abufTypePtr; str: ARRAY OF CHAR; len : CARDINAL);

    BEGIN
        (*
            REALLOCATE - attempts to reallocate storage. The address,
            a, should either be NIL in which case ALLOCATE
            is called, or alternatively it should have already
            been initialized by ALLOCATE. The allocated storage
            is resized accordingly.
        *)
        (* PROCEDURE REALLOCATE (VAR a: ADDRESS; Size: CARDINAL) ; *)
        Storage.REALLOCATE( ptr^.b,ptr^.len + len);
        (* PROCEDURE MemCopy (from: ADDRESS; length: CARDINAL; to: ADDRESS) ; *)
        IF ptr^.b <> NIL THEN
            MemUtils.MemCopy(SYSTEM.ADR(str),len, ptr^.b);
            ptr^.len := ptr^.len + len;
        END;
    END abAppend;

    PROCEDURE abFree (ptr : abufTypePtr);

    BEGIN
        (* PROCEDURE DEALLOCATE (VAR a: ADDRESS ; Size: CARDINAL) ; *)
        Storage.DEALLOCATE(ptr^.b, ptr^.len);
        ptr^.len := 0;
    END abFree;

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

    PROCEDURE editorDrawRows;

        VAR
            y : CARDINAL;
            str : ARRAY [0..15] OF CHAR;

    BEGIN
        str[0] := "~";
        str[1] := CHR(13);
        str[2] := CHR(10);
        str[3] := CHR(0);
        FOR y := 1 TO editorConfig.screenrows -1 DO
            (* IO.Write("~");
            IO.Write(CHR(13));
            IO.Write(CHR(10)); *)
            libc.write(FIO.StdOut,SYSTEM.ADR(str),3);
        END;
        
        IO.Write("~");
    END editorDrawRows;

   PROCEDURE editorRefreshScreen;

    BEGIN
        (* modif  using libc write procedure *)
        libc.write(FIO.StdOut,SYSTEM.ADR(clearScreenStr),3);
        libc.write(FIO.StdOut,SYSTEM.ADR(cursorHomeStr),3);
        editorDrawRows;
        libc.write(FIO.StdOut,SYSTEM.ADR(cursorHomeStr),3);

        (* 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 requestCursorPosition(VAR x,y : CARDINAL);

        VAR
            c   : CHAR;

    BEGIN
        str := "";
        SplitV1.InitStructure(theStructure);
        Strings.Concat(str,ASCII.esc,str);
        Strings.Concat(str,"[6n", str);
        (* sending request *)
        (* FOR i := 0 TO 3 DO
            IO.Write(str[i])
        END; *)
        libc.write(FIO.StdOut,SYSTEM.ADR(str),4);
        (* getting answer *)
        str := "";
        REPEAT
            IO.Read(c);
            cc[0] := c;
            Strings.Concat(str,cc,str);
        UNTIL c = "R";

        (* displaying the retour , changing the escape to ? *)
        (* str[0] := "?"; *)
        (* FOR i := 0 TO Strings.Length(str) DO
            IO.Write(str[i])
        END; *)
        (* libc.write(FIO.StdOut,SYSTEM.ADR(str),Length(str)); *)
        (* extracting the coordinates *)
        Strings.Delete(str,0,2);
        Strings.Delete(str,Strings.Length(str) -1 ,1);
        SplitV1.SplitStr(str, ";", theStructure);
        NumberIO.StrToCard(theStructure[0].element,x);
        NumberIO.StrToCard(theStructure[1].element,y);
        
        (* NumberIO.WriteCard(x, 5);
        NumberIO.WriteCard(y, 5); *)
    END requestCursorPosition;

    PROCEDURE cursorpos(x,y : CARDINAL);
    BEGIN
        cursorposStr := "";
        Strings.Concat(cursorposStr, ASCII.esc,cursorposStr);
        Strings.Concat(cursorposStr, "[",cursorposStr);
        tmpString := "";
        Strings.Concat(tmpString,cursorposStr, tmpString);
        tmpString1 := "";
        Card2Str(y,tmpString1);
        Strings.Concat(tmpString,tmpString1, tmpString);
        tmpString2[0] := ";";
        Strings.Concat(tmpString, tmpString2,tmpString);
        tmpString1 := "";
        Card2Str(x,tmpString1);
        Strings.Concat(tmpString,tmpString1, tmpString);
        tmpString2[0] := "H";
        Strings.Concat(tmpString, tmpString2,tmpString);
        (* FOR i := 0 TO Strings.Length(tmpString) DO
            IO.Write(tmpString[i])
        END; *)
        libc.write(FIO.StdOut,SYSTEM.ADR(tmpString),Strings.Length(tmpString));
    END cursorpos;

    PROCEDURE getWindowSize(VAR y : CARDINAL; VAR 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 := "";
        Strings.Concat(cursorHomeStr,ASCII.esc,cursorHomeStr);
        Strings.Concat(cursorHomeStr,"[H",cursorHomeStr);

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

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

    PROCEDURE InitEditor;

    BEGIN
        TCSAFLUSH := termios.tcsflush (); 

        InitScreenEscapes;
        getWindowSize(editorConfig.screenrows, editorConfig.screencols);

        abufInit.len := 0;
        abufInit.b   := NIL;
    END InitEditor;


BEGIN
    
    enablerawMode;
    InitEditor;
                            
    LOOP
        editorRefreshScreen;
        editorProcessKeypress;
        editorRefreshScreen;
    END;


    (* Delay.Delay(2000); *)
    IF disablerawMode() = -1 THEN
        HALT
    END;    
       
    NumberIO.WriteCard(editorConfig.screencols, 5);
    NumberIO.WriteCard(editorConfig.screenrows, 5);
END editor1.