// Copyright (c) 2010-2026, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-443271.
//
// This file is part of the GLVis visualization tool and library. For more
// information and source code availability see https://glvis.org.
//
// GLVis is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.

#ifndef GLVIS_SDL_HPP
#define GLVIS_SDL_HPP

#include <string>
#include <mutex>
#include <condition_variable>
#include <deque>
#include "../glwindow.hpp"
#include "sdl_helper.hpp"

typedef void (*TouchDelegate)(SDL_MultiGestureEvent&);
typedef void (*WindowDelegate)(int, int);

class SdlMainThread;
SdlMainThread& GetSdlMainThread();

class SdlWindow : public GLWindow
{
private:
   friend class SdlMainThread;
   struct Handle
   {
      SDL_Window * hwnd{nullptr};
      SDL_GLContext gl_ctx{};

      Handle() = default;

      Handle(const std::string& title, int x, int y, int w, int h,
             Uint32 wndflags);

      ~Handle();

      Handle(Handle&& other)
         : hwnd(other.hwnd), gl_ctx(other.gl_ctx)
      {
         other.hwnd = nullptr;
         other.gl_ctx = 0;
      }

      Handle& operator= (Handle&& other)
      {
         std::swap(hwnd, other.hwnd);
         std::swap(gl_ctx, other.gl_ctx);
         return *this;
      }

      bool isInitialized() const
      {
         return (hwnd != nullptr && gl_ctx != 0);
      }
   };

   int window_id = -1;
   Handle handle;

   static const int high_dpi_threshold = 144;
   // The display is high-dpi when:
   // - SDL's "screen coordinates" sizes are different from the pixel sizes, or
   // - either the horizontal or the vertical dpi, as returned by getDpi(),
   //   is >= high_dpi_threshold, defined above.
   bool high_dpi = false;
   // Ratio of SDL's "screen coordinates" to GLVis' "screen coordinates":
   // - set to 1 on non-high-dpi displays,
   // - set to 1 on high-dpi displays where SDL's "screen coordinates" sizes are
   //   different from the pixels sizes (e.g. Mac retina displays),
   // - set to 2 on other high-dpi displays, so that GLVis can always work with
   //   scaled "screen coordinates" on all high-dpi displays.
   float pixel_scale_x = 1.0f, pixel_scale_y = 1.0f;

   bool running;


   WindowDelegate onReshape{nullptr};
   TouchDelegate onTouchPinch{nullptr};
   TouchDelegate onTouchRotate{nullptr};

   bool update_before_expose{false};

   // bool requiresExpose;
   bool takeScreenshot{false};
   std::string screenshot_file;
   bool screenshot_convert;
   bool lastKeyDownProcessed;
   SDL_Keymod lastKeyDownMods;
   char lastKeyDownChar;

   // internal event handlers
   void windowEvent(SDL_WindowEvent& ew);
   void motionEvent(SDL_MouseMotionEvent& em);
   void mouseEventDown(SDL_MouseButtonEvent& eb);
   void mouseEventUp(SDL_MouseButtonEvent& eb);
   void keyDownEvent(SDL_Keysym& ks);
   void textInputEvent(const SDL_TextInputEvent &e);
   void multiGestureEvent(SDL_MultiGestureEvent & e);

   bool is_multithreaded{true};

   bool call_idle_func{false};

   // Hand off events to the SdlWindow. Intended to be called by the main SDL
   // thread in MainThread::MainLoop().
   void queueEvents(std::vector<SDL_Event> events)
   {
      {
         std::lock_guard<std::mutex> evt_guard{event_mutex};
         waiting_events.insert(waiting_events.end(), events.begin(), events.end());
      }
      if (is_multithreaded)
      {
         events_available.notify_all();
      }
   }

   std::condition_variable events_available;
   std::mutex event_mutex;
   // The window-specific events collected by the main event thread.
   std::deque<SDL_Event> waiting_events;
public:
   SdlWindow();
   ~SdlWindow();

   // Initializes SDL2 and starts the main loop. Should be called from the main
   // thread only.
   static void StartSDL(bool server_mode);

   /** @brief Creates a new OpenGL window. Returns false if SDL or OpenGL
       initialization fails. */
   bool createWindow(const char * title, int x, int y, int w, int h,
                     bool legacyGlOnly) override;

   /// Runs the window loop.
   void mainLoop() override;
   void mainIter() override;

   // Called by worker threads in GLVisCommand::signal()
   void signalLoop() override;

   void setOnReshape(WindowDelegate func) { onReshape = func; }
   void setTouchPinchCallback(TouchDelegate cb) { onTouchPinch = cb; }
   void setTouchRotateCallback(TouchDelegate cb) { onTouchRotate = cb; }

   void clearEvents() override
   {
      GLWindow::clearEvents();
      onReshape = nullptr;
   }

   void getWindowSize(int& w, int& h) const override;
   void getGLDrawSize(int& w, int& h) const override;
   void getDpi(int& wdpi, int& hdpi) const override;
   /// This property is set by createWindow().
   bool isHighDpi() const override { return high_dpi; }

   void setWindowTitle(const std::string& title) override;
   void setWindowTitle(const char* title) override;
   void setWindowSize(int w, int h) override;
   void setWindowPos(int x, int y) override;

   void signalKeyDown(SDL_Keycode k, SDL_Keymod m = KMOD_NONE) override;
   void signalQuit() override { running = false; }

   /// Queues a screenshot to be taken.
   void screenshot(std::string filename, bool convert = false) override
   {
      takeScreenshot = true;
      screenshot_file = filename;
      screenshot_convert = convert;
      // Queue up an expose, so Screenshot() can pull image from the back
      // buffer
      signalExpose();
   }

   void swapBuffer();

   operator bool() { return handle.isInitialized(); }
   bool isWindowInitialized() const override { return handle.isInitialized(); }
   /// Returns true if the OpenGL context was successfully initialized.
   bool isGlInitialized() const override;
};

#endif
