/*
 * Graphic driver for openKropki, using SDL as backend
 *
 * 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 <stdio.h>    /* puts(), printf() */
#include <stdlib.h>   /* malloc(), free() */
#include <string.h>   /* memset */
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

#include "gz.h"
#include "drv_gra.h" /* include self for control */

#define SCALE 1

struct gra_sprite {
  SDL_Texture *ptr;
  int w;
  int h;
};

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;


/* loads a gziped bmp image from memory into a SDL surface */
static SDL_Surface *loadgzbmp_surface(const unsigned char *memgz, long memgzlen) {
  unsigned char *rawimage;
  long rawimagelen;
  SDL_Surface *res;
  SDL_RWops *rwop;
  if (isGz(memgz, memgzlen) == 0) return(NULL);
  rawimage = ungz(memgz, memgzlen, &rawimagelen);
  rwop = SDL_RWFromMem(rawimage, (int)rawimagelen);
  res = SDL_LoadBMP_RW(rwop, 1);
  /* SDL_FreeRW(rwop); already freed by SDL_LoadBMP_RW() */
  free(rawimage);
  return(res);
}


/* init the video subsystem */
int gra_init(int width, int height, int flags, const char *windowtitle, const void *titleicon, long titleicon_len) {
  Uint32 sdl_video_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
  SDL_Surface *titleiconsurface;

  if (flags & GRA_FULLSCREEN) sdl_video_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  window = SDL_CreateWindow(windowtitle, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, sdl_video_flags);

  renderer = SDL_CreateRenderer(window, -1, 0);
  if (renderer == NULL) {
    SDL_DestroyWindow(window);
    printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
    return(1);
  }

  /* set the window's titlebar icon */
  if ((titleicon != NULL) && (titleicon_len > 0)) {
    titleiconsurface = loadgzbmp_surface(titleicon, titleicon_len);
    if (titleicon != NULL) {
      SDL_SetWindowIcon(window, titleiconsurface);
      SDL_FreeSurface(titleiconsurface);
    }
  }

  /* set the window's minimum size */
  SDL_SetWindowMinimumSize(window, 400, 300);

  /* init the ttf subsystem */
  if (TTF_Init() != 0) {
    printf("ERROR: TTF_Init() failed (%s)\n", TTF_GetError());
  }

  /* set up audio */
  {
    SDL_AudioSpec want;
    memset(&want, 0, sizeof(want));
    want.freq = 22050;
    want.format = AUDIO_S16;
    want.channels = 1;
    want.samples = 1024; /* more than this leads to a noticeable delay when playing sound effects */
    if (SDL_OpenAudio(&want, NULL) == 0) {
      SDL_PauseAudio(0); /* start audio */
    }
  }

  /* set up alpha blending */
  /* SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); */

  return(0);
}


/* plays wav.gz data */
void gra_playwavgz(const void *memgz, long memgzlen) {
  unsigned char *rawwave, *rawaudio;
  Uint32 rawaudiolen;
  long rawwavelen;
  SDL_AudioSpec *res, audiospec;
  SDL_RWops *rwop;
  if (isGz(memgz, memgzlen) == 0) return;
  rawwave = ungz(memgz, memgzlen, &rawwavelen);
  rwop = SDL_RWFromMem(rawwave, (int)rawwavelen);
  res = SDL_LoadWAV_RW(rwop, 1, &audiospec, &rawaudio, &rawaudiolen);
  /* SDL_FreeRW(rwop); do NOT free this! already freed by SDL_LoadWAV_RW() */
  free(rawwave);
  /* enqueue data (unless SDL_LoadWAV_RW() failed) */
  if (res != NULL) {
    SDL_QueueAudio(1, rawaudio, rawaudiolen);
    SDL_FreeWAV(rawaudio); /* free original buffer (SDL_QueueAudio() copied it) */
  }
}


/* close and clean up the graphic subsystem */
void gra_close(void) {
  SDL_DestroyWindow(window);
  if (TTF_WasInit()) TTF_Quit();
  SDL_Quit();
}


void gra_clear(uint8_t r, uint8_t g, uint8_t b) {
  SDL_SetRenderDrawColor(renderer, r, g, b, 0xff);
  SDL_RenderClear(renderer);
}


void gra_bgtile(const void *memgz, long memgzlen) {
  struct gra_sprite *tile;
  int x, y;
  int winw, winh, tilw, tilh;
  tile = gra_loadgzbmp(memgz, memgzlen);
  tilw = gra_getspritewidth(tile);
  tilh = gra_getspriteheight(tile);
  gra_getwinsize(&winw, &winh);

  for (y = 0; y < winh; y += tilh) {
    for (x = 0; x < winw; x += tilw) {
      gra_drawsprite(tile, x, y);
    }
  }

  gra_freesprite(tile);
}


void gra_switchfullscreen(void) {
  static int fullscreenflag = 0;
  fullscreenflag ^= 1;
  if (fullscreenflag != 0) {
      SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
    } else {
      SDL_SetWindowFullscreen(window, 0);
  }
  SDL_Delay(50); /* wait for 50ms - the video thread needs some time to set things up */
}


void gra_drawpartsprite(const struct gra_sprite *sprite, int srcx, int srcy, int srcwidth, int srcheight, int dstx, int dsty) {
  static SDL_Rect srcrect;
  static SDL_Rect dstrect;
  if (sprite == NULL) return;
  dstrect.x = dstx * SCALE;
  dstrect.y = dsty * SCALE;
  dstrect.w = srcwidth * SCALE;
  dstrect.h = srcheight * SCALE;
  srcrect.x = srcx;
  srcrect.y = srcy;
  srcrect.w = srcwidth;
  srcrect.h = srcheight;
  SDL_RenderCopy(renderer, sprite->ptr, &srcrect, &dstrect);
}


void gra_drawsprite(const struct gra_sprite *sprite, int x, int y) {
  static SDL_Rect rect;
  if (sprite == NULL) return;
  rect.x = x * SCALE;
  rect.y = y * SCALE;
  rect.w = sprite->w * SCALE;
  rect.h = sprite->h * SCALE;
  SDL_RenderCopy(renderer, sprite->ptr, NULL, &rect);
}


void gra_refresh(void) {
  SDL_RenderPresent(renderer);
}


/* loads a gziped bmp image from memory and returns a gra_sprite */
struct gra_sprite *gra_loadgzbmp(const void *memgz, long memgzlen) {
  SDL_Surface *surface;
  struct gra_sprite *res;
  surface = loadgzbmp_surface(memgz, memgzlen);
  if (surface == NULL) return(NULL);
  res = malloc(sizeof(struct gra_sprite));
  res->ptr = SDL_CreateTextureFromSurface(renderer, surface);
  SDL_FreeSurface(surface);
  SDL_QueryTexture(res->ptr, NULL, NULL, &(res->w), &(res->h));
  return(res);
}


void loadSpriteSheet(struct gra_sprite **sprites, int width, int height, int itemcount, const void *memptr, int memlen) {
  SDL_Surface *spritesheet, *item;
  SDL_Rect rect;
  int i;
  spritesheet = loadgzbmp_surface(memptr, memlen);
  if (spritesheet == NULL) puts("bmp is NULL!!!");
  item = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000L, 0x00FF0000L, 0x0000FF00L, 0x000000FFL);
  if (item == NULL) puts("item is NULL!!!");
  for (i = 0; i < itemcount; i++) {
    rect.x = i * width;
    rect.y = 0;
    rect.w = width;
    rect.h = height;
    SDL_FillRect(item, NULL, 0);
    SDL_BlitSurface(spritesheet, &rect, item, NULL);
    sprites[i] = malloc(sizeof(struct gra_sprite));
    sprites[i]->ptr = SDL_CreateTextureFromSurface(renderer, item);
    if (sprites[i]->ptr == NULL) puts("sprites[i]->ptr is NULL!!!");
    SDL_QueryTexture(sprites[i]->ptr, NULL, NULL, &(sprites[i]->w), &(sprites[i]->h));
  }
  SDL_FreeSurface(item);
  SDL_FreeSurface(spritesheet);
}


int gra_getspritewidth(const struct gra_sprite *sprite) {
  if (sprite == NULL) return(0);
  return(sprite->w);
}


int gra_getspriteheight(const struct gra_sprite *sprite) {
  if (sprite == NULL) return(0);
  return(sprite->h);
}


int gra_drawrect(int x, int y, int width, int height, uint8_t r, uint8_t g, uint8_t b, uint8_t a, int fillflag) {
  SDL_Rect rect;
  int res;
  rect.x = x;
  rect.y = y;
  rect.w = width;
  rect.h = height;
  res = SDL_SetRenderDrawColor(renderer, r, g, b, a);
  if (res != 0) return(-1);
  if (fillflag == 0) {
      res = SDL_RenderDrawRect(renderer, &rect);
    } else {
      res = SDL_RenderFillRect(renderer, &rect);
  }
  if (res != 0) return(-2);
  return(0);
}


void gra_drawline(int x1, int y1, int x2, int y2, int thickness, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
  int t;
  SDL_Rect rect;
  SDL_SetRenderDrawColor(renderer, r, g, b, a);
  /* make sure that the line goes from left to right (later code relies on that assumption, also SDL on some platforms is troubled by rects with a negative width */
  if (x1 > x2) {
    t = x1;
    x1 = x2;
    x2 = t;
    t = y1;
    y1 = y2;
    y2 = t;
  }
  /* optimize horizontal and vertical lines by drawing rectangles (much faster) */
  if (x1 == x2) {
    rect.x = x1 - (thickness >> 1);
    rect.y = y1;
    rect.w = thickness;
    rect.h = y2 - y1;
    SDL_RenderFillRect(renderer, &rect);
  } else if (y1 == y2) {
    rect.x = x1;
    rect.y = y1 - (thickness >> 1);
    rect.w = x2 - x1;
    rect.h = thickness;
    SDL_RenderFillRect(renderer, &rect);
  } else { /* diagonal line (only 45 degrees) */
    /* quick and dirty, only 45 degrees are possible - prints out many rectangles */
    int yy = y1;
    for (; x1 < x2; x1++) {
      gra_drawrect(x1, yy, thickness, thickness, r, g, b, a, 1);
      if (y1 < y2) {
        yy++;
      } else {
        yy--;
      }
    }
  }
}


void gra_circle_filled(int x, int y, int rad, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
  int xx, yy;

  rad += 1; /* slight adjustment */

  /* set the 'drawing color' first */
  SDL_SetRenderDrawColor(renderer, r, g, b, a);

  /* brute force over every pixel */
  for (yy = 1; yy < rad; yy++) {
    /* find the lenght of this pixel line */
    for (xx = 1; xx * xx + yy * yy < rad * rad; xx++);
    /* draw the line of pixels */
    gra_drawrect(1 + x - xx, 1 + y - yy, xx * 2, yy * 2, r, g, b, a, 0);
  }
}

void gra_circle(int x0, int y0, int rad, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
  /* SDL_RenderDrawPoint(renderer, x, y); */
  int x = rad;
  int y = 0;
  int radiusError = 1 - x;

  /* set the 'drawing color' first */
  SDL_SetRenderDrawColor(renderer, r, g, b, a);

  /* draw the outer circle */
  while(x >= y) {
    SDL_RenderDrawPoint(renderer, x + x0, y + y0);
    SDL_RenderDrawPoint(renderer, y + x0,  x + y0);
    SDL_RenderDrawPoint(renderer, -x + x0,  y + y0);
    SDL_RenderDrawPoint(renderer, -y + x0,  x + y0);
    SDL_RenderDrawPoint(renderer, -x + x0, -y + y0);
    SDL_RenderDrawPoint(renderer, -y + x0, -x + y0);
    SDL_RenderDrawPoint(renderer, x + x0, -y + y0);
    SDL_RenderDrawPoint(renderer, y + x0, -x + y0);
    y++;
    if (radiusError < 0) {
      radiusError += 2 * y + 1;
    } else {
      x--;
      radiusError += 2 * (y - x) + 1;
    }
  }

}


struct gra_sprite *gra_text2sprite(const char *message, const void *font_ttf, long font_ttf_len, int fontSize, uint8_t r, uint8_t g, uint8_t b, uint8_t a, int maxwidth) {
  SDL_Surface *surface;
  SDL_Color txtcolor;
  SDL_RWops *rwop;
  TTF_Font *font = NULL;
  struct gra_sprite *res;

  /* if no max width, then set maxwidth to width of the window */
  if (maxwidth < 1) gra_getwinsize(&maxwidth, NULL);

  /* Load the font */
  rwop = SDL_RWFromConstMem(font_ttf, (int)font_ttf_len);
  if (rwop == NULL) {
    puts("OOOPS rwop is NULL!!!!");
    return(NULL);
  }
  font = TTF_OpenFontRW(rwop, 1, fontSize);
  txtcolor.r = r;
  txtcolor.g = g;
  txtcolor.b = b;
  txtcolor.a = 0xff; /* alpha here doesn't work anyway, I set it later on the texture */
  surface = TTF_RenderUTF8_Blended_Wrapped(font, message, txtcolor, (Uint32)maxwidth);
  res = malloc(sizeof(struct gra_sprite));
  res->ptr = SDL_CreateTextureFromSurface(renderer, surface);
  SDL_FreeSurface(surface);
  SDL_QueryTexture(res->ptr, NULL, NULL, &(res->w), &(res->h));
  SDL_SetTextureAlphaMod(res->ptr, a);
  TTF_CloseFont(font);
  /* SDL_FreeRW(rwop); Don't try to free the rwop, it's already free! */
  return(res);
}


void gra_freesprite(struct gra_sprite *spr) {
  if (spr == NULL) return;
  SDL_DestroyTexture(spr->ptr);
  free(spr);
}


void gra_getwinsize(int *w, int *h) {
  SDL_GetWindowSize(window, w, h);
}
