/*
 * This file is part of the openKropki project.
 * It provides abstract drawing functions based on SDL.
 *
 * Copyright (C) 2014-2020 Mateusz Viste
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>            /* sprintf() */
#include <stdint.h>
#include <stdlib.h>           /* abs() */
#include <string.h>           /* memset() */
#include "drv_gra.h"
#include "media/data.h"       /* contains the font data, sprites etc */
#include "ui.h"               /* include self for control */
#include "version.h"


static int kratkaSize = 0;
static int kratkaThickness = 0;
static uint32_t kratkaColor = 0;
static uint32_t playercolor[3] = {0, 0, 0};
static uint32_t kartkaBackgroundColor = 0;
static int wallThickness = 0;
static int kropkaRadius = 0;
static int kropkaActiveBorder = 0;
static int kropkaActiveRadius = 0;
static int plansza_width = 0, plansza_height = 0;


int ui_setparams(int w, int h, int tkratkaSize, int tkratkaThickness, uint32_t tkratkaKolor, uint32_t tplayer1color, uint32_t tplayer2color, uint32_t tkartkaBackgroundColor, int twallThickness, int tkropkaRadius, uint32_t tkropkaActiveBorderColor) {
  /* Save all parameters */
  plansza_width = w;
  plansza_height = h;
  kratkaSize = tkratkaSize;
  kratkaThickness = tkratkaThickness;
  kratkaColor = tkratkaKolor;
  playercolor[1] = tplayer1color;
  playercolor[2] = tplayer2color;
  kartkaBackgroundColor = tkartkaBackgroundColor;
  kropkaRadius = tkropkaRadius;
  if (kropkaRadius < 2) kropkaRadius = 2;
  /* thin marker ring just outside the dot */
  kropkaActiveBorder = 2 + (kropkaRadius / 10); /* stroke thickness */
  if (kropkaActiveBorder < 2) kropkaActiveBorder = 2;
  if (kropkaActiveBorder > 4) kropkaActiveBorder = 4;
  kropkaActiveRadius = kropkaRadius + 2 + kropkaActiveBorder;
  /* ring hue follows the player; fixed accent color is unused */
  (void)tkropkaActiveBorderColor;
  wallThickness = twallThickness;
  if (wallThickness < 1) wallThickness = 1;
  /* get out of here */
  return(0);
}


/* redraw one screen pixel with the tiled wood background */
static void wood_pixel(struct gra_sprite *wood, int tilw, int tilh, int dx, int dy) {
  int sx = dx % tilw;
  int sy = dy % tilh;
  if (sx < 0) sx += tilw;
  if (sy < 0) sy += tilh;
  gra_drawpartsprite(wood, sx, sy, 1, 1, dx, dy);
}


/* keep the wood tile loaded -- ui_drawKartka runs every frame during pop-in */
static struct gra_sprite *wood_tile = NULL;
static int wood_tilw = 0, wood_tilh = 0;

static struct gra_sprite *get_wood_tile(void) {
  if (wood_tile == NULL) {
    wood_tile = gra_loadgzbmp(wood5_bmp_gz, wood5_bmp_gz_len);
    if (wood_tile != NULL) {
      wood_tilw = gra_getspritewidth(wood_tile);
      wood_tilh = gra_getspriteheight(wood_tile);
    }
  }
  return(wood_tile);
}


/* Cream notebook page on the wood desk (menus + playfield share this look). */
static void draw_paper_page(int px, int py, int pw, int ph, uint32_t paper_color) {
  const uint32_t border_color = 0x666666u;
  const int border_th = 2;
  int shadow;
  struct gra_sprite *wood;

  if ((pw < 8) || (ph < 8)) return;

  shadow = pw / 80;
  if (shadow < 5) shadow = 5;
  if (shadow > 10) shadow = 10;

  /* soft page shadow on the wood */
  gra_drawrect(px + shadow, py + shadow, pw, ph, 0x1a120cu, 0x55, 1);

  /* cream notebook page */
  gra_drawrect(px, py, pw, ph, paper_color, 0xff, 1);

  /* outer border */
  gra_drawrect(px, py, pw, border_th, border_color, 0xff, 1);
  gra_drawrect(px, py + ph - border_th, pw, border_th, border_color, 0xff, 1);
  gra_drawrect(px, py, border_th, ph, border_color, 0xff, 1);
  gra_drawrect(px + pw - border_th, py, border_th, ph, border_color, 0xff, 1);

  /* Soften three corners (bottom-right stays square where the shadow sits). */
  wood = get_wood_tile();
  if ((wood != NULL) && (wood_tilw > 0) && (wood_tilh > 0)) {
    wood_pixel(wood, wood_tilw, wood_tilh, px, py);
    wood_pixel(wood, wood_tilw, wood_tilh, px + 1, py);
    wood_pixel(wood, wood_tilw, wood_tilh, px, py + 1);
    wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 1, py);
    wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 2, py);
    wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 1, py + 1);
    wood_pixel(wood, wood_tilw, wood_tilh, px, py + ph - 1);
    wood_pixel(wood, wood_tilw, wood_tilh, px + 1, py + ph - 1);
    wood_pixel(wood, wood_tilw, wood_tilh, px, py + ph - 2);
  }
}


void ui_drawKartka(int width, int height, const int *scrollvector) {
  int x, y;
  int px, py, pw, ph;
  int bth;
  int trim;
  const uint32_t border_color = 0x666666u;

  /* clear entire screen */
  gra_clear(0xffffffu);

  /* draw the under-playfield background */
  gra_bgtile(wood5_bmp_gz, wood5_bmp_gz_len);

  /* Outer margin is ~2/3 of a cell (was a full cell) so the sheet looks cut. */
  trim = kratkaSize / 3;
  if (trim < 1) trim = 1;
  px = scrollvector[0] + trim;
  py = scrollvector[1] + trim;
  pw = (width + 1) * kratkaSize - (2 * trim);
  ph = (height + 1) * kratkaSize - (2 * trim);
  bth = kratkaThickness;
  if (bth < 2) bth = 2;
  if ((pw < 8) || (ph < 8)) return;

  draw_paper_page(px, py, pw, ph, kartkaBackgroundColor);

  /* internal grid lines (clipped to the trimmed page) */
  for (y = 0; y < height; y++) {
    gra_drawrect(px, (y + 1) * kratkaSize + scrollvector[1], pw, kratkaThickness, kratkaColor, 0xff, 1);
  }
  for (x = 0; x < width; x++) {
    gra_drawrect((x + 1) * kratkaSize + scrollvector[0], py, kratkaThickness, ph, kratkaColor, 0xff, 1);
  }

  /* border again over grid ends + corner punches */
  gra_drawrect(px, py, pw, bth, border_color, 0xff, 1);
  gra_drawrect(px, py + ph - bth, pw, bth, border_color, 0xff, 1);
  gra_drawrect(px, py, bth, ph, border_color, 0xff, 1);
  gra_drawrect(px + pw - bth, py, bth, ph, border_color, 0xff, 1);
  {
    struct gra_sprite *wood = get_wood_tile();
    if ((wood != NULL) && (wood_tilw > 0) && (wood_tilh > 0) && (pw >= 3) && (ph >= 3)) {
      wood_pixel(wood, wood_tilw, wood_tilh, px, py);
      wood_pixel(wood, wood_tilw, wood_tilh, px + 1, py);
      wood_pixel(wood, wood_tilw, wood_tilh, px, py + 1);
      wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 1, py);
      wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 2, py);
      wood_pixel(wood, wood_tilw, wood_tilh, px + pw - 1, py + 1);
      wood_pixel(wood, wood_tilw, wood_tilh, px, py + ph - 1);
      wood_pixel(wood, wood_tilw, wood_tilh, px + 1, py + ph - 1);
      wood_pixel(wood, wood_tilw, wood_tilh, px, py + ph - 2);
    }
  }
}


void ui_init(void) {
  /* 0x0 = pick ~90% of the desktop, clamped to a sensible range */
  gra_init(0, 0, 0, "OpenKropki", icon_bmp_gz, icon_bmp_gz_len);

  /* Decode all embedded BMP/WAV assets once so menus and gameplay never gunzip again. */
  gra_loadgzbmp(frontpic_bmp_gz, frontpic_bmp_gz_len);
  gra_loadgzbmp(wood5_bmp_gz, wood5_bmp_gz_len);
  gra_loadgzbmp(flag_mouseover_bmp_gz, flag_mouseover_bmp_gz_len);
  gra_loadgzbmp(flag_cz_bmp_gz, flag_cz_bmp_gz_len);
  gra_loadgzbmp(flag_de_bmp_gz, flag_de_bmp_gz_len);
  gra_loadgzbmp(flag_en_bmp_gz, flag_en_bmp_gz_len);
  gra_loadgzbmp(flag_fr_bmp_gz, flag_fr_bmp_gz_len);
  gra_loadgzbmp(flag_pl_bmp_gz, flag_pl_bmp_gz_len);
  gra_loadgzbmp(flag_ru_bmp_gz, flag_ru_bmp_gz_len);
  gra_loadgzbmp(face_human_bmp_gz, face_human_bmp_gz_len);
  gra_loadgzbmp(face_dumbo_bmp_gz, face_dumbo_bmp_gz_len);
  gra_loadgzbmp(face_tigran_bmp_gz, face_tigran_bmp_gz_len);
  gra_loadgzbmp(face_bender_bmp_gz, face_bender_bmp_gz_len);
  gra_loadgzbmp(face_viktor_bmp_gz, face_viktor_bmp_gz_len);

  gra_preload_wavgz(sfx_click_wav_gz, sfx_click_wav_gz_len);
  gra_preload_wavgz(sfx_collect_wav_gz, sfx_collect_wav_gz_len);
  gra_preload_wavgz(sfx_death_wav_gz, sfx_death_wav_gz_len);
  gra_preload_wavgz(sfx_pop1_wav_gz, sfx_pop1_wav_gz_len);
  gra_preload_wavgz(sfx_pop3_wav_gz, sfx_pop3_wav_gz_len);
  gra_preload_wavgz(sfx_winfantasia_wav_gz, sfx_winfantasia_wav_gz_len);
  gra_preload_wavgz(sfx_wrong_wav_gz, sfx_wrong_wav_gz_len);
}


void ui_drawscores(const int *scores, const char *msg, int msgplayercolor, const char **playernames) {
  static int cache_scores[2] = {-1, -1};
  static int cache_msgcolor = -1;
  static char cache_msg[256];
  static char cache_names[2][64];
  static struct gra_sprite *sc[2] = {NULL, NULL};
  static struct gra_sprite *msgspr = NULL;
  static int cache_valid = 0;
  char scoresstr[128];
  int x, w, h;
  int dirty = 0;

  if (!cache_valid) dirty = 1;
  else if ((scores[0] != cache_scores[0]) || (scores[1] != cache_scores[1])) dirty = 1;
  else if (msgplayercolor != cache_msgcolor) dirty = 1;
  else if (((msg == NULL) || (msg[0] == 0)) ? (cache_msg[0] != 0) : (strcmp(msg, cache_msg) != 0)) dirty = 1;
  else if ((playernames[0] == NULL) || (strcmp(playernames[0], cache_names[0]) != 0)) dirty = 1;
  else if ((playernames[1] == NULL) || (strcmp(playernames[1], cache_names[1]) != 0)) dirty = 1;

  if (dirty) {
    gra_freesprite(sc[0]);
    gra_freesprite(sc[1]);
    gra_freesprite(msgspr);
    sc[0] = sc[1] = msgspr = NULL;
    for (x = 0; x < 2; x++) {
      snprintf(scoresstr, sizeof(scoresstr), "%s: %d", playernames[x], scores[x]);
      sc[x] = gra_text2sprite(scoresstr, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolor[x+1], -1, 0xff, -1);
      cache_scores[x] = scores[x];
      snprintf(cache_names[x], sizeof(cache_names[x]), "%s", playernames[x] ? playernames[x] : "");
    }
    if ((msg != NULL) && (msg[0] != 0)) {
      msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolor[msgplayercolor], -1, 0xff, -1);
      snprintf(cache_msg, sizeof(cache_msg), "%s", msg);
    } else {
      cache_msg[0] = 0;
    }
    cache_msgcolor = msgplayercolor;
    cache_valid = 1;
  }

  gra_getwinsize(&w, &h);
  /* scores bar background */
  gra_drawrect(0, 0, w, gra_getspriteheight(sc[0]), 0xffffffu, 0xff, 1);
  /* draw the message, if any */
  if (msgspr != NULL) gra_drawsprite(msgspr, (w - gra_getspritewidth(msgspr)) / 2, 1);
  /* draw the score for Players 1 & 2 */
  for (x = 0; x < 2; x++) {
    if (x == 0) {
        gra_drawsprite(sc[x], 10, 1);
      } else {
        gra_drawsprite(sc[x], w - (gra_getspritewidth(sc[x]) + 10), 1);
    }
  }
}


int ui_getscorebarheight(void) {
  static int cached = -1;
  struct gra_sprite *s;
  if (cached >= 0) return(cached);
  /* same font/size as ui_drawscores() */
  s = gra_text2sprite("Ag", DroidSansBold_ttf, DroidSansBold_ttf_len, 20, 0, -1, 0xff, -1);
  if (s == NULL) {
    cached = 24;
  } else {
    cached = gra_getspriteheight(s);
    gra_freesprite(s);
  }
  return(cached);
}


/* Deepen a player color so the last-move ring frames the disc on cream paper. */
static uint32_t lastmove_ring_color(uint32_t c) {
  unsigned r = (c >> 16) & 0xffu;
  unsigned g = (c >> 8) & 0xffu;
  unsigned b = c & 0xffu;
  r = (r * 7) / 10;
  g = (g * 7) / 10;
  b = (b * 7) / 10;
  return((r << 16) | (g << 8) | b);
}


void graph_drawKropka(int x, int y, int player, int activeflag, int scale_pm, const int *scrollvector) {
  if ((player > 0) && (player < 3)) {
    int cx = (x + 1) * kratkaSize + scrollvector[0];
    int cy = (y + 1) * kratkaSize + scrollvector[1];
    int rad, ring_rad, ring_th;

    if (scale_pm < 1) scale_pm = 1;
    rad = (kropkaRadius * scale_pm + 500) / 1000;
    ring_rad = (kropkaActiveRadius * scale_pm + 500) / 1000;
    ring_th = (kropkaActiveBorder * scale_pm + 500) / 1000;
    if (rad < 1) rad = 1;
    if (ring_th < 1) ring_th = 1;

    if (activeflag != 0) {
      /* player-tinted ring: shows whose move it was at a glance */
      gra_ring(cx, cy, ring_rad, ring_th, lastmove_ring_color(playercolor[player]), 0xff);
    }
    gra_circle_filled(cx, cy, rad, playercolor[player], 0xff);
  }
}


void graph_drawWall(int x1, int y1, int x2, int y2, int player, const int *scrollvector) {
  if ((player > 0) && (player < 3)) {
    gra_drawline((x1+1) * kratkaSize + scrollvector[0], (y1+1) * kratkaSize + scrollvector[1], (x2+1) * kratkaSize + scrollvector[0], (y2+1) * kratkaSize + scrollvector[1], wallThickness, playercolor[player], 0xff);
  }
}


void graph_mousemap(int mx, int my, int *dotx, int *doty, const int *scrollvector) {
  float x, y, x1, y1;
  int intx, inty;
  /* apply the scroll vector to x/y coordinates */
  mx -= scrollvector[0];
  my -= scrollvector[1];
  /* compute the field coordinates */
  x = mx;
  x -= kratkaSize;
  x /= kratkaSize;
  x1 = x + 0.5f;  /* a very primitive way of rounding numbers - but works well, and it's fast */
  intx = (int)x1;
  *dotx = intx;
  y = my;
  y -= kratkaSize;
  y /= kratkaSize;
  y1 = y + 0.5f;  /* a very primitive way of rounding numbers - but works well, and it's fast */
  inty = (int)y1;
  *doty = inty;
  /* perform some boundaries check, to not take into account clicks into empty spaces */
  if ((intx - x < -0.3f) || (intx - x > 0.3f) || (inty - y < -0.3f) || (inty - y > 0.3f)) {
    *dotx = -1;
    *doty = -1;
  }
}


/* wait for event and fill the ev struct accordingly. this function ignores
 * mouse movements, unless nofilter has been set to non-zero in which case it
 * will return an Event_SWEEP or Event_CURSORMOVE event as soon as the mouse
 * moves.
 *
 * Important: filtered-out mouse motion must NOT keep waiting -- otherwise the
 * caller cannot redraw (pop-in animation would stutter while the mouse moves). */
void ui_getevent_to(struct ui_event_t *ev, int nofilter, int timeout_ms) {
  SDL_Event event;
  static int clickx = -1, clicky = -1, motion = 0;
  /* clear the event struct and pre-fill with Event_NONE */
  memset(ev, 0, sizeof(*ev));
  ev->type = Event_NONE;
  if (timeout_ms < 0) timeout_ms = 0;

  if (SDL_WaitEventTimeout(&event, timeout_ms) == 0) return; /* no event (timeout) */

  /* Ignore idle pointer motion, but return immediately so animations keep moving. */
  if ((event.type == SDL_MOUSEMOTION) && (clickx < 0) && (nofilter == 0)) {
    return; /* Event_NONE */
  }

  switch (event.type) {
    case SDL_QUIT:
      ev->type = Event_QUIT;
      return;
    case SDL_MOUSEBUTTONDOWN:
      /* when BUTTON DOWN -> remember pos to differentiate between CLICK and SWEEP later */
      if ((event.button.button == SDL_BUTTON_LEFT) && (event.button.button == 1)) {
        clickx = event.button.x;
        clicky = event.button.y;
      }
      break;
    case SDL_MOUSEMOTION:
      /* check if we are dealing with sweep */
      if ((event.button.button == 1) && ((abs(clickx - event.button.x) > 3) || (abs(clicky - event.button.y) > 3))) {
        motion = 1;
        ev->type = Event_SWEEP;
        ev->x = clickx - event.button.x;
        ev->y = clicky - event.button.y;
        ev->button = 1;
        clickx = event.button.x;
        clicky = event.button.y;
      } else {
        ev->type = Event_CURSORMOVE;
        ev->x = event.button.x;
        ev->y = event.button.y;
      }
      return;
    case SDL_MOUSEWHEEL:
      ev->type = Event_SCROLL;
      ev->x = event.wheel.x;
      ev->y = event.wheel.y;
      return;
    case SDL_MOUSEBUTTONUP:
      clickx = -1;
      /* if it was a motion, forget it */
      if (motion != 0) {
        motion = 0;
        ev->type = Event_NONE;
        return;
      }
      /* otherwise it's a simple click */
      if (event.button.button == SDL_BUTTON_LEFT) {
        ev->type = Event_CLICK;
        ev->x = event.button.x;
        ev->y = event.button.y;
        ev->button = 1;
        return;
      }
      if (event.button.button == SDL_BUTTON_RIGHT) {
        ev->type = Event_CLICK;
        ev->x = event.button.x;
        ev->y = event.button.y;
        ev->button = 2;
        return;
      }
      if (event.button.button == SDL_BUTTON_MIDDLE) {
        ev->type = Event_CLICK;
        ev->x = event.button.x;
        ev->y = event.button.y;
        ev->button = 3;
        return;
      }
      break;
    case SDL_KEYDOWN:
      ev->type = Event_KEYPRESS;
      ev->button = event.key.keysym.sym;
      return;
  }
  /* */
  ev->type = Event_UNKNOWN;
}


void ui_getevent(struct ui_event_t *ev, int nofilter) {
  ui_getevent_to(ev, nofilter, 100);
}


unsigned long ui_getticks(void) {
  return(SDL_GetTicks());
}


void ui_getmousepos(int *x, int *y) {
  SDL_GetMouseState(x, y);
}


int ui_menu(const struct ui_menu_description_t *menu, int menucount, int autofreesprites, int maxw, int maxh) {
  int i, w, h, res = -1;
  int lastmouseposy, lastmouseposx;
  struct ui_event_t ev = {0};

  /* set mouse trackers to current position */
  ui_getmousepos(&lastmouseposx, &lastmouseposy);

  /* (re)draw screen and wait for events */
  for (;;) {
    int x = 0, y = 0; /* preinit in case ui_menu() gets a list where first entry has RELPOS set */
    int hintposx = 0, hintposy = 0;
    int xmargin = 0, ymargin = 0;
    int winw, winh;
    const int desk_pad = 40; /* leave wood around the A4-ish page */
    struct gra_sprite *hint;

    gra_clear(0xffffffu);
    gra_bgtile(wood5_bmp_gz, wood5_bmp_gz_len);
    gra_getwinsize(&winw, &winh);

    /* paper page inset from the desk edges, then capped to maxw/maxh */
    w = winw - (2 * desk_pad);
    h = winh - (2 * desk_pad);
    if (w < 200) w = winw > 200 ? 200 : winw;
    if (h < 160) h = winh > 160 ? 160 : winh;
    if (w > maxw) w = maxw;
    if (h > maxh) h = maxh;

    /* landscape A4 proportions (297:210), same feel as the in-game sheet */
    if (w * 210 > h * 297) {
      w = h * 297 / 210;
    } else {
      h = w * 210 / 297;
    }
    if (w < 200) w = 200;
    if (h < 140) h = 140;
    xmargin = (winw - w) / 2;
    ymargin = (winh - h) / 2;

    /* white page with shadow + border (same frame language as the playfield) */
    draw_paper_page(xmargin, ymargin, w, h, 0xffffffu);

    res = -1;
    hint = NULL;

    /* draw each sprite of the menu */
    for (i = 0; i < menucount; i++) {
      struct gra_sprite *spr = menu[i].sprite[0];
      /* */
      if ((menu[i].flags & UI_MENU_FLAG_XRELPOS) == 0) x = 0;
      if ((menu[i].flags & UI_MENU_FLAG_YRELPOS) == 0) y = 0;
      /* */
      x += (menu[i].propxpos * w) / 100;
      if (menu[i].flags & UI_MENU_FLAG_XCENTER) { /* x-center sprite around its middle */
        x -= gra_getspritewidth(menu[i].sprite[0]) / 2;
      }
      y += (menu[i].propypos * h) / 100;
      if ((menu[i].flags & UI_MENU_FLAG_YRELPOS) == 0) {
        y -= gra_getspriteheight(menu[i].sprite[0]) / 2; /* center y-pos at sprite middle */
      }
      /* keep sprites inside the page and clear of the 2px border (+1px gap);
       * right edge gets one extra pixel so the copyright does not kiss the frame */
      {
        const int inset = 3;
        const int inset_right = 4;
        int maxx = w - inset_right - gra_getspritewidth(spr);
        int maxy = h - inset - gra_getspriteheight(spr);
        if (x < inset) x = inset;
        if (y < inset) y = inset;
        if (x > maxx) x = maxx;
        if (y > maxy) y = maxy;
        if (x < inset) x = inset;
        if (y < inset) y = inset;
      }
      /* apply margins */
      x += xmargin;
      y += ymargin;
      /* if on-mouse sprite available, determine whether or not it should be used (is mouse over it?) */
      if ((menu[i].sprite[1] != NULL) && (lastmouseposx >= x) && (lastmouseposx <= x + gra_getspritewidth(spr)) && (lastmouseposy >= y) && (lastmouseposy <= y + gra_getspriteheight(spr))) {
        const int hover_pad = 5;
        const int hover_rad = 4;
        const uint32_t hover_border = 0x7eb8e8u;
        int sw = gra_getspritewidth(spr);
        int sh = gra_getspriteheight(spr);
        /* light blue, slightly rounded frame around the hovered selectable item */
        gra_roundrect(x - hover_pad, y - hover_pad, sw + (2 * hover_pad), sh + (2 * hover_pad), hover_rad, hover_border, 0xff, 0);
        /* draw the basic sprite only if ORSPR flag is specified */
        if (menu[i].flags & UI_MENU_FLAG_ORSPR) gra_drawsprite(menu[i].sprite[0], x, y);
        /* draw the on-mouse sprite */
        gra_drawsprite(menu[i].sprite[1], x, y);
        res = menu[i].clickid; /* remember result value in case of a click */
        /* compute the position of the mouseover hint, if defined, and remember it for later */
        if (menu[i].hint != NULL) {
          hint = menu[i].hint;
          hintposx = lastmouseposx + 16;
          hintposy = lastmouseposy + 16;
          if (hintposx + gra_getspritewidth(menu[i].hint) > w) hintposx = (w - gra_getspritewidth(menu[i].hint));
          if (hintposy + gra_getspriteheight(menu[i].hint) > h) hintposy = (h - gra_getspriteheight(menu[i].hint));
        }
      } else {
        gra_drawsprite(spr, x, y);
      }
      /* adjust x and y so they point to the bottom right edge of the sprite, in case next sprite wants to be drawn there */
      y += gra_getspriteheight(menu[i].sprite[0]) - ymargin;
      x += gra_getspritewidth(menu[i].sprite[0]) - xmargin;
    }

    /* display the hint now (if present), so it covers all other elements */
    if (hint != NULL) gra_drawsprite(hint, hintposx, hintposy);

    /* refresh screen and look for events */
    gra_refresh();
    ui_getevent(&ev, 1);
    switch (ev.type) {
      case Event_CLICK:
        if (res >= 0) goto FREE_AND_RETURN;
        break;
      case Event_CURSORMOVE: /* update mouse position */
        lastmouseposx = ev.x;
        lastmouseposy = ev.y;
        break;
      case Event_QUIT:
        res = UI_MENU_EXIT;
        goto FREE_AND_RETURN;
      case Event_KEYPRESS:
        if (ev.button == 0x1B) { /* ESC key == "back" */
          res = UI_MENU_BACK;
          goto FREE_AND_RETURN;
        }
        break;
      default:
        break;
    }
  }

  FREE_AND_RETURN:
  if (autofreesprites) { /* free sprite resources if autofreesprites enabled */
    while (--menucount >= 0) {
      gra_freesprite(menu[menucount].sprite[0]);
      gra_freesprite(menu[menucount].sprite[1]);
      gra_freesprite(menu[menucount].hint);
    }
  }
  return(res);
}


void ui_throwmsg(const char *msg) {
  int w, h, x, y, sprw, sprh, margin;
  struct gra_sprite *msgspr;
  gra_getwinsize(&w, &h);
  msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, -1, 0xff, -1);
  sprw = gra_getspritewidth(msgspr);
  sprh = gra_getspriteheight(msgspr);
  x = (w - sprw) / 2;
  y = (h - sprh) / 2;
  margin = sprw * 5 / 100;
  gra_drawrect(x - margin, y - margin, sprw + (2 * margin), sprh + (2 * margin), 0xe98c36u, 200, 1);
  gra_drawrect(x - margin, y - margin, sprw + (2 * margin), sprh + (2 * margin), 0x323232u, 0xff, 0);
  gra_drawsprite(msgspr, x, y);
  gra_refresh();
  gra_freesprite(msgspr);
}


void ui_delay(unsigned int ms) {
  SDL_Delay((Uint32)ms);
}


void ui_changecursor(enum ui_mousecursor_t curid) {
  static SDL_Cursor *oldcurs, *curs;
  switch (curid) {
    case UI_CURSOR_WAIT:
      curs = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
      break;
    case UI_CURSOR_NORMAL:
      curs = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
      break;
  }
  SDL_SetCursor(curs);
  if (oldcurs != NULL) SDL_FreeCursor(oldcurs);
  oldcurs = curs;
}


/* shade a diamond-shaped area around a dot */
void ui_shadediamond(int x, int y, int p, const int *scrollvector) {
  int x1, x2, yy, i;
  x1 = (x+1) * kratkaSize + scrollvector[0];
  x2 = x1;
  yy = y * kratkaSize + scrollvector[1];
  for (i = kratkaSize * 2 + 2; i > 0; i--) {
    if ((yy + i) & 1) continue; /* pattern 1-0-1-0-1-0... */
    gra_drawline(x1, yy + i, x2, yy + i, 1, playercolor[p], 0xff);
    if (i > kratkaSize) {
      x1 -= 2;
      x2 += 2;
    } else {
      x1 += 2;
      x2 -= 2;
    }
  }
}


/* shade a block of kratka items */
void ui_shadeblock(int x, int y, int w, int h, int p, const int *scrollvector) {
  int x1, x2, yy, i;
  x1 = (x + 1) * kratkaSize + scrollvector[0];
  x2 = x1 + (w * kratkaSize);
  yy = y * kratkaSize + scrollvector[1];
  for (i = 0; i < (h * kratkaSize); i++) {
    if ((yy + i) & 1) continue; /* pattern 1-0-1-0-1-0... */
    gra_drawline(x1, yy + i, x2, yy + i, 1, playercolor[p], 0xff);
  }
}
