// LAF OS Library
// Copyright (C) 2021-2023  Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "os/x11/system.h"

#include "os/x11/cursor.h"

#include <X11/Xcursor/Xcursor.h>
#include <X11/cursorfont.h>

#include <algorithm>
#include <array>

namespace os {

class XCursorImageX11 {
public:
  XCursorImageX11() : m_ximage(nullptr) {}
  ~XCursorImageX11() { reset(); }

  ::XcursorImage* recreate(int w, int h)
  {
    if (m_ximage && m_ximage->width == w && m_ximage->height == h)
      return m_ximage;
    reset();
    return m_ximage = XcursorImageCreate(w, h);
  }

  void reset()
  {
    if (m_ximage)
      XcursorImageDestroy(m_ximage);
    m_ximage = nullptr;
  }

private:
  ::XcursorImage* m_ximage;
};

// Cache of system cursors
std::array<CursorRef, int(NativeCursor::Cursors)> g_nativeCursors;
XCursorImageX11 g_cachedCursorImage;

SystemX11::~SystemX11()
{
  destroyInstance();
  g_nativeCursors.fill(nullptr);
  g_cachedCursorImage.reset();
}

CursorRef SystemX11::getNativeCursor(NativeCursor cursor)
{
  const int i = int(cursor);
  if (i < 0 || i >= int(NativeCursor::Cursors))
    return nullptr;
  if (g_nativeCursors[i].get())
    return g_nativeCursors[i];

  ::Display* display = X11::instance()->display();
  ::Cursor xcursor = None;
  switch (cursor) {
    case NativeCursor::Hidden: {
      char data = 0;
      const Pixmap image =
        XCreateBitmapFromData(display, DefaultRootWindow(display), (char*)&data, 1, 1);

      XColor color;
      xcursor = XCreatePixmapCursor(display, image, image, &color, &color, 0, 0);

      XFreePixmap(display, image);
      break;
    }
    case NativeCursor::Arrow:     xcursor = XCreateFontCursor(display, XC_arrow); break;
    case NativeCursor::Crosshair: xcursor = XCreateFontCursor(display, XC_crosshair); break;
    case NativeCursor::IBeam:     xcursor = XCreateFontCursor(display, XC_xterm); break;
    case NativeCursor::Wait:      xcursor = XCreateFontCursor(display, XC_watch); break;
    case NativeCursor::Link:      xcursor = XCreateFontCursor(display, XC_hand1); break;
    case NativeCursor::Help:      xcursor = XCreateFontCursor(display, XC_question_arrow); break;
    case NativeCursor::Forbidden: xcursor = XCreateFontCursor(display, XC_X_cursor); break;
    case NativeCursor::Move:      xcursor = XCreateFontCursor(display, XC_fleur); break;
    case NativeCursor::SizeN:     xcursor = XCreateFontCursor(display, XC_top_side); break;
    case NativeCursor::SizeNS:    xcursor = XCreateFontCursor(display, XC_sb_v_double_arrow); break;
    case NativeCursor::SizeS:     xcursor = XCreateFontCursor(display, XC_bottom_side); break;
    case NativeCursor::SizeW:     xcursor = XCreateFontCursor(display, XC_left_side); break;
    case NativeCursor::SizeE:     xcursor = XCreateFontCursor(display, XC_right_side); break;
    case NativeCursor::SizeWE:    xcursor = XCreateFontCursor(display, XC_sb_h_double_arrow); break;
    case NativeCursor::SizeNW:    xcursor = XCreateFontCursor(display, XC_top_left_corner); break;
    case NativeCursor::SizeNE:    xcursor = XCreateFontCursor(display, XC_top_right_corner); break;
    case NativeCursor::SizeSW:    xcursor = XCreateFontCursor(display, XC_bottom_left_corner); break;
    case NativeCursor::SizeSE:    xcursor = XCreateFontCursor(display, XC_bottom_right_corner); break;
  }

  return g_nativeCursors[i] = make_ref<CursorX11>(xcursor);
}

CursorRef SystemX11::makeCursor(const Surface* surface, const gfx::Point& focus, const int scale)
{
  ASSERT(surface);
  ::Display* display = X11::instance()->display();

  // This X11 server doesn't support ARGB cursors.
  if (!XcursorSupportsARGB(display))
    return nullptr;

  SurfaceFormatData format;
  surface->getFormat(&format);

  // Only for 32bpp surfaces
  if (format.bitsPerPixel != 32)
    return nullptr;

  const int w = scale * surface->width();
  const int h = scale * surface->height();

  ::Cursor xcursor = None;
  ::XcursorImage* image = g_cachedCursorImage.recreate(w, h);
  if (image != None) {
    XcursorPixel* dst = image->pixels;
    for (int y = 0; y < h; ++y) {
      const uint32_t* src = (const uint32_t*)surface->getData(0, y / scale);
      for (int x = 0, u = 0; x < w; ++x, ++dst) {
        const uint32_t c = *src;
        *dst = (((c & format.alphaMask) >> format.alphaShift) << 24) |
               (((c & format.redMask) >> format.redShift) << 16) |
               (((c & format.greenMask) >> format.greenShift) << 8) |
               (((c & format.blueMask) >> format.blueShift));
        if (++u == scale) {
          u = 0;
          ++src;
        }
      }
    }

    // We have to limit the focus position inside the cursor area to
    // avoid crash from XcursorImageLoadCursor():
    //
    //   X Error of failed request:  BadMatch (invalid parameter attributes)
    //     Major opcode of failed request:  138 (RENDER)
    //     Minor opcode of failed request:  27 (RenderCreateCursor)
    image->xhot = std::clamp(scale * focus.x + scale / 2, 0, w - 1);
    image->yhot = std::clamp(scale * focus.y + scale / 2, 0, h - 1);
    xcursor = XcursorImageLoadCursor(display, image);
  }

  return make_ref<CursorX11>(xcursor);
}

} // namespace os
