/*
 * 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 uint32_t kropkaActiveBorderColor = 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;
  kropkaActiveBorder = kropkaRadius / 3;
  if (kropkaActiveBorder < 2) kropkaActiveBorder = 2;
  kropkaActiveRadius = kropkaRadius + (2 * kropkaActiveBorder);
  kropkaActiveBorderColor = tkropkaActiveBorderColor;
  wallThickness = twallThickness;
  if (wallThickness < 1) wallThickness = 1;
  /* get out of here */
  return(0);
}


void ui_drawKartka(int width, int height, const int *scrollvector) {
  int x, y;
  /* clear entire screen */
  gra_clear(0xffffffu);

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

  /* draw an empty page */
  gra_drawrect(scrollvector[0], scrollvector[1], (width + 1) * kratkaSize, (height + 1) * kratkaSize, kartkaBackgroundColor, 0xff, 1);

  /* draw the horizontal lines */
  for (y = -1; y <= height; y++) {
    if ((y == -1) || (y == height)) { /* borders are always black */
        gra_drawrect(scrollvector[0], (y + 1) * kratkaSize + scrollvector[1], kratkaThickness + (plansza_width + 1) * kratkaSize, kratkaThickness, 0, 0xff, 1);
      } else {
        gra_drawrect(scrollvector[0], (y + 1) * kratkaSize + scrollvector[1], kratkaThickness + (plansza_width + 1) * kratkaSize, kratkaThickness, kratkaColor, 0xff, 1);
    }
  }
  /* draw the vertical lines */
  for (x = -1; x <= width; x++) {
    if ((x == -1) || (x == width)) { /* borders are always black */
        gra_drawrect((x + 1) * kratkaSize + scrollvector[0], scrollvector[1] + kratkaThickness, kratkaThickness, (plansza_height + 1) * kratkaSize - kratkaThickness, 0, 0xff, 1);
      } else {
        gra_drawrect((x + 1) * kratkaSize + scrollvector[0], scrollvector[1] + kratkaThickness, kratkaThickness, (plansza_height + 1) * kratkaSize - kratkaThickness, kratkaColor, 0xff, 1);
    }
  }
}


void ui_init(int width, int height) {
  gra_init(width, height, 0, "OpenKropki", icon_bmp_gz, icon_bmp_gz_len);
}


void ui_drawscores(const int *scores, const char *msg, int msgplayercolor, const char **playernames) {
  char scoresstr[128];
  int x, w, h;
  struct gra_sprite *sc[2], *msgspr = NULL;
  gra_getwinsize(&w, &h);
  /* load textures */
  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);
  }
  if ((msg != NULL) && (msg[0] != 0)) msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolor[msgplayercolor], -1, 0xff, -1);
  /* scores bar background */
  gra_drawrect(0, 0, w, gra_getspriteheight(sc[0]), 0xffffffu, 0xff, 1);
  /* draw the message, if any */
  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);
    }
  }
  /* free textures */
  gra_freesprite(sc[0]);
  gra_freesprite(sc[1]);
  gra_freesprite(msgspr);
}


void graph_drawKropka(int x, int y, int player, int activeflag, const int *scrollvector) {
  if ((player > 0) && (player < 3)) {
    gra_circle_filled((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaRadius, playercolor[player], 0xff);
    if (activeflag != 0) { /* display a circle around last dot (active dot) */
      int i;
      for (i = 0; i < kropkaActiveBorder; i++) {
        gra_circle((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaActiveRadius - i, kropkaActiveBorderColor, 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 */
void ui_getevent(struct ui_event_t *ev, int nofilter) {
  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;
  /* */
  for (;;) {
    if (SDL_WaitEventTimeout(&event, 100) == 0) return; /* no event (timeout) */
    if (event.type != SDL_MOUSEMOTION) break;
    if ((event.type == SDL_MOUSEMOTION) && (clickx >= 0)) break;
    if (nofilter != 0) break;
  }
  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;
}


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;
    struct gra_sprite *hint;
    gra_clear(0x606070u); /* clear screen (and fill with solid color) */
    gra_getwinsize(&w, &h);

    /* compute virtual width and height, as well as margins (if applicable) */
    if (w > maxw) {
      xmargin = (w - maxw) / 2;
      w = maxw;
    }
    if (h > maxh) {
      ymargin = (h - maxh) / 2;
      h = maxh;
    }

    /* draw menu background */
    gra_drawrect(xmargin, ymargin, w, h, 0xffffffu, 0xff, 1);

    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 */
      }
      /* make sure not to draw sprites out of screen */
      if (x < 0) x = 0;
      if (y < 0) y = 0;
      if (x + gra_getspritewidth(spr) > w) x = w - gra_getspritewidth(spr);
      if (y + gra_getspriteheight(spr) > h) y = h - gra_getspriteheight(spr);
      /* 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))) {
        /* 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);
  }
}