MODULE Graph;
(* Copyright 2017 Arthur Yefimov

This file is part of Free Oberon.

Free Oberon is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Free Oberon 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.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
*)
IMPORT SDL := SDL2, Strings, Texts,
       SYSTEM, Platform, Out;

CONST
  (* Flip Flags *)
  flipNone* = {};
  flipH*    = 0;
  flipV*    = 1;
  flipHV*   = flipH + flipV;

  (* Draw Mode Flags *)
  drawSpriteNormal* = 0;
  drawSpriteLit*    = 1;
  drawSpriteTrans*  = 2;

  (* Settings *)
  fullscreen*  = 0;
  buffered*    = 1;
  spread*      = 2;
  sharpPixels* = 3;
  software*    = 4;
  initMouse*   = 8;
  initSound*   = 9;
  initNet*     = 10;

  (* Event Types *)
  quit*        = 1;
  windowEvent* = 2;
  keyDown*     = 3;
  keyUp*       = 4;
  textInput*   = 5;
  mouseMove*   = 6;
  mouseDown*   = 7;
  mouseUp*     = 8;
  mouseWheel*  = 9;

  (* Mouse Buttons *)
  btnLeft*  = 0;
  btnRight* = 1;
  btnMid*   = 2;

  (* Random Modulo *)
  randomModulo* = 2147483647; (* =2^31-1 *)

  (* Key Codes *)
  kA*           = 4;
  kB*           = 5;
  kC*           = 6;
  kD*           = 7;
  kE*           = 8;
  kF*           = 9;
  kG*           = 10;
  kH*           = 11;
  kI*           = 12;
  kJ*           = 13;
  kK*           = 14;
  kL*           = 15;
  kM*           = 16;
  kN*           = 17;
  kO*           = 18;
  kP*           = 19;
  kQ*           = 20;
  kR*           = 21;
  kS*           = 22;
  kT*           = 23;
  kU*           = 24;
  kV*           = 25;
  kW*           = 26;
  kX*           = 27;
  kY*           = 28;
  kZ*           = 29;
  k1*           = 30;
  k2*           = 31;
  k3*           = 32;
  k4*           = 33;
  k5*           = 34;
  k6*           = 35;
  k7*           = 36;
  k8*           = 37;
  k9*           = 38;
  k0*           = 39;
  k1Pad*        = 89;
  k2Pad*        = 90;
  k3Pad*        = 91;
  k4Pad*        = 92;
  k5Pad*        = 93;
  k6Pad*        = 94;
  k7Pad*        = 95;
  k8Pad*        = 96;
  k9Pad*        = 97;
  k0Pad*        = 98;
  kF1*          = 58;
  kF2*          = 59;
  kF3*          = 60;
  kF4*          = 61;
  kF5*          = 62;
  kF6*          = 63;
  kF7*          = 64;
  kF8*          = 65;
  kF9*          = 66;
  kF10*         = 67;
  kF11*         = 68;
  kF12*         = 69;
  kEsc*         = 41;
  kTilde*       = 53;
  kMinus*       = 45;
  kEquals*      = 46;
  kBackspace*   = 42;
  kTab*         = 43;
  kOpenBrace*   = 47;
  kCloseBrace*  = 48;
  kEnter*       = 40;
  kColon*       = 51;
  kQuote*       = 52;
  kBackslash*   = 49;
  kBackslash2*  = 100;
  kComma*       = 54;
  kStop*        = 55;
  kSlash*       = 56;
  kSpace*       = 44;
  kInsert*      = 73;
  kDel*         = 76;
  kHome*        = 74;
  kEnd*         = 77;
  kPgUp*        = 75;
  kPgDn*        = 78;
  kLeft*        = 80;
  kRight*       = 79;
  kUp*          = 82;
  kDown*        = 81;
  kSlashPad*    = 84;
  kAsterisk*    = 85;
  kMinusPad*    = 86;
  kPlusPad*     = 87;
  kDelPad*      = 99;
  kEnterPad*    = 88;
  kPrtScr*      = 70;
  kPause*       = 72;

  kModifiers*   = 115;

  kLShift*      = 225;
  kRShift*      = 229;
  kLCtrl*       = 224;
  kRCtrl*       = 228;
  kAlt*         = 226;
  kAltGr*       = 230;
  kLWin*        = 227;
  kRWin*        = 231;
  kMenu*        = 123;
  kScrLock*     = 124;
  kNumLock*     = 125;
  kCapsLock*    = 126;

  kMax*         = 127;

  (* Modifiers Set *)
  mLShift*   = 0;
  mRShift*   = 1;
  mLCtrl*    = 6;
  mRCtrl*    = 7;
  mLAlt*     = 8;
  mRAlt*     = 9;
  mLGui*     = 10;
  mRGui*     = 11;
  mNum*      = 12;
  mCaps*     = 13;
  mMode*     = 14;
  mReserved* = 15;
  mCtrl*  = {mLCtrl, mRCtrl};
  mShift* = {mLShift, mRShift};
  mAlt*   = {mLAlt, mRAlt};
  mGui*   = {mLGui, mRGui};

TYPE
  ADDRESS = SYSTEM.ADRINT;
  CHAR = SYSTEM.CHAR8;
  SET32 = SET;

  Bitmap* = POINTER TO BitmapDesc;
  BitmapDesc* = RECORD
    surface: SDL.Surface;
    w*, h*: INTEGER
  END;

  MonoFont* = POINTER TO MonoFontDesc;
  MonoFontDesc* = RECORD
    bmp*: Bitmap;
    charW*, charH*: INTEGER;
    charRows*, charsInRow*: INTEGER;
    sprites*: POINTER TO ARRAY OF ARRAY OF SDL.Rect
  END;

  CharGeo* = RECORD
    w*, x*, y*: INTEGER
  END;

  Font* = POINTER TO FontDesc;
  FontDesc* = RECORD
    bmp*: Bitmap;
    geo*: ARRAY 512 OF CharGeo;
    geoCount*: INTEGER;
    h*: INTEGER
  END;

  KeyArray = SDL.KeyArray;

  Key* = RECORD
    code*: INTEGER; (* Physical key code *)
    sym*: INTEGER; (* Virtual key code *)
    mod*: SET; (* Key modifiers *)
    repeat*: BOOLEAN
  END;

  Region* = RECORD
    x*, y*, w*, h*: INTEGER
  END;

  (* SDL Net *)
  (*
  IPAddress* = Net.IPAddress;
  Socket* = Net.TCPSocket;

  SocketSet* = Net.SocketSet;

  PStr* = POINTER TO ARRAY OF CHAR;

  NetBuf* = POINTER TO NetBufDesc;
  NetBufDesc* = RECORD
    s: PStr;
    len: INTEGER; (* Actual used length of the buffer *)
    lastLen: INTEGER
  END;
  *)

  Event* = RECORD
    type*: INTEGER;
    key*: Key;
    x*, y*: INTEGER;
    xRel*, yRel*: INTEGER;
    button*: INTEGER;
    buttons*: SET; (* What mouse buttons are pressed *)
    down*: BOOLEAN;
    s*: ARRAY 32 OF CHAR
  END;

  EventQueue* = RECORD
    buf: ARRAY 256 OF Event;
    first, last: INTEGER; (* Index of first and last element *)
    len: INTEGER (* Amount of elements currently in queue *)
  END;

  CloseBtnProc* = PROCEDURE;

VAR
  window: SDL.Window;
  renderer: SDL.Renderer;
  screen: Bitmap;
  screenTexture: SDL.Texture;
  events: EventQueue;
  keyPressed: INTEGER;

  sizeStepX, sizeStepY: INTEGER;
  wantW, wantH: INTEGER;
  wantFullscreen, wantBuffer, wantSpread: BOOLEAN;
  wantSharpPixels, wantSoftware: BOOLEAN;
  wantMouse, wantSound, wantNet: BOOLEAN;
  wantFPS: INTEGER;
  buffer: Bitmap;
  lastFlip: INTEGER;
  frames, framesT: INTEGER;
  screenAlpha: INTEGER;

  (* Flip Region *)
  flipRegion: Region;

  (* Mouse *)
  mouseX, mouseY: INTEGER;
  mouseFocusX, mouseFocusY: INTEGER;
  lastBlitMouseOutside: BOOLEAN;
  lastBlitMouseX, lastBlitMouseY: INTEGER;
  needRedrawMouse: BOOLEAN; (* True if mouse has moved since last redraw *)
  showMouse: BOOLEAN; (* Whether to show mouse pointer on screen *)
  stdMousePointer: Bitmap;
  mousePointer: Bitmap;
  underMouse: Bitmap; (* Buffer to copy part of image under the mouse *)

  randomSeed-: INTEGER;

PROCEDURE -AAIncludeSDL2h0 '#include "SDL2.h0"';

(* C Macros *)

PROCEDURE LoadBmp(IN filename: ARRAY OF CHAR): SDL.Surface;
BEGIN
  RETURN SDL.LoadBmpRW(SDL.RWFromFile(filename, "rb"), 1)
END LoadBmp;

(* General *)

PROCEDURE GetError*(VAR s: ARRAY OF CHAR);
TYPE P = POINTER [1] TO ARRAY 10240 OF CHAR;
VAR p: P;
BEGIN
  p := SYSTEM.VAL(P, SDL.GetError());
  s := p^$
END GetError;

PROCEDURE Settings*(w, h: INTEGER; flags: SET);
BEGIN (*!TODO: Refactor, save options in a SET*)
  wantW := w; wantH := h;
  wantFullscreen := fullscreen IN flags;
  wantBuffer := buffered IN flags;
  wantSpread := spread IN flags;
  wantSharpPixels := sharpPixels IN flags;
  wantSoftware := software IN flags;
  wantMouse := initMouse IN flags;
  showMouse := wantMouse;
  wantNet := initNet IN flags;
  wantSound := initSound IN flags
END Settings;

PROCEDURE SetSizeStep*(w, h: INTEGER);
BEGIN
  sizeStepX := w; sizeStepY := h
END SetSizeStep;

PROCEDURE SetFPS*(fps: INTEGER);
BEGIN
  IF fps <= 0 THEN fps := -1 END;
  wantFPS := fps
END SetFPS;

PROCEDURE GetDesktopResolution*(VAR w, h: INTEGER);
VAR mode: SDL.DisplayMode;
BEGIN
  SDL.GetDesktopDisplayMode(0, mode);
  w := mode.w; h := mode.h
END GetDesktopResolution;

PROCEDURE CheckWantedScreenSize;
BEGIN
  IF (wantW = -1) OR (wantH = -1) THEN
    GetDesktopResolution(wantW, wantH);
    IF ~wantFullscreen THEN
      DEC(wantW, 20); DEC(wantH, 50)
    END
  END
END CheckWantedScreenSize;

(* Flip Region *)
PROCEDURE SetRegion*(x, y, w, h: INTEGER);
BEGIN
  flipRegion.x := x;  flipRegion.y := y;
  flipRegion.w := w;  flipRegion.h := h
END SetRegion;

PROCEDURE UnsetRegion*;
BEGIN
  flipRegion.w := -1
END UnsetRegion;

PROCEDURE AddRegion*(x, y, w, h: INTEGER);
BEGIN
  IF flipRegion.w = -1 THEN (* No flip region yet *)
    SetRegion(x, y, w, h) (* Just set it *)
  ELSE (* Flip Region exists, add to it *)
    IF x < flipRegion.x THEN flipRegion.x := x END;
    IF y < flipRegion.y THEN flipRegion.y := y END;
    IF x + w > flipRegion.x + flipRegion.w THEN
      flipRegion.w := w + x - flipRegion.x END;
    IF y + h > flipRegion.y + flipRegion.h THEN
      flipRegion.h := h + y - flipRegion.y END
  END
END AddRegion;

(* Drawing *)

PROCEDURE MakeCol*(r, g, b: INTEGER): INTEGER;
BEGIN
  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, r) * {0..7});
  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, g) * {0..7});
  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, b) * {0..7});
  RETURN SYSTEM.LSH(SYSTEM.LSH(0FF00H + b, 8) + g, 8) + r
END MakeCol;

PROCEDURE ColorToRGB*(color: INTEGER; VAR r, g, b: INTEGER);
BEGIN
  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, color) * {0..7});
  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -8)) * {0..7});
  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -16)) * {0..7})
END ColorToRGB;

PROCEDURE ColorToRGBA*(color: INTEGER; VAR r, g, b, a: INTEGER);
BEGIN
  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, color) * {0..7});
  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -8)) * {0..7});
  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -16)) * {0..7});
  a := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -24)) * {0..7})
END ColorToRGBA;

PROCEDURE BmpCol*(bmp: Bitmap; r, g, b: INTEGER): INTEGER;
BEGIN
  RETURN SDL.MapRGB(bmp.surface.format, SHORT(r), SHORT(g), SHORT(b))
END BmpCol;

PROCEDURE ClearToColor*(bmp: Bitmap; color: INTEGER);
BEGIN
  SDL.FillRectNil(bmp.surface, color)
END ClearToColor;

PROCEDURE ClearBitmap*(bmp: Bitmap);
BEGIN
  ClearToColor(bmp, MakeCol(0, 0, 0))
END ClearBitmap;

PROCEDURE ClearScreenToColor*(color: INTEGER);
BEGIN
  ClearToColor(screen, color)
END ClearScreenToColor;

PROCEDURE ClearScreen*;
BEGIN
  ClearToColor(screen, MakeCol(0, 0, 0))
END ClearScreen;

PROCEDURE LockBitmap*(bmp: Bitmap);
BEGIN
  SDL.LockSurface(bmp.surface)
END LockBitmap;

PROCEDURE UnlockBitmap*(bmp: Bitmap);
BEGIN
  SDL.UnlockSurface(bmp.surface)
END UnlockBitmap;

PROCEDURE PutPixelFast*(bmp: Bitmap; x, y, color: INTEGER);
VAR n: ADDRESS;
BEGIN n := SYSTEM.VAL(ADDRESS, bmp.surface.pixels);
  INC(n, (y * bmp.w + x) * 4);
  SYSTEM.PUT(n, color)
END PutPixelFast;

PROCEDURE PutPixel*(bmp: Bitmap; x, y, color: INTEGER);
VAR n: ADDRESS;
BEGIN
  IF (x >= 0) & (x < bmp.w) & (y >= 0) & (y < bmp.h) THEN
    SDL.LockSurface(bmp.surface);
    n := SYSTEM.VAL(ADDRESS, bmp.surface.pixels);
    INC(n, (y * bmp.w + x) * 4);
    SYSTEM.PUT(n, color);
    SDL.UnlockSurface(bmp.surface)
  END
END PutPixel;

PROCEDURE GetPixelFast*(bmp: Bitmap; x, y: INTEGER): INTEGER;
VAR color: INTEGER;
  n: ADDRESS;
BEGIN
  n := SYSTEM.VAL(ADDRESS, bmp.surface.pixels);
  INC(n, (y * bmp.w + x) * 4);
  SYSTEM.GET(n, color);
RETURN color END GetPixelFast;

PROCEDURE GetPixel*(bmp: Bitmap; x, y: INTEGER): INTEGER;
VAR color: INTEGER;
  n: ADDRESS;
BEGIN
  IF (x >= 0) & (x < bmp.w) &
     (y >= 0) & (y < bmp.h) THEN
    SDL.LockSurface(bmp.surface);
    n := SYSTEM.VAL(ADDRESS, bmp.surface.pixels);
    INC(n, (y * bmp.w + x) * 4);
    SYSTEM.GET(n, color);
    SDL.UnlockSurface(bmp.surface)
  ELSE color := 0 END;
  RETURN color
END GetPixel;

PROCEDURE HLine*(bmp: Bitmap; x1, y, x2, color: INTEGER);
VAR rect: SDL.Rect; t: INTEGER;
BEGIN
  IF x1 > x2 THEN t := x1; x1 := x2; x2 := t END;
  rect.x := x1; rect.y := y;
  rect.w := x2 - x1 + 1; rect.h := 1;
  SDL.FillRect(bmp.surface, rect, color)
END HLine;

PROCEDURE VLine*(bmp: Bitmap; x, y1, y2, color: INTEGER);
VAR rect: SDL.Rect; t: INTEGER;
BEGIN
  IF y1 > y2 THEN t := y1; y1 := y2; y2 := t END;
  rect.x := x; rect.y := y1;
  rect.w := 1; rect.h := y2 - y1 + 1;
  SDL.FillRect(bmp.surface, rect, color)
END VLine;

PROCEDURE Line*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
VAR x, y, i, dx, dy, sx, sy, e: INTEGER; vert: BOOLEAN;
BEGIN
  IF x1 = x2 THEN VLine(bmp, x1, y1, y2, color)
  ELSIF y1 = y2 THEN HLine(bmp, x1, y1, x2, color)
  ELSE
    SDL.LockSurface(bmp.surface);
    dx := ABS(x1 - x2); dy := ABS(y1 - y2);
    IF x2 > x1 THEN sx := 1 ELSE sx := -1 END;
    IF y2 > y1 THEN sy := 1 ELSE sy := -1 END;
    x := x1; y := y1; vert := dy > dx;
    IF vert THEN i := dx; dx := dy; dy := i END;
    e := 2 * dy - dx;
    FOR i := 0 TO dx DO
      IF (x >= 0) & (x < bmp.w) &
         (y >= 0) & (y < bmp.h) THEN
        PutPixelFast(bmp, x, y, color)
      END;
      IF e >= 0 THEN
        IF vert THEN INC(x, sx) ELSE INC(y, sy) END;
        DEC(e, 2 * dx)
      END;
      IF vert THEN INC(y, sy) ELSE INC(x, sx) END;
      INC(e, 2 * dy)
    END;
    SDL.UnlockSurface(bmp.surface)
  END
END Line;

PROCEDURE FastLine*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
BEGIN
  (*Al.FastLine(bmp.bmp, x1, y1, x2, y2, color)*)
END FastLine;

PROCEDURE Rect*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER); (*!FIXME*)
VAR rect: SDL.Rect;
BEGIN
  rect.x := x1; rect.y := y1;
  rect.w := 1; rect.h := y2 - y1 + 1;
  SDL.FillRect(bmp.surface, rect, color);
  rect.x := x2;
  SDL.FillRect(bmp.surface, rect, color);
  rect.x := x1; rect.w := x2 - x1 + 1; rect.h := 1;
  SDL.FillRect(bmp.surface, rect, color);
  rect.y := y2;
  SDL.FillRect(bmp.surface, rect, color)
END Rect;

PROCEDURE RectFill*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
VAR rect: SDL.Rect;
BEGIN
  rect.x := x1; rect.y := y1;
  rect.w := x2 - x1 + 1; rect.h := y2 - y1 + 1;
  SDL.FillRect(bmp.surface, rect, color)
END RectFill;

PROCEDURE Circle*(bmp: Bitmap; x, y, r, color: INTEGER);
BEGIN
END Circle;

PROCEDURE CircleFill*(bmp: Bitmap; x, y, r, color: INTEGER);
BEGIN
END CircleFill;

PROCEDURE Ellipse*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
BEGIN
END Ellipse;

PROCEDURE EllipseFill*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
BEGIN
END EllipseFill;

PROCEDURE FloodFill*(bmp: Bitmap; x, y, color: INTEGER);
BEGIN
END FloodFill;

(* Bitmap *)

PROCEDURE (bmp: Bitmap) Finalize*, NEW;
BEGIN
END Finalize;

PROCEDURE CreateBitmap*(w, h: INTEGER): Bitmap;
VAR bmp: Bitmap;
    s: ARRAY 2560 OF CHAR;
BEGIN
  NEW(bmp);
  bmp.surface := SDL.CreateRGBSurface(0, w, h, 32,
    000000FFH, 0000FF00H, 00FF0000H, -1000000H);
  IF bmp.surface = NIL THEN
    GetError(s); Out.String(s); Out.Ln
  END;
  bmp.w := w;  bmp.h := h;
  RETURN bmp
END CreateBitmap;

PROCEDURE DestroyBitmap*(bmp: Bitmap);
BEGIN
  SDL.FreeSurface(bmp.surface)
END DestroyBitmap;

PROCEDURE LoadBitmap*(IN filename: ARRAY OF CHAR): Bitmap;
VAR bmp: Bitmap;
BEGIN        (* LoadBmp(filename) for BMP-only *)
  NEW(bmp); bmp.surface := SDL.ImgLoad(filename);
  IF bmp.surface = NIL THEN bmp := NIL
  ELSE bmp.w := bmp.surface.w; bmp.h := bmp.surface.h END;
  RETURN bmp
END LoadBitmap;

PROCEDURE Blit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy: INTEGER);
VAR a, b: SDL.Rect;
BEGIN a.x := sx; a.y := sy; a.w := sw; a.h := sh;
  b.x := dx; b.y := dy;
  SDL.BlitSurface(src.surface, a, dest.surface, b)
END Blit;

PROCEDURE FlipBlit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy: INTEGER;
    hFlip: BOOLEAN);
VAR x, y, sx0: INTEGER;
  c1, c2, r1, r2, g1, g2, b1, b2, a1, a2: INTEGER;
BEGIN
  IF ~hFlip THEN Blit(src, dest, sx, sy, sw, sh, dx, dy)
  ELSE
    IF sx < 0 THEN DEC(sw, -sx); INC(dx, -sx); sx := 0
    ELSIF sx + sw - 1 >= src.w THEN DEC(sw, sx + sw - 1 - src.w)
    ELSIF dx + sw - 1 >= dest.w THEN DEC(sw, dx + sw - 1 - dest.w)
    END;
    IF sy < 0 THEN DEC(sh, -sy); INC(dy, -sy); sy := 0
    ELSIF sy + sh - 1 >= src.h THEN DEC(sh, sy + sh - 1 - src.h)
    ELSIF dy + sh - 1 >= dest.h THEN DEC(sh, dy + sh - 1 - dest.h)
    END;
    LockBitmap(src);
    LockBitmap(dest);
    sx0 := sx + sw - 1;
    FOR y := dy TO dy + sh - 1 DO
      sx := sx0;
      FOR x := dx TO dx + sw - 1 DO
        c1 := GetPixelFast(src, sx, sy);
        ColorToRGBA(c1, r1, g1, b1, a1);
        IF a1 # 0 THEN
          c2 := GetPixelFast(dest, x, y);
          ColorToRGBA(c2, r2, g2, b2, a2);
          IF a1 # 255 THEN
            c1 := MakeCol((r1 * a1 + r2 * (255 - a1)) DIV 256, 
                (g1 * a1 + g2 * (255 - g1)) DIV 256, 
                (b1 * a1 + b2 * (255 - b1)) DIV 256)
          END;
          PutPixelFast(dest, x, y, c1)
        END;
        DEC(sx)
      END;
      INC(sy)
    END;
    UnlockBitmap(dest);
    UnlockBitmap(src)
  END
END FlipBlit;

PROCEDURE BlitWhole*(src, dest: Bitmap; x, y: INTEGER);
VAR b: SDL.Rect;
BEGIN b.x := x; b.y := y;
  SDL.BlitSurfaceNil(src.surface, dest.surface, b)
END BlitWhole;

PROCEDURE StretchBlit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy, dw, dh: INTEGER);
VAR a, b: SDL.Rect;
BEGIN
  a.x := sx; a.y := sy; a.w := sw; a.h := sh;
  b.x := dx; b.y := dy; b.w := dw; b.h := dh;
  SDL.BlitScaled(src.surface, a, dest.surface, b)
END StretchBlit;

PROCEDURE SetScreenAlpha*(alpha: INTEGER);
BEGIN screenAlpha := alpha
END SetScreenAlpha;

PROCEDURE SetAlpha*(bmp: Bitmap; alpha: INTEGER);
BEGIN
  IF SDL.SetSurfaceAlphaMod(bmp.surface, SHORT(CHR(alpha))) = 0 THEN END
END SetAlpha;

PROCEDURE SetClip*(bmp: Bitmap; x, y, w, h: INTEGER);
VAR r: SDL.Rect;
BEGIN r.x := x; r.y := y; r.w := w; r.h := h;
  IF SDL.SetClipRect(bmp.surface, r) = 0 THEN END
END SetClip;

PROCEDURE UnsetClip*(bmp: Bitmap);
BEGIN
  IF SDL.SetClipRectNil(bmp.surface) = 0 THEN END
END UnsetClip;

PROCEDURE MaskedBlit*(src, dest: Bitmap; sx, sy, dx, dy, w, h: INTEGER);
BEGIN
  (*Al.MaskedBlit(src.bmp, dest.bmp, sx, sy, dx, dy, w, h)*)
END MaskedBlit;

PROCEDURE DrawSpriteEx*(dest, sprite: Bitmap; x, y, mode: INTEGER; flip: SET);
BEGIN
  (*Al.DrawSpriteEx(dest.bmp, sprite.bmp, x, y, mode, flip)*)
END DrawSpriteEx;

PROCEDURE DrawCharacterEx*(dest, sprite: Bitmap; x, y, color, bg: INTEGER);
BEGIN
  (*Al.DrawCharacterEx(dest.bmp, sprite.bmp, x, y, color, bg)*)
END DrawCharacterEx;

PROCEDURE SetColorKey*(bmp: Bitmap; color: INTEGER);
BEGIN
  SDL.SetColorKey(bmp.surface, 1, color)
END SetColorKey;

PROCEDURE SetColorMod*(bmp: Bitmap; r, g, b: INTEGER);
BEGIN
  SDL.SetSurfaceColorMod(bmp.surface, r, g, b)
END SetColorMod;

(* MonoFont *)

PROCEDURE LoadMonoFont*(IN filename: ARRAY OF CHAR; charW, charH: INTEGER): MonoFont;
VAR bmp: Bitmap; font: MonoFont;
    x, y, sx, sy, tmp: INTEGER;
BEGIN
  bmp := LoadBitmap(filename);
  IF bmp = NIL THEN font := NIL
  ELSE
    bmp.surface := SDL.ConvertSurface(bmp.surface,
      screen.surface.format, 0);
    SetColorKey(bmp, BmpCol(bmp, 0, 0, 0));
    NEW(font); font.bmp := bmp;
    font.charW := charW; font.charH := charH;
    font.charsInRow := font.bmp.w DIV charW;
    font.charRows := font.bmp.h DIV charH;
    (*!FIXME remove sprites from here at all*)
    NEW(font.sprites, font.charRows, font.charsInRow);
    sy := 0;
    FOR y := 0 TO font.charRows - 1 DO
      sx := 0;
      FOR x := 0 TO font.charsInRow - 1 DO
        font.sprites[y, x].x := sx;
        font.sprites[y, x].y := sy;
        font.sprites[y, x].w := charW;
        font.sprites[y, x].h := charH;
        INC(sx, charW)
      END;
      INC(sy, charH)
    END
  END;
  RETURN font
END LoadMonoFont;

PROCEDURE DrawCharacter*(dest: Bitmap; font: MonoFont;
  x, y: INTEGER; ch: CHAR; fg: INTEGER);
VAR fx, fy, r, g, b: INTEGER; dstRect: SDL.Rect;
BEGIN
  dstRect.x := x; dstRect.y := y;
  fx := ORD(ch) MOD font.charsInRow;
  fy := ORD(ch) DIV font.charsInRow;
  ColorToRGB(fg, r, g, b);
  SDL.SetSurfaceColorMod(font.bmp.surface, r, g, b);
  SDL.BlitSurface(font.bmp.surface, font.sprites[fy, fx],
    screen.surface, dstRect)
END DrawCharacter;

PROCEDURE DrawString*(dest: Bitmap; font: MonoFont;
  x, y: INTEGER; IN s: ARRAY OF CHAR; fg: INTEGER);
VAR i, cx: INTEGER;
BEGIN
  i := 0; cx := x;
  WHILE (s[i] # 0X) & (cx < dest.w) DO
    DrawCharacter(dest, font, cx, y, s[i], fg);
    INC(i); INC(cx, font.charW)
  END
END DrawString;

(* Font *)

PROCEDURE LoadFont*(IN fname: ARRAY OF CHAR): Font;
VAR T: Texts.Text;
  S: Texts.Scanner;
  i: INTEGER;
  s: ARRAY 512 OF CHAR;
  f: Font;
BEGIN NEW(f);
  s := fname$; Strings.Append(".png", s);
  f.bmp := LoadBitmap(s);
  IF f.bmp = NIL THEN f := NIL
  ELSE
    s := fname$; Strings.Append(".dat", s);
    NEW(T); Texts.Open(T, s);
    Texts.OpenScanner(S, T, 0);
    Texts.Scan(S);
    IF S.eot THEN f := NIL
    ELSE
      IF S.class = Texts.Int THEN f.h := SHORT(S.i) ELSE f.h := 0 END;
      Texts.Scan(S); i := 0;
      WHILE ~S.eot & (S.class = Texts.Int) DO
        f.geo[i].w := SHORT(S.i); Texts.Scan(S);
        f.geo[i].x := SHORT(S.i); Texts.Scan(S);
        f.geo[i].y := SHORT(S.i); Texts.Scan(S);
        INC(i)
      END;
      f.geoCount := i
    END
  END;
RETURN f END LoadFont;

PROCEDURE GetCharGeometry*(font: Font; ch: INTEGER; VAR fx, fy, w: INTEGER);
VAR i: INTEGER;
BEGIN
  IF (32 <= ch) & (ch <= 127) THEN i := ch - 32
  ELSIF (1040 <= ch) & (ch <= 1103) THEN i := ch - 1040 + 95
  ELSIF ch = 1025 THEN i := 64 + 95
  ELSIF ch = 1105 THEN i := 65 + 95
  ELSE i := font.geoCount
  END;
  IF i >= font.geoCount THEN i := 31 (*"?"*) END;
  w  := font.geo[i].w;
  fx := font.geo[i].x;
  fy := font.geo[i].y
END GetCharGeometry;

PROCEDURE GetTextWidth*(font: Font; IN s: ARRAY OF CHAR): INTEGER;
VAR i, w, tw, maxTw, fx, fy: INTEGER;
  c1, c2: CHAR;
  ch: INTEGER;
BEGIN
  maxTw := 0; tw := 0; i := 0;
  IF font # NIL THEN
    WHILE (i < LEN(s) - 1) & (s[i] # 0X) DO
      c1 := s[i]; c2 := s[i + 1];
      IF c1 < 80X THEN ch := ORD(c1); INC(i)
      ELSE ch := ORD(c1) MOD 32 * 64 + ORD(c2) MOD 64; INC(i, 2)
      END;
      IF ch = 0AH THEN
        IF tw > maxTw THEN maxTw := tw END;
        tw := 0
      ELSE
        GetCharGeometry(font, ch, fx, fy, w);
        INC(tw, w)
      END
    END
  END;
  IF tw > maxTw THEN maxTw := tw END;
RETURN maxTw END GetTextWidth;

PROCEDURE DrawChar*(dest: Bitmap; font: Font; x, y, ch, color: INTEGER;
    VAR w: INTEGER);
VAR r, g, b, fx, fy: INTEGER; (* Font X, Y *)
BEGIN
  IF font # NIL THEN
    GetCharGeometry(font, ch, fx, fy, w);
    IF ch # 32 (*space*) THEN
      ColorToRGB(color, r, g, b);
      SetColorMod(font.bmp, r, g, b);
      Blit(font.bmp, dest, fx, fy, w, font.h, x, y)
    END
  END
END DrawChar;

PROCEDURE DrawText*(dest: Bitmap; font: Font; x, y: INTEGER;
    IN s: ARRAY OF CHAR; color: INTEGER);
VAR x0, i, w: INTEGER;
  c1, c2: CHAR;
  ch: INTEGER;
BEGIN
  IF font # NIL THEN
    x0 := x; i := 0;
    WHILE (i < LEN(s) - 1) & (s[i] # 0X) DO
      c1 := s[i]; c2 := s[i + 1];
      IF c1 < 80X THEN ch := ORD(c1); INC(i)
      ELSE ch := ORD(c1) MOD 32 * 64 + ORD(c2) MOD 64; INC(i, 2)
      END;
      IF ch = 0AH THEN INC(y, font.h); x := x0
      ELSE DrawChar(dest, font, x, y, ch, color, w); INC(x, w)
      END
    END
  END
END DrawText;

(* Events *)

PROCEDURE StartTextInput*;
BEGIN SDL.StartTextInput
END StartTextInput;

PROCEDURE StopTextInput*;
BEGIN SDL.StopTextInput
END StopTextInput;

PROCEDURE QueueEvent;
BEGIN
  INC(events.len); INC(events.last);
  IF events.last = LEN(events.buf) THEN events.last := 0 END
END QueueEvent;

PROCEDURE PumpKeyDown(VAR event: SDL.Event);
VAR e: SDL.KeyboardEvent;
  n: INTEGER; mod: SET;
BEGIN
  IF events.len < LEN(events.buf) THEN
    e := SYSTEM.VAL(SDL.KeyboardEvent, SYSTEM.ADR(event));
    n := e.keysym.mod; mod := SYSTEM.VAL(SET32, n);
    QueueEvent;
    events.buf[events.last].type := keyDown;
    events.buf[events.last].key.code := e.keysym.scancode;
    events.buf[events.last].key.sym := e.keysym.sym;
    events.buf[events.last].key.mod := mod;
    events.buf[events.last].key.repeat := e.repeat # 0;
    INC(keyPressed)
  END
END PumpKeyDown;

PROCEDURE PumpTextEvent(event: SDL.Event);
VAR sym: INTEGER;
    e: SDL.TextInputEvent;
BEGIN
  IF events.len < LEN(events.buf) THEN
    e := SYSTEM.VAL(SDL.TextInputEvent, SYSTEM.ADR(event));
    IF e.text[1] = 0X THEN (* ASCII character *)
      sym := ORD(e.text[0])
    ELSE (* Unicode character. Assuming 2 bytes *)
      sym := ORD(e.text[1]);
      (* UTF-8 cyrillic *)
      IF (e.text[0] = 0D0X) OR (e.text[0] = 0D1X) THEN
        IF e.text[0] = 0D0X THEN DEC(sym, 090H)
        ELSE DEC(sym, 060H - 16)
        END;
        (* Convert to CP866 *)
        IF sym = 65 THEN sym := 0F1H (* jo *)
        ELSIF sym = -15 THEN sym := 0F0H (* JO *)
        ELSIF sym < 48 THEN INC(sym, 80H) (* A..JA, a..p *)
        ELSE INC(sym, 0E0H - 48) (* r..ja *)
        END
      END
    END;
    QueueEvent;
    events.buf[events.last].type := textInput;
    events.buf[events.last].key.sym := sym; (*!FIXME sym needed?*)
    events.buf[events.last].s := e.text$
  END
END PumpTextEvent;

PROCEDURE UpdateMousePos(event: SDL.Event);
VAR e: SDL.MouseMotionEvent; newX, newY: INTEGER;
BEGIN
  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
  newX := e.x; newY := e.y;
  IF newX < 0 THEN newX := 0
  ELSIF newX >= screen.w THEN newX := screen.w - 1 END;
  IF newY < 0 THEN newY := 0
  ELSIF newY >= screen.h THEN newY := screen.h - 1 END;
  IF (newX # mouseX) OR (newY # mouseY) THEN
    mouseX := newX;  mouseY := newY;
    needRedrawMouse := TRUE
  END
END UpdateMousePos;

(* Keyboard *)

PROCEDURE GetKeyArray(): KeyArray;
BEGIN
  RETURN SYSTEM.VAL(KeyArray, SDL.GetKeyboardStateNil())
END GetKeyArray;

PROCEDURE KeyDown*(key: INTEGER): BOOLEAN;
VAR keys: KeyArray;
BEGIN
  keys := GetKeyArray();
  RETURN keys[key]
END KeyDown;

PROCEDURE AltPressed*(): BOOLEAN;
VAR keys: KeyArray;
BEGIN
  keys := GetKeyArray();
  RETURN keys[kAlt] OR keys[kAltGr]
END AltPressed;

PROCEDURE ShiftPressed*(): BOOLEAN;
VAR keys: KeyArray;
BEGIN
  keys := GetKeyArray();
  RETURN keys[kLShift] OR keys[kRShift]
END ShiftPressed;

PROCEDURE CtrlPressed*(): BOOLEAN;
VAR keys: KeyArray;
BEGIN
  keys := GetKeyArray();
  RETURN keys[kLCtrl] OR keys[kRCtrl]
END CtrlPressed;

(* Mouse *)

PROCEDURE MouseOnScreen*(): BOOLEAN;
VAR flags: SET;
BEGIN
  flags := SDL.GetWindowFlags(window);
  RETURN SDL.windowMouseFocus IN flags
END MouseOnScreen;

PROCEDURE ShowMouse*(show: BOOLEAN);
BEGIN
  showMouse := show
END ShowMouse;

PROCEDURE GetRealMousePos*(VAR x, y: INTEGER);
BEGIN
  IF SDL.GetMouseState(x, y) = 0 THEN END
END GetRealMousePos;

PROCEDURE GetMousePos*(VAR x, y: INTEGER);
BEGIN
  x := mouseX; y := mouseY
END GetMousePos;

PROCEDURE GetMouseButtons*(): SET;
VAR x, y: INTEGER;
BEGIN RETURN SYSTEM.VAL(SET32, SDL.GetMouseState(x, y))
END GetMouseButtons;

PROCEDURE CreateStdMousePointer*;
VAR b: Bitmap; fg, bg: INTEGER;
BEGIN b := CreateBitmap(12, 19);
  bg := MakeCol(255, 0, 255); fg := MakeCol(0, 0, 0);
  ClearToColor(b, bg); SetColorKey(b, bg);
  Line(b, 0, 0, 10, 10, fg); Line(b, 0, 0, 0, 14, fg);
  Line(b, 0, 14, 3, 11, fg); Line(b, 10, 10, 6, 10, fg);
  Line(b, 4, 12, 6, 17, fg); Line(b, 6, 11, 9, 17, fg);
  Line(b, 7, 18, 8, 18, fg); bg := MakeCol(255, 255, 255);
  VLine(b, 1, 2, 12, bg); VLine(b, 2, 3, 11, bg);
  VLine(b, 3, 4, 10, bg); VLine(b, 4, 5, 11, bg);
  VLine(b, 5, 6, 13, bg); VLine(b, 6, 7, 9, bg);
  VLine(b, 7, 8, 9, bg); VLine(b, 8, 9, 9, bg);
  VLine(b, 6, 12, 15, bg); VLine(b, 7, 14, 17, bg);
  VLine(b, 8, 16, 17, bg);
  stdMousePointer := b
END CreateStdMousePointer;

PROCEDURE SetMouseFocus*(x, y: INTEGER);
BEGIN
  mouseFocusX := x; mouseFocusY := y;
  needRedrawMouse := TRUE
END SetMouseFocus;

PROCEDURE SetMousePointer*(bmp: Bitmap; x, y: INTEGER);
BEGIN
  IF bmp = NIL THEN
    mousePointer := stdMousePointer;
    x := 0; y := 0
  ELSE mousePointer := bmp
  END;
  SetMouseFocus(x, y);
  underMouse := CreateBitmap(mousePointer.w, mousePointer.h);
  needRedrawMouse := TRUE
END SetMousePointer;

PROCEDURE GetMousePointer*(): Bitmap;
BEGIN
  RETURN mousePointer
END GetMousePointer;

PROCEDURE SetStdMousePointer*;
BEGIN
  SetMousePointer(stdMousePointer, 0, 0)
END SetStdMousePointer;

PROCEDURE InitMouseData;
BEGIN
  CreateStdMousePointer;
  SetStdMousePointer;
  mouseX := -1
END InitMouseData;

(* Misc *)
PROCEDURE SetWindowTitle*(IN title: ARRAY OF CHAR);
BEGIN
  SDL.SetWindowTitle(window, title)
END SetWindowTitle;

PROCEDURE SetWindowIcon*(icon: Bitmap);
BEGIN
  SDL.SetWindowIcon(window, icon.surface)
END SetWindowIcon;

PROCEDURE SwitchToWindowed*;
BEGIN
  IF wantFullscreen THEN
    SDL.SetWindowSize(window, screen.w, screen.h);
    IF SDL.SetWindowFullscreen(window, {}) = 0 THEN
      wantFullscreen := FALSE
    END
  END
END SwitchToWindowed;

PROCEDURE SwitchToFullscreen*;
BEGIN
  IF ~wantFullscreen THEN
    IF SDL.SetWindowFullscreen(window, SDL.windowFullscreenDesktop) = 0 THEN
      wantFullscreen := TRUE
    END
  END
END SwitchToFullscreen;

PROCEDURE ToggleFullscreen*;
BEGIN
  IF wantFullscreen THEN SwitchToWindowed
  ELSE SwitchToFullscreen
  END
END ToggleFullscreen;

PROCEDURE Delay*(n: INTEGER);
BEGIN
  SDL.Delay(n)
END Delay;

PROCEDURE HandleMouseButton(VAR event: SDL.Event);
VAR e: SDL.MouseButtonEvent;
BEGIN

END HandleMouseButton;

PROCEDURE PumpQuit;
BEGIN
  IF events.len < LEN(events.buf) THEN
    QueueEvent;
    events.buf[events.last].type := quit
  END
END PumpQuit;

PROCEDURE PumpMouseMove(VAR event: SDL.Event);
VAR e: SDL.MouseMotionEvent;
    newX, newY: INTEGER;
BEGIN
  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
  newX := e.x; newY := e.y;
  IF newX < 0 THEN newX := 0
  ELSIF newX >= screen.w THEN newX := screen.w - 1
  END;
  IF newY < 0 THEN newY := 0
  ELSIF newY >= screen.h THEN newY := screen.h - 1
  END;
  IF (newX # mouseX) OR (newY # mouseY) THEN
    mouseX := newX; mouseY := newY;
    needRedrawMouse := TRUE;
    IF events.len < LEN(events.buf) THEN
      QueueEvent;
      events.buf[events.last].type := mouseMove;
      events.buf[events.last].x := newX;
      events.buf[events.last].y := newY;
      events.buf[events.last].xRel := e.xRel;
      events.buf[events.last].yRel := e.yRel;
      events.buf[events.last].buttons := SYSTEM.VAL(SET32, e.state)
    END
  END
END PumpMouseMove;

PROCEDURE PumpMouseButton(VAR event: SDL.Event; type: INTEGER);
VAR e: SDL.MouseButtonEvent;
BEGIN
  e := SYSTEM.VAL(SDL.MouseButtonEvent, SYSTEM.ADR(event));
  IF events.len < LEN(events.buf) THEN
    QueueEvent;
    events.buf[events.last].type := type;
    events.buf[events.last].button := e.button - 1;
    events.buf[events.last].down := e.state # 0;
    IF e.x < 0 THEN e.x := 0
    ELSIF e.x >= screen.w THEN e.x := screen.w - 1
    END;
    IF e.y < 0 THEN e.y := 0
    ELSIF e.y >= screen.h THEN e.y := screen.h - 1
    END;
    events.buf[events.last].x := e.x;
    events.buf[events.last].y := e.y;
    events.buf[events.last].key.mod := SDL.GetModState()
  END
END PumpMouseButton;

PROCEDURE RepeatFlip*;
BEGIN
  IF screenTexture # NIL THEN
    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL.RenderClear(renderer);
    SDL.RenderCopyNil(renderer, screenTexture);
    SDL.RenderPresent(renderer)
  END
END RepeatFlip;

PROCEDURE WaitEvents*(timeout: INTEGER);
VAR event: SDL.Event; n: INTEGER;
BEGIN
  n := SDL.PollEvent(event);
  IF (n # 0) OR (events.len = 0) THEN
    IF n = 0 THEN
      IF timeout > 0 THEN n := SDL.WaitEventTimeout(event, timeout)
      ELSIF timeout < 0 THEN n := SDL.WaitEvent(event)
      END
    END;
    IF n # 0 THEN
      REPEAT
        IF event.type = SDL.mouseMotion THEN
          PumpMouseMove(event)
        ELSIF event.type = SDL.mouseButtonDown THEN
          PumpMouseButton(event, mouseDown)
        ELSIF event.type = SDL.mouseButtonUp THEN
          PumpMouseButton(event, mouseUp)
        ELSIF event.type = SDL.keyDown THEN
          PumpKeyDown(event)
        ELSIF event.type = SDL.textInput THEN
          PumpTextEvent(event)
        ELSIF event.type = SDL.quit THEN
          PumpQuit
        END
      UNTIL SDL.PollEvent(event) = 0
    END
  END
END WaitEvents;

PROCEDURE PollEvent*(VAR event: Event): BOOLEAN;
VAR hasEvent: BOOLEAN;
BEGIN
  IF events.len > 0 THEN
    event := events.buf[events.first];
    IF event.type = keyDown THEN DEC(keyPressed) END;
    DEC(events.len); INC(events.first);
    IF events.first = LEN(events.buf) THEN events.first := 0 END;
    hasEvent := TRUE
  ELSE hasEvent := FALSE
  END;
  RETURN hasEvent
END PollEvent;

PROCEDURE KeyPressed*(): BOOLEAN;
BEGIN
  WaitEvents(0);
  RETURN keyPressed > 0
END KeyPressed;

PROCEDURE ReadKey*(): CHAR;
VAR event: Event; done: BOOLEAN; ch: CHAR;
BEGIN done := FALSE;
  REPEAT
    WaitEvents(-1);
    WHILE PollEvent(event) DO
      CASE event.type OF
        keyDown: ch := SHORT(CHR(event.key.sym)); done := TRUE
      | quit: ch := 0X; done := TRUE
      ELSE
      END
    END
  UNTIL done;
  RETURN ch
END ReadKey;

PROCEDURE Pause*;
BEGIN IF ReadKey() = 0X THEN END
END Pause;

PROCEDURE WindowShown*(): BOOLEAN;
VAR flags: SET;
BEGIN
  flags := SDL.GetWindowFlags(window);
  RETURN SDL.windowShown IN flags
END WindowShown;

PROCEDURE GetTicks*(): INTEGER;
BEGIN
  RETURN SDL.GetTicks()
END GetTicks;

PROCEDURE Flip*;
VAR mx, my: INTEGER; (* Mouse bitmap X Y *)
    blitMouse: BOOLEAN;
    dt: INTEGER; (* Delta time *)

  PROCEDURE PrepareMouse;
  BEGIN
    mx := mouseX - mouseFocusX;
    my := mouseY - mouseFocusY;
    (* Save image under mouse from buffer *)
    Blit(screen, underMouse, mx, my,
      underMouse.w, underMouse.h, 0, 0);
    (* Blit mouse pointer onto buffer *)
    IF mouseX # -1 THEN
      Blit(mousePointer, screen, 0, 0,
        mousePointer.w, mousePointer.h, mx, my)
    END
  END PrepareMouse;

  PROCEDURE CleanMouse;
  BEGIN
    (* Restore image under mouse in buffer *)
    IF mouseX # -1 THEN
      Blit(underMouse, screen, 0, 0,
        underMouse.w, underMouse.h, mx, my);
    END;
    needRedrawMouse := FALSE
  END CleanMouse;

BEGIN
  IF wantFPS # -1 THEN
    IF lastFlip # -1 THEN
      dt := 1000 DIV wantFPS - (GetTicks() - lastFlip);
      IF (dt > 0) & (dt < 1000) THEN Delay(dt) END
    END;
    lastFlip := GetTicks()
  END;
  IF WindowShown() THEN
    mx := 0; my := 0;
    blitMouse := showMouse & MouseOnScreen();
    IF blitMouse THEN PrepareMouse END;

    (* Blit buffer on screen *)
    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL.RenderClear(renderer);
    IF screenTexture # NIL THEN
      SDL.DestroyTexture(screenTexture);
      screenTexture := NIL
    END;
    screenTexture := SDL.CreateTextureFromSurface(renderer, screen.surface);
    SDL.SetTextureAlphaMod(screenTexture, screenAlpha);
    SDL.RenderCopyNil(renderer, screenTexture);
    SDL.RenderPresent(renderer);

    IF blitMouse THEN CleanMouse END
  END
END Flip;

(* Net *)

(*
PROCEDURE OpenServer*(port: INTEGER): Socket;
VAR addr: IPAddress; socket: Socket;
BEGIN
  IF Net.ResolveHost(addr, NIL, SHORT(port)) = 0
  THEN socket := Net.TCPOpen(addr)
  ELSE socket := NIL END;
  RETURN socket
END OpenServer;

PROCEDURE CloseConn*(VAR socket: Socket);
BEGIN
  IF Net.TCPClose(socket) = 0 THEN socket := NIL END
END CloseConn;

PROCEDURE Accept*(server: Socket): Socket;
BEGIN
  RETURN Net.TCPAccept(server)
END Accept;

PROCEDURE ConnectTo*(ip: ARRAY OF CHAR; port: INTEGER): Socket;
VAR addr: IPAddress; socket: Socket;
BEGIN
  IF Net.ResolveHost(addr, ip, SHORT(port)) = 0
  THEN socket := Net.TCPOpen(addr)
  ELSE socket := NIL END;
  RETURN socket
END ConnectTo;

PROCEDURE SocketReady*(socket: Socket): BOOLEAN;
BEGIN
  RETURN (socket # NIL) & (socket.ready # 0)
END SocketReady;

PROCEDURE Send*(socket: Socket;
  data: ARRAY OF CHAR; len: INTEGER): INTEGER;
BEGIN
  RETURN Net.TCPSend(socket, data, len)
END Send;

PROCEDURE SendStr*(socket: Socket; s: ARRAY OF CHAR): INTEGER;
VAR len: INTEGER;
BEGIN len := 0;
  WHILE (len < LEN(s)) & (s[len] # 0X) DO INC(len) END;
  RETURN Net.TCPSend(socket, s, len)
END SendStr;

PROCEDURE Recv*(socket: Socket;
  VAR data: ARRAY OF CHAR; len: INTEGER): INTEGER;
BEGIN
  RETURN Net.TCPRecv(socket,
    SYSTEM.VAL(SYSTEM.PTR, SYSTEM.ADR(data[0])), len)
END Recv;

PROCEDURE RecvStr*(socket: Socket; VAR s: ARRAY OF CHAR): INTEGER;
VAR n: INTEGER;
BEGIN
  n := Recv(socket, s, LEN(s) - 1);
  IF n >= 0 THEN
    IF n >= LEN(s) THEN n := LEN(s) END;
    s[n] := 0X
  END;
  RETURN n
END RecvStr;
*)

(* Socket Set *)

(*
PROCEDURE AllocSocketSet*(maxSockets: INTEGER): SocketSet;
BEGIN
  RETURN Net.AllocSocketSet(maxSockets)
END AllocSocketSet;

PROCEDURE FreeSocketSet*(set: SocketSet);
BEGIN
  Net.FreeSocketSet(set)
END FreeSocketSet;

PROCEDURE AddSocket*(set: SocketSet; socket: Socket): INTEGER;
BEGIN
  RETURN Net.AddSocket(set, SYSTEM.VAL(Net.Socket, socket))
END AddSocket;

PROCEDURE DelSocket*(set: SocketSet; socket: Socket): INTEGER;
BEGIN
  RETURN Net.DelSocket(set, SYSTEM.VAL(Net.Socket, socket))
END DelSocket;

PROCEDURE CheckSockets*(set: SocketSet; timeout: INTEGER): BOOLEAN;
BEGIN
  RETURN Net.CheckSockets(set, timeout) # 0
END CheckSockets;
*)

(* NetBuf *)

(*
PROCEDURE InitNetBuf*(buf: NetBuf; size: INTEGER);
BEGIN
  NEW(buf.s, size); buf.len := 0; buf.lastLen := 0
END InitNetBuf;

PROCEDURE NewNetBuf*(size: INTEGER): NetBuf;
VAR buf: NetBuf;
BEGIN
  NEW(buf); InitNetBuf(buf, size); RETURN buf
END NewNetBuf;

PROCEDURE RecvBuf*(socket: Socket; buf: NetBuf);
VAR n, len: INTEGER; s: PStr;
BEGIN
  len := LEN(buf.s^) - buf.len;
  IF len = 0 THEN
    NEW(s, LEN(buf.s^) * 2); COPY(buf.s^, s^);
    buf.s := s
  END;
  n := Net.TCPRecv(socket,
    SYSTEM.VAL(SYSTEM.PTR, SYSTEM.ADR(buf.s[buf.len])),
    len);
  buf.lastLen := buf.len;
  IF n > 0 THEN
    INC(buf.len, n);
    IF buf.len > LEN(buf.s^) THEN buf.len := LEN(buf.s^) END
  END
END RecvBuf;

PROCEDURE BufGet*(buf: NetBuf; sep: CHAR;
  VAR s: ARRAY OF CHAR): BOOLEAN;
VAR i: INTEGER; found: BOOLEAN;
BEGIN i := SHORT(buf.lastLen);
  WHILE (i < buf.len) & (buf.s[i] # sep) DO INC(i) END;
  found := i < buf.len;
  IF found THEN
    S.Extract(buf.s^, 0, i, s);
    S.Delete(buf.s^, 0, i + 1);
    DEC(buf.len, i + 1); buf.lastLen := 0
  END;
  RETURN found
END BufGet;
*)

(* Random *)

PROCEDURE Time*(): INTEGER;
BEGIN
  RETURN SHORT(Platform.Time())
END Time;

(* Set random seed value. Any values are allowed, although
   values not in [1..2^31-2] will be mapped into this range. *)
PROCEDURE PutSeed*(newSeed: INTEGER);
BEGIN
  newSeed := newSeed MOD randomModulo;
  IF newSeed = 0 THEN randomSeed := 1
  ELSE randomSeed := newSeed
  END
END PutSeed;

PROCEDURE NextRND;
CONST
  a = 16807;
  q = 127773; (* m div a *)
  r = 2836;   (* m mod a *)
VAR
  lo, hi, test: INTEGER;
BEGIN
  hi := randomSeed DIV q;
  lo := randomSeed MOD q;
  test := a * lo - r * hi;
  IF test > 0 THEN randomSeed := test
  ELSE randomSeed := test + randomModulo
  END
END NextRND;

(* Calculates a new number. range has to be in the intervall
   [1..2^31-2]. Result is a number from 0, 1, ... , range-1. *)
PROCEDURE Random*(range: INTEGER): INTEGER;
BEGIN
  NextRND;
  RETURN randomSeed MOD range
END Random;

(* Calculates a number x with 0.0 <= x < 1.0. *)
PROCEDURE Uniform*(): SHORTREAL;
BEGIN
  NextRND;
  RETURN (randomSeed - 1) * SHORT(1 / (randomModulo - 1))
END Uniform;

PROCEDURE Randomize*;
BEGIN
  PutSeed(Time())
END Randomize;

(* Init *)

PROCEDURE Init*(): Bitmap;
VAR flags: SET; success: BOOLEAN; w, h, nw, nh: INTEGER;
    s: ARRAY 2000 OF CHAR;
BEGIN screen := NIL;
  IF SDL.Init({SDL.initVideo}) # 0 THEN GetError(s); Out.String(s); Out.Ln
  ELSE
    flags := {};
    IF wantFullscreen THEN
      flags := flags + SDL.windowFullscreenDesktop;
      IF (wantW <= 0) OR (wantH <= 0) THEN
        GetDesktopResolution(wantW, wantH)
      ELSIF wantSpread THEN
        GetDesktopResolution(w, h);
        IF wantSharpPixels THEN
          nw := w DIV wantW; nh := h DIV wantH;
          IF nw < nh THEN
            wantW := w DIV nw; wantH := h DIV nw;
          ELSE
            wantW := w DIV nh; wantH := h DIV nh;
          END
        END;
        IF w / h > wantW / wantH THEN wantW := w * wantH DIV h
        ELSE wantH := h * wantW DIV w
        END
      END
    ELSIF (wantW <= 0) OR (wantH <= 0) THEN wantW := 640; wantH := 400
    END;
    IF sizeStepX # 1 THEN wantW := wantW DIV sizeStepX * sizeStepX END;
    IF sizeStepY # 1 THEN wantH := wantH DIV sizeStepY * sizeStepY END;
    window := SDL.CreateWindow("",
      SDL.windowPosUndefined, SDL.windowPosUndefined,
      wantW, wantH, flags);
    IF window # NIL THEN
      IF wantSoftware THEN flags := {SDL.rendererSoftware}
      ELSE flags := {SDL.rendererAccelerated}
      END;
      INCL(flags, SDL.rendererPresentVsync);
      renderer := SDL.CreateRenderer(window, -1, flags);
      IF wantSharpPixels THEN
        SDL.SetHint(SDL.hintRenderScaleQuality, "0")
      ELSE
        SDL.SetHint(SDL.hintRenderScaleQuality, "1")
      END;
      SDL.RenderSetLogicalSize(renderer, wantW, wantH);
      screen := CreateBitmap(wantW, wantH);
      screenTexture := NIL;
      UnsetRegion;
      SDL.ShowCursor(0);
      IF wantMouse THEN InitMouseData END;
      flags := {SDL.imgInitPng, SDL.imgInitJpg};
      IF flags - SDL.ImgInit(flags) # {} THEN
        Out.String("Could not initialize PNG or JPG: ");
        GetError(s); Out.String(s); Out.Ln
      END;
      Randomize;
      keyPressed := 0;
      lastFlip := -1;
      screenAlpha := 255
    END
  END;
  RETURN screen
END Init;

PROCEDURE Close*;
BEGIN
  IF screenTexture # NIL THEN
    SDL.DestroyTexture(screenTexture);
    screenTexture := NIL
  END;
  (*
  IF wantSound THEN Mix.Quit END;
  IF wantNet THEN Net.Quit END;
  *)
  SDL.Quit
END Close;

BEGIN
  wantW := 640; wantH := 400;
  sizeStepX := 1; sizeStepY := 1;
  wantFullscreen := TRUE; wantSpread := TRUE;
  wantBuffer := FALSE; buffer := NIL; wantFPS := 60;
  wantSharpPixels := TRUE;
  mousePointer := NIL; lastBlitMouseOutside := FALSE;
  mouseFocusX := 0; mouseFocusY := 0;
  events.first := 0; events.last := -1; events.len := 0;
  randomSeed := 1; keyPressed := 0
END Graph.

