/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou 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 lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include <SDL2/SDL.h>
#include "biniou.h"
#ifdef WITH_SDL2UI
#include "osd.h"
#endif
#include "src/defaults.h"
#include "commands.h"
#include "commands_key.h"
#include "pthread_utils.h"


uint32_t options = BO_NONE;
uint32_t version = 0;

#define NO_MOUSE_CURSOR

static SDL_Window *window = NULL;
static SDL_DisplayMode current; // current screen resolution
#ifdef WITH_SDL2UI
static pthread_t thread;
#endif

static void
create_window(const Uint32 flags)
{
  gchar *icon_file;
  SDL_Surface *icon = NULL;
  gchar *window_title;
  int x0 = WIDTH_ORIGIN == INT32_MIN ? current.w - WIDTH : WIDTH_ORIGIN;
  int y0 = HEIGHT_ORIGIN == INT32_MIN ? 0 : HEIGHT_ORIGIN;

  window_title = g_strdup_printf("Le Biniou (%dx%d)", WIDTH, HEIGHT);

  SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
  window = SDL_CreateWindow(window_title, x0, y0, WIDTH, HEIGHT, flags);
  g_free(window_title);
  if (NULL == window) {
    xerror("Couldn't set %dx%d video mode: %s\n", WIDTH, HEIGHT, SDL_GetError());
  }

  icon_file = g_strdup_printf("%s/lebiniou.bmp", DEFAULT_DATADIR);
  icon = SDL_LoadBMP(icon_file);
  g_free(icon_file);
  if (NULL != icon) {
    Uint32 colorkey = SDL_MapRGB(icon->format, 0, 0, 0);
    SDL_SetColorKey(icon, SDL_TRUE, colorkey);
    SDL_SetWindowIcon(window, icon);
    SDL_FreeSurface(icon);
  }
}


static inline void
SDL_refresh_32bpp(Context_t *ctx)
{
  const int depth = 24, pitch = 3 * WIDTH;
  const Uint32 pixel_format = SDL_PIXELFORMAT_RGB24;
  SDL_Surface *surf = NULL;

  if (!xpthread_mutex_lock(&ctx->frame_mutex)) {
    surf = SDL_CreateRGBSurfaceWithFormatFrom((void *)ctx->frame, WIDTH, HEIGHT,
                                              depth, pitch, pixel_format);
    xpthread_mutex_unlock(&ctx->frame_mutex);
  }
  assert(NULL != surf);
  if (SDL_BlitScaled(surf, NULL, SDL_GetWindowSurface(window), NULL) < 0) {
    xerror("SDL_BlitScaled failed\n");
  }
  SDL_FreeSurface(surf);
}


static void
SDL_get_event(Context_t *ctx)
{
  // TODO middle = change color, right = erase (3x3)
  SDL_Event evt;
  memset(&evt, 0, sizeof(SDL_Event));

  while (SDL_PollEvent(&evt) != 0) {
    BKey_t key;

    switch (evt.type) {
      case SDL_KEYDOWN:
        key.val = evt.key.keysym.sym;
        key.mod = evt.key.keysym.mod;

        on_key(ctx, &key);
        break;

      case SDL_QUIT: {
        Command_t *cmd = Command_new(CT_SDL2, CMD_APP_QUIT, NULL, NULL);
        Context_push_command(ctx, cmd);
      }
        break;

      case SDL_MOUSEMOTION:
        switch (evt.motion.state) {
          case SDL_BUTTON_LEFT:
            ctx->params3d.xe = evt.motion.x;
            ctx->params3d.ye = evt.motion.y;
            Params3d_rotate(&ctx->params3d);
            break;

          case SDL_BUTTON_RIGHT + SDL_BUTTON_LEFT: /* <- WTF ? */
            // printf("right button motion @ %d %d\n",  evt.motion.x, evt.motion.y);
            set_pixel(active_buffer(ctx), evt.motion.x, MAXY-evt.motion.y, 255);
            break;

          default:
            break;
        }
        break;

      case SDL_MOUSEWHEEL:
        if (evt.wheel.y > 0) { // scroll up
          Params3d_zoom_in(&ctx->params3d);
        } else if (evt.wheel.y < 0) { // scroll down
          Params3d_zoom_out(&ctx->params3d);
        }
        break;

      case SDL_MOUSEBUTTONDOWN:
        /* printf("type= %d, button= %d\n", evt.button.type, evt.button.button); */
        switch (evt.button.button) {
          case SDL_BUTTON_LEFT:
            ctx->params3d.xs = evt.motion.x;
            ctx->params3d.ys = evt.motion.y;
            break;

          case SDL_BUTTON_RIGHT:
            // printf("button down @ %d %d\n", evt.motion.x, evt.motion.y);
            set_pixel(active_buffer(ctx), evt.motion.x, MAXY-evt.motion.y, 255);
            break;

          default:
            break;
        }
        break;

      case SDL_WINDOWEVENT:
        switch (evt.window.event) {
          case SDL_WINDOWEVENT_RESIZED:
#ifdef DEBUG_SDL2
            SDL_Log("Window %d resized to %dx%d",
                    evt.window.windowID, evt.window.data1,
                    evt.window.data2);
#endif
            break;

          default:
            break;
        }
        break;

      default:
        break;
    }
  }
}


void
run(Context_t *ctx)
{
  SDL_get_event(ctx);
  SDL_refresh_32bpp(ctx);

  if (SDL_UpdateWindowSurface(window) < 0) {
    SDL_Log("[1] SDL_UpdateWindowSurface failed: %s", SDL_GetError());
    exit(1);
  }
}


int8_t
create(Context_t *ctx)
{
  Uint32 flags = 0;
  Uint32 subsystems;
  int ret;

  /* Initialize SDL */
  subsystems = SDL_WasInit(SDL_INIT_VIDEO);
  if (subsystems == 0) {
    ret = SDL_Init(SDL_INIT_VIDEO);
    if (ret == -1) {
      xerror("Couldn't initialize SDL: %s\n", SDL_GetError());
    }
  }

  /* We assume running on the first screen/display */
  const int screen = 0;
  if (SDL_GetCurrentDisplayMode(screen, &current) == 0) {
#ifdef DEBUG_SDL2
    printf("[i] SDL Screen resolution: %dx%d\n", current.w, current.h);
#endif
  } else {
    xerror("SDL_GetCurrentDisplayMode failed\n");
  }

  if (ctx->window_decorations == 0) {
    flags |= SDL_WINDOW_BORDERLESS;
  }

#ifndef FIXED
  flags |= SDL_WINDOW_RESIZABLE;
#endif
  create_window(flags);

#ifdef NO_MOUSE_CURSOR
  SDL_ShowCursor(SDL_DISABLE);
#endif

  /* Fill windows on boot */
  /* Initialize main window with target picture */
  /* No colormaps are loaded yet, so we convert to grayscale */
  const int depth = 24, pitch = 3 * WIDTH;
  const Uint32 pixel_format = SDL_PIXELFORMAT_RGB24;
  Pixel_t *target = xmalloc(3 * BUFFSIZE * sizeof(Pixel_t));
  Buffer8_t *pic = Buffer8_new();
  Buffer8_copy(ctx->target_pic->buff, pic);
  Buffer8_flip_v(pic);
  const Pixel_t *src = pic->buffer;
  Pixel_t *dst = target;
  uint32_t i = 0;

  for (i = 0; i < BUFFSIZE; i++, src++, dst += 3) {
    dst[0] = dst[1] = dst[2] = *src;
  }
  Buffer8_delete(pic);
  SDL_Surface *surf = SDL_CreateRGBSurfaceWithFormatFrom((void *)target, WIDTH, HEIGHT,
                      depth, pitch, pixel_format);
  assert(NULL != surf);
  if (SDL_BlitScaled(surf, NULL, SDL_GetWindowSurface(window), NULL) < 0) {
    xerror("SDL_BlitScaled failed\n");
  }
  SDL_FreeSurface(surf);
  xfree(target);
  if (SDL_UpdateWindowSurface(window) < 0) {
    xerror("[2] SDL_UpdateWindowSurface failed: %s\n", SDL_GetError());
  }
#ifdef WITH_SDL2UI
  xpthread_create(&thread, NULL, osd_thread, (void *)ctx);
#endif

  return 1;
}


void
destroy(Context_t *ctx)
{
#ifdef WITH_SDL2UI
  xpthread_join(thread, NULL);
#endif
  SDL_DestroyWindow(window);
  SDL_Quit();
}


void
fullscreen(const int fs)
{
  if (fs) {
    printf("[S] Set full-screen\n");
  } else {
    printf("[S] Unset full-screen\n");
  }
  SDL_SetWindowFullscreen(window, fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}


void
switch_cursor(void)
{
  SDL_ShowCursor(SDL_ShowCursor(SDL_QUERY) ? SDL_DISABLE : SDL_ENABLE);
}
