/*
 * 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 uint8_t kratkaColorR = 0, kratkaColorG = 0, kratkaColorB = 0, kratkaColorA = 0xff;
static uint8_t playercolorR[3] = {0, 0, 0};
static uint8_t playercolorG[3] = {0, 0, 0};
static uint8_t playercolorB[3] = {0, 0, 0};
static uint8_t playercolorA[3] = {0, 0, 0};
static uint8_t kartkaBackgroundColorR = 0,  kartkaBackgroundColorG = 0, kartkaBackgroundColorB = 0;
static int wallThickness = 0;
static int kropkaRadius = 0;
static int kropkaActiveBorder = 0;
static int plansza_width = 0, plansza_height = 0;


int ui_setparams(int w, int h, int tkratkaSize, int tkratkaThickness, int tkratkaKolor, int tplayer1color, int tplayer2color, int tkartkaBackgroundColor, int twallThickness, int tkropkaRadius) {
  /* Save all parameters */
  plansza_width = w;
  plansza_height = h;
  kratkaSize = tkratkaSize;
  kratkaThickness = tkratkaThickness;
  kratkaColorR = (tkratkaKolor >> 16) & 0xff;
  kratkaColorG = (tkratkaKolor >> 8) & 0xff;
  kratkaColorB = tkratkaKolor & 0xff;
  kratkaColorA = 0xff;
  playercolorR[0] = 0;
  playercolorG[0] = 0;
  playercolorB[0] = 0;
  playercolorA[0] = 0xff;
  playercolorR[1] = (tplayer1color >> 16) & 0xff;
  playercolorG[1] = (tplayer1color >> 8) & 0xff;
  playercolorB[1] = tplayer1color & 0xff;
  playercolorA[1] = 0xff;
  playercolorR[2] = (tplayer2color >> 16) & 0xff;
  playercolorG[2] = (tplayer2color >> 8) & 0xff;
  playercolorB[2] = tplayer2color & 0xff;
  playercolorA[2] = 0xff;
  kartkaBackgroundColorR = (tkartkaBackgroundColor >> 16) & 0xff;
  kartkaBackgroundColorG = (tkartkaBackgroundColor >> 8) & 0xff;
  kartkaBackgroundColorB = tkartkaBackgroundColor & 0xff;
  kropkaRadius = tkropkaRadius;
  if (kropkaRadius < 2) kropkaRadius = 2;
  kropkaActiveBorder = kropkaRadius / 4;
  if (kropkaActiveBorder < 2) kropkaActiveBorder = 2;
  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(0xff, 0xff, 0xff);

  /* 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, kartkaBackgroundColorR, kartkaBackgroundColorG, kartkaBackgroundColorB, 0xff, 1);

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


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


void ui_applyfaderect(int size) {
  int w, h, step;
  gra_getwinsize(&w, &h);
  for (step = 0; step < size; step += 1) {
    int a = 0xff - ((0xff * step) / size);
    gra_drawrect(step, step, w - (step * 2), h - (step * 2), 0xff, 0xff, 0xff, (uint8_t)a, 0);
  }
}


void ui_drawscores(const int *scores, const char *msg, int msgplayercolor) {
  char scoresstr[128];
  static char *playernames[] = {"YOU", "OPPONENT"};
  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, playercolorR[x+1], playercolorG[x+1], playercolorB[x+1], playercolorA[x+1], -1);
  }
  if ((msg != NULL) && (msg[0] != 0)) msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolorR[msgplayercolor], playercolorG[msgplayercolor], playercolorB[msgplayercolor], playercolorA[msgplayercolor], -1);
  /* background */
  gra_drawrect(0, 0, w, gra_getspriteheight(sc[0]), 0xff, 0xff, 0xff, 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)) {
    if (activeflag != 0) {
      gra_circle_filled((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaRadius, 10, 10, 10, 0xff);
      gra_circle_filled((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaRadius - kropkaActiveBorder, playercolorR[player], playercolorG[player], playercolorB[player], playercolorA[player]);
    } else {
      gra_circle_filled((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaRadius, playercolorR[player], playercolorG[player], playercolorB[player], playercolorA[player]);
    }
  }
}


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, playercolorR[player], playercolorG[player], playercolorB[player], playercolorA[player]);
  }
}


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;
      }
      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());
}


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

  /* (re)draw screen and wait for events */
  for (;;) {
    gra_clear(0xff, 0xff, 0xff); /* all-white screen */
    gra_getwinsize(&w, &h);

    res = -1;

    /* draw each sprite of the menu */
    for (i = 0; i < menucount; i++) {
      int x;
      static int y;
      struct gra_sprite *spr = menu[i].sprite[0];
      /* */
      if (menu[i].propxpos == 0xff) { /* x-center */
        x = (w - gra_getspritewidth(menu[i].sprite[0])) / 2;
      } else {
        x = (menu[i].propxpos * w) / 100;
      }
      if (menu[i].propypos != 0xff) {
        y = (menu[i].propypos * h) / 100;
        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);
      /* 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))) {
        spr = menu[i].sprite[1];
        res = menu[i].clickid;
      }
      /* finally put the sprite on screen */
      gra_drawsprite(spr, x, y);
      /* adjust y so it points just below the sprite, in case next sprite wants to be drawn there */
      y += gra_getspriteheight(menu[i].sprite[0]);
    }

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


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, 0, 0, 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), 0xe9, 0x8c, 0x36, 200, 1);
  gra_drawrect(x - margin, y - margin, sprw + (2 * margin), sprh + (2 * margin), 50, 50, 50, 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;
}
