///////////////////////////////////////////////////////////////////////////
// Name:        src/gtk/display.cpp
// Author:      Paul Cornett
// Created:     2014-04-17
// Copyright:   (c) 2014 Paul Cornett
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#include "wx/private/display.h"
#if wxUSE_DISPLAY
    #include "wx/window.h"
#endif

#include "wx/gtk/private/wrapgtk.h"
#include "wx/gtk/private/backend.h"
#ifdef GDK_WINDOWING_X11
    #ifndef __WXGTK4__
        #include "wx/unix/private/displayx11.h"

        #define wxGTK_HAVE_X11_DISPLAY
    #endif

    #include <gdk/gdkx.h>
#endif

// This file is not used at all when using Win32.
#if !defined(GDK_WINDOWING_WIN32)

GdkWindow* wxGetTopLevelGDK();

// There are 2 quite different implementations here: one for GTK+ 4 and the
// other one for the previous versions.

#ifdef __WXGTK4__

static inline GdkDisplay* GetDisplay()
{
    return gdk_window_get_display(wxGetTopLevelGDK());
}

// This class is always defined as it's used for the main display even when
// wxUSE_DISPLAY == 0.
class wxDisplayImplGTK : public wxDisplayImpl
{
    typedef wxDisplayImpl base_type;
public:
    wxDisplayImplGTK(unsigned i);
    virtual wxRect GetGeometry() const override;
    virtual wxRect GetClientArea() const override;
    virtual int GetDepth() const override;
    virtual double GetScaleFactor() const override;

#if wxUSE_DISPLAY
    virtual bool IsPrimary() const override;
    virtual wxArrayVideoModes GetModes(const wxVideoMode& mode) const override;
    virtual wxVideoMode GetCurrentMode() const override;
    virtual bool ChangeMode(const wxVideoMode& mode) override;
#endif // wxUSE_DISPLAY

    GdkMonitor* const m_monitor;
};

// This class is only defined when we're built with full display support.
#if wxUSE_DISPLAY
class wxDisplayFactoryGTK: public wxDisplayFactory
{
public:
    virtual wxDisplayImpl* CreateDisplay(unsigned n) override;
    virtual unsigned GetCount() override;
    virtual int GetFromPoint(const wxPoint& pt) override;
    virtual int GetFromWindow(const wxWindow* win) override;
};

wxDisplayImpl* wxDisplayFactoryGTK::CreateDisplay(unsigned n)
{
    return new wxDisplayImplGTK(n);
}

unsigned wxDisplayFactoryGTK::GetCount()
{
    return gdk_display_get_n_monitors(::GetDisplay());
}

int wxDisplayFactoryGTK::GetFromPoint(const wxPoint& pt)
{
    GdkRectangle rect;
    GdkDisplay* display = ::GetDisplay();
    GdkMonitor* monitor = gdk_display_get_monitor_at_point(display, pt.x, pt.y);
    gdk_monitor_get_geometry(monitor, &rect);
    if (wxRect(rect.x, rect.y, rect.width, rect.height).Contains(pt))
    {
        for (unsigned i = gdk_display_get_n_monitors(display); i--;)
        {
            if (gdk_display_get_monitor(display, i) == monitor)
                return i;
        }
    }
    return wxNOT_FOUND;
}

int wxDisplayFactoryGTK::GetFromWindow(const wxWindow* win)
{
    if (win && win->m_widget)
    {
        GdkDisplay* display = gtk_widget_get_display(win->m_widget);
        GdkMonitor* monitor;
        if (GdkWindow* window = gtk_widget_get_window(win->m_widget))
            monitor = gdk_display_get_monitor_at_window(display, window);
        else
            monitor = gdk_display_get_primary_monitor(display);

        for (unsigned i = gdk_display_get_n_monitors(display); i--;)
        {
            if (gdk_display_get_monitor(display, i) == monitor)
                return i;
        }
    }
    return wxNOT_FOUND;
}
#endif // wxUSE_DISPLAY

wxDisplayImplGTK::wxDisplayImplGTK(unsigned i)
    : base_type(i)
    , m_monitor(gdk_display_get_monitor(GetDisplay(), i))
{
}

wxRect wxDisplayImplGTK::GetGeometry() const
{
    GdkRectangle rect;
    gdk_monitor_get_geometry(m_monitor, &rect);
    return wxRect(rect.x, rect.y, rect.width, rect.height);
}

wxRect wxDisplayImplGTK::GetClientArea() const
{
    GdkRectangle rect;
    gdk_monitor_get_workarea(m_monitor, &rect);
    return wxRect(rect.x, rect.y, rect.width, rect.height);
}

int wxDisplayImplGTK::GetDepth() const
{
    return 24;
}

double wxDisplayImplGTK::GetScaleFactor() const
{
    return gdk_monitor_get_scale_factor(m_monitor);
}

#if wxUSE_DISPLAY
bool wxDisplayImplGTK::IsPrimary() const
{
    return gdk_monitor_is_primary(m_monitor) != 0;
}

wxArrayVideoModes
wxDisplayImplGTK::GetModes(const wxVideoMode& WXUNUSED(mode)) const
{
    return wxArrayVideoModes();
}

wxVideoMode wxDisplayImplGTK::GetCurrentMode() const
{
    return wxVideoMode();
}

bool wxDisplayImplGTK::ChangeMode(const wxVideoMode& WXUNUSED(mode))
{
    return false;
}
#endif // wxUSE_DISPLAY

#else // !__WXGTK4__

#if defined(__WXGTK3__) && defined(GDK_WINDOWING_X11)

static inline bool wxIsX11GDKScreen(GdkScreen* screen)
{
    return wxGTKImpl::IsX11(screen);
}

#else // !__WXGTK3__

static inline int wx_gdk_screen_get_primary_monitor(GdkScreen* screen)
{
    int monitor = 0;
#if GTK_CHECK_VERSION(2,20,0)
    if (wx_is_at_least_gtk2(20))
        monitor = gdk_screen_get_primary_monitor(screen);
#endif
    return monitor;
}
#define gdk_screen_get_primary_monitor wx_gdk_screen_get_primary_monitor

static inline bool wxIsX11GDKScreen(GdkScreen* WXUNUSED(screen))
{
    return true;
}

#endif // __WXGTK3__/!__WXGTK3__

static inline void
wx_gdk_screen_get_monitor_workarea(GdkScreen* screen, int monitor, GdkRectangle* dest)
{
    wxGCC_WARNING_SUPPRESS(deprecated-declarations)
#if GTK_CHECK_VERSION(3,4,0)
    if (gtk_check_version(3,4,0) == nullptr)
        gdk_screen_get_monitor_workarea(screen, monitor, dest);
    else
#endif
    {
        gdk_screen_get_monitor_geometry(screen, monitor, dest);
#ifdef wxGTK_HAVE_X11_DISPLAY
        if ( wxIsX11GDKScreen(screen) )
        {
            GdkRectangle rect = { 0, 0, 0, 0 };
            wxGetWorkAreaX11(GDK_SCREEN_XSCREEN(screen),
                rect.x, rect.y, rect.width, rect.height);
            // in case _NET_WORKAREA result is too large
            if (rect.width && rect.height)
                gdk_rectangle_intersect(dest, &rect, dest);
        }
#endif // wxGTK_HAVE_X11_DISPLAY
    }
    wxGCC_WARNING_RESTORE()
}
#define gdk_screen_get_monitor_workarea wx_gdk_screen_get_monitor_workarea

static inline GdkScreen* GetScreen()
{
    return gdk_window_get_screen(wxGetTopLevelGDK());
}

class wxDisplayImplGTK : public wxDisplayImpl
{
    typedef wxDisplayImpl base_type;
public:
    wxDisplayImplGTK(unsigned i);
    virtual wxRect GetGeometry() const override;
    virtual wxRect GetClientArea() const override;
    virtual int GetDepth() const override;
#if GTK_CHECK_VERSION(3,10,0)
    virtual double GetScaleFactor() const override;
#endif // GTK+ 3.10
    virtual wxSize GetRawPPI() const override;

#if wxUSE_DISPLAY
    virtual bool IsPrimary() const override;
    virtual wxArrayVideoModes GetModes(const wxVideoMode& mode) const override;
    virtual wxVideoMode GetCurrentMode() const override;
    virtual bool ChangeMode(const wxVideoMode& mode) override;
#endif // wxUSE_DISPLAY

    GdkScreen* const m_screen;
};

#if wxUSE_DISPLAY
class wxDisplayFactoryGTK: public wxDisplayFactory
{
public:
    virtual wxDisplayImpl* CreateDisplay(unsigned n) override;
    virtual unsigned GetCount() override;
    virtual int GetFromPoint(const wxPoint& pt) override;
    virtual int GetFromWindow(const wxWindow* win) override;
};

wxGCC_WARNING_SUPPRESS(deprecated-declarations)

wxDisplayImpl* wxDisplayFactoryGTK::CreateDisplay(unsigned n)
{
    return new wxDisplayImplGTK(n);
}

unsigned wxDisplayFactoryGTK::GetCount()
{
    return gdk_screen_get_n_monitors(GetScreen());
}

int wxDisplayFactoryGTK::GetFromPoint(const wxPoint& pt)
{
    GdkRectangle rect;
    GdkScreen* screen = GetScreen();
    int monitor = gdk_screen_get_monitor_at_point(screen, pt.x, pt.y);
    gdk_screen_get_monitor_geometry(screen, monitor, &rect);
    if (!wxRect(rect.x, rect.y, rect.width, rect.height).Contains(pt))
        monitor = wxNOT_FOUND;
    return monitor;
}

int wxDisplayFactoryGTK::GetFromWindow(const wxWindow* win)
{
    int monitor = wxNOT_FOUND;
    if (win && win->m_widget)
    {
        GdkScreen* screen = gtk_widget_get_screen(win->m_widget);
        if (GdkWindow* window = gtk_widget_get_window(win->m_widget))
            monitor = gdk_screen_get_monitor_at_window(screen, window);
        else
            monitor = gdk_screen_get_primary_monitor(screen);
    }
    return monitor;
}
#endif // wxUSE_DISPLAY

wxDisplayImplGTK::wxDisplayImplGTK(unsigned i)
    : base_type(i)
    , m_screen(GetScreen())
{
}

wxRect wxDisplayImplGTK::GetGeometry() const
{
    GdkRectangle rect;
    gdk_screen_get_monitor_geometry(m_screen, m_index, &rect);
    return wxRect(rect.x, rect.y, rect.width, rect.height);
}

wxRect wxDisplayImplGTK::GetClientArea() const
{
    GdkRectangle rect;
    gdk_screen_get_monitor_workarea(m_screen, m_index, &rect);
    return wxRect(rect.x, rect.y, rect.width, rect.height);
}

int wxDisplayImplGTK::GetDepth() const
{
    return gdk_visual_get_depth(gdk_screen_get_system_visual(m_screen));
}

#if GTK_CHECK_VERSION(3,10,0)
double wxDisplayImplGTK::GetScaleFactor() const
{
    if ( gtk_check_version(3,10,0) == nullptr )
        return gdk_screen_get_monitor_scale_factor(m_screen, m_index);

    return 1.0;
}
#endif // GTK+ 3.10

wxSize wxDisplayImplGTK::GetRawPPI() const
{
    auto const widthInMM = gdk_screen_get_monitor_width_mm(m_screen, m_index);
    auto const heightInMM = gdk_screen_get_monitor_height_mm(m_screen, m_index);

    // Don't use manifestly invalid values.
    if ( widthInMM <= 0 && heightInMM <= 0 )
        return wxDisplay::GetStdPPI();

    const wxRect geometry = GetGeometry();
    const double ppiX = geometry.width * 25.4 / widthInMM;
    const double ppiY = geometry.height * 25.4 / heightInMM;

    return wxSize(wxRound(ppiX), wxRound(ppiY));
}

#if wxUSE_DISPLAY
bool wxDisplayImplGTK::IsPrimary() const
{
    return gdk_screen_get_primary_monitor(m_screen) == int(m_index);
}

wxArrayVideoModes wxDisplayImplGTK::GetModes(const wxVideoMode& mode) const
{
    wxArrayVideoModes modes;
#ifdef wxGTK_HAVE_X11_DISPLAY
    if ( wxIsX11GDKScreen(m_screen) )
    {
        Display* display = GDK_DISPLAY_XDISPLAY(gdk_screen_get_display(m_screen));
#ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
        int nScreen = gdk_x11_screen_get_screen_number(m_screen);
        modes = wxXF86VidMode_GetModes(mode, display, nScreen);
#else
        modes = wxX11_GetModes(this, mode, display);
#endif
    }
    else
#endif
    {
        const wxVideoMode current(GetCurrentMode());
        if (current.Matches(mode))
            modes.push_back(current);
    }
    return modes;
}

wxVideoMode wxDisplayImplGTK::GetCurrentMode() const
{
    wxVideoMode mode;
#if defined(wxGTK_HAVE_X11_DISPLAY) && defined(HAVE_X11_EXTENSIONS_XF86VMODE_H)
    if ( wxIsX11GDKScreen(m_screen) )
    {
        Display* display = GDK_DISPLAY_XDISPLAY(gdk_screen_get_display(m_screen));
        int nScreen = gdk_x11_screen_get_screen_number(m_screen);
        mode = wxXF86VidMode_GetCurrentMode(display, nScreen);
    }
    else
#endif
    {
        const wxRect rect(GetGeometry());
        mode.w = rect.width;
        mode.h = rect.height;
        mode.bpp = gdk_visual_get_depth(gdk_screen_get_system_visual(m_screen));
    }
    return mode;
}

bool wxDisplayImplGTK::ChangeMode(const wxVideoMode& mode)
{
    bool success = false;
#if defined(wxGTK_HAVE_X11_DISPLAY) && defined(HAVE_X11_EXTENSIONS_XF86VMODE_H)
    if ( wxIsX11GDKScreen(m_screen) )
    {
        Display* display = GDK_DISPLAY_XDISPLAY(gdk_screen_get_display(m_screen));
        int nScreen = gdk_x11_screen_get_screen_number(m_screen);
        success = wxXF86VidMode_ChangeMode(mode, display, nScreen);
    }
#else
    wxUnusedVar(mode);
#endif
    return success;
}

wxGCC_WARNING_RESTORE()

#endif // wxUSE_DISPLAY

#endif // __WXGTK4__/!__WXGTK4__

#if wxUSE_DISPLAY

wxDisplayFactory* wxDisplay::CreateFactory()
{
    return new wxDisplayFactoryGTK;
}

#else // !wxUSE_DISPLAY

class wxDisplayFactorySingleGTK : public wxDisplayFactorySingle
{
protected:
    virtual wxDisplayImpl *CreateSingleDisplay()
    {
        return new wxDisplayImplGTK(0);
    }
};

wxDisplayFactory* wxDisplay::CreateFactory()
{
    return new wxDisplayFactorySingleGTK;
}

#endif // wxUSE_DISPLAY/!wxUSE_DISPLAY

#endif // !defined(GDK_WINDOWING_WIN32)
