/*
 * Copyright © 2024 Joel Winarske
 *
 * 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 (including the next
 * paragraph) 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.
 */

// Danil, 2021+ Vulkan shader launcher, self
// https://github.com/danilw/vulkan-shadertoy-launcher The MIT License

#pragma once

#include "vulkan/render.h"
#include "vulkan/utils.h"

class VulkanUtils;

class VulkanRender;

class ShaderToy : public VulkanUtils, public VulkanRender {
 public:
  ShaderToy();

  ~ShaderToy();

  int init(int width,
           int height,
           struct wl_display* wl_display,
           struct wl_surface* wl_surface,
           uint32_t dev_index,
           bool use_gpu_idx,
           bool debug,
           bool reload_shaders,
           VkPresentModeKHR present_mode =
               VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR);

  void draw_frame(uint32_t time);

  struct app_os_window* get_app_os_window() { return &os_window_; }

  void toggle_pause() {
    os_window_.app_data.pause = !os_window_.app_data.pause;
  }

  void toggle_draw_debug() {
    os_window_.app_data.drawdebug = !os_window_.app_data.drawdebug;
  }

  void toggle_fps_lock() { os_window_.fps_lock = !os_window_.fps_lock; }

  void screen_shot() { screenshot_once_ = true; }

 private:
  uint32_t resize_size_[2]{};

  bool main_image_srgb_ = false;  // srgb surface fix

  // keyboard is texture that send from this data
  uint8_t keyboard_texture_[256 * 3 * 4]{};  // texture
  bool keyboard_draw_{};
  bool screenshot_once_{};

  // update to 2021 Shadertoy iMouse.w change
  // https://www.shadertoy.com/view/llySRh (comments)
  bool last_iMousel_clicked_[2] = {};

  // do not edit, it just to see where keyboard texture used
  static constexpr uint32_t iKeyboard = UINT32_C(1);

  // to build-in compressed shaders into bin(exe) file
  // used OFFSCREEN_BUFFERS size, names of .hex files should be set manually(and
  // edit yariv_shaders[]), this example using 4 buffers same as on shadertoy
  // #define YARIV_SHADER

  struct shaders_push_constants {
    float iMouse[4];
    float iDate[4];
    int iMouse_lr[2];
    float iResolution[2];
    int debugdraw;  // look function check_hotkeys
    int pCustom;    // custom data
    float iTime;
    float iTimeDelta;
    int iFrame;
  };

  enum {
    BUFFER_VERTICES = 0,
    BUFFER_INDICES = 1,
  };
  enum {
    SHADER_MAIN_VERTEX = 0,
    SHADER_MAIN_FRAGMENT = 1,
  };

  struct render_data {
    struct objects {
      struct vertex {
        float pos[3];
      } vertices[3];

      uint16_t indices[3];
    } objects;

    struct shaders_push_constants push_constants;

    struct vk_image images[IMAGE_TEXTURES + OFFSCREEN_BUFFERS + iKeyboard];
    struct vk_buffer buffers[2];
    struct vk_shader shaders[2 + OFFSCREEN_BUFFERS * 2];
    struct vk_graphics_buffers* main_gbuffers;
    struct vk_offscreen_buffers* buf_obuffers;

    VkRenderPass buf_render_pass[OFFSCREEN_BUFFERS];
    struct vk_layout buf_layout[OFFSCREEN_BUFFERS];
    struct vk_pipeline buf_pipeline[OFFSCREEN_BUFFERS];
    VkDescriptorSet buf_desc_set[OFFSCREEN_BUFFERS];

    VkRenderPass main_render_pass;
    struct vk_layout main_layout;
    struct vk_pipeline main_pipeline;
    VkDescriptorSet main_desc_set;

  } render_data_{};

  uint32_t dev_index_{};
  bool use_gpu_idx_{};

  VkInstance vk_{};
  struct vk_physical_device phy_dev_{};
  struct vk_device dev_{};
  struct vk_swapchain swapchain_{};
  struct app_os_window os_window_{};

  struct vk_render_essentials essentials_{};

  VkFence offscreen_fence_ = VK_NULL_HANDLE;
  VkQueue offscreen_queue_[OFFSCREEN_BUFFERS] = {VK_NULL_HANDLE};
  VkCommandBuffer offscreen_cmd_buffer_[OFFSCREEN_BUFFERS] = {VK_NULL_HANDLE};
  VkSemaphore wait_buf_sem_ = VK_NULL_HANDLE;
  VkSemaphore wait_main_sem_ = VK_NULL_HANDLE;
  bool first_submission_ = true;

  void update_key_map(int w, int h, bool val);

  void update_keypress();

  void check_hotkeys(struct app_os_window* os_window);

  vk_error allocate_render_data(struct vk_physical_device* phy_dev,
                                struct vk_device* dev,
                                struct vk_swapchain* swapchain,
                                struct vk_render_essentials* essentials,
                                struct render_data* render_data,
                                bool reload_shaders);

  static void free_render_data(struct vk_device* dev,
                               struct vk_render_essentials* essentials,
                               struct render_data* render_data);

  static void exit_cleanup_render_loop(struct vk_device* dev,
                                       struct vk_render_essentials* essentials,
                                       struct render_data* render_data,
                                       VkSemaphore wait_buf_sem,
                                       VkSemaphore wait_main_sem,
                                       VkFence offscreen_fence);

  void render_loop_init(struct vk_physical_device* phy_dev,
                        struct vk_device* dev,
                        struct vk_swapchain* swapchain,
                        struct app_os_window* os_window);

  static void exit_cleanup(VkInstance vk,
                           struct vk_device* dev,
                           struct vk_swapchain* swapchain);

  bool on_window_resize(struct vk_physical_device* phy_dev,
                        struct vk_device* dev,
                        struct vk_render_essentials* essentials,
                        struct vk_swapchain* swapchain,
                        struct render_data* render_data,
                        struct app_os_window* os_window);

  static void update_params(struct app_data_struct* app_data, bool fps_lock);

  void set_push_constants(struct app_os_window* os_window);

  void update_push_constants_window_size(struct app_os_window* os_window);

  void update_push_constants_local_size(float width, float height);

  static bool render_loop_buf(struct vk_physical_device* /* phy_dev */,
                              struct vk_device* dev,
                              struct vk_render_essentials* essentials,
                              struct render_data* render_data,
                              VkCommandBuffer cmd_buffer,
                              int render_index,
                              int buffer_index,
                              struct app_data_struct* /* app_data */);

  bool render_loop_draw(struct vk_physical_device* phy_dev,
                        struct vk_device* dev,
                        struct vk_swapchain* swapchain,
                        struct app_os_window* os_window);

  bool update_iKeyboard_texture(struct vk_physical_device* /* phy_dev */,
                                struct vk_device* /* dev */,
                                struct vk_render_essentials* /* essentials */,
                                struct render_data* /* render_data */);

  static vk_error transition_images_screenshot_swapchain_begin(
      struct vk_device* dev,
      struct vk_render_essentials* essentials,
      struct vk_image* srcImage,
      struct vk_image* dstImage);

  static vk_error transition_images_screenshot_swapchain_end(
      struct vk_device* dev,
      struct vk_render_essentials* essentials,
      struct vk_image* srcImage,
      struct vk_image* dstImage);

  // RGBA BMP from https://en.wikipedia.org/wiki/BMP_file_format
  static inline unsigned char ev(int32_t v);

  static void write_bmp(uint32_t w, uint32_t h, const uint8_t* rgba);

  static vk_error make_screenshot(struct vk_physical_device* phy_dev,
                                  struct vk_device* dev,
                                  struct vk_swapchain* swapchain,
                                  struct vk_render_essentials* essentials,
                                  struct render_data* render_data,
                                  uint32_t image_index);
};