/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2003-2023 Kim Woelders
 *
 * 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 shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * 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 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.
 */
#include "E.h"
#include "ewins.h"
#include "ipc.h"
#include "screen.h"
#include "xwin.h"

typedef struct {
    int             type;
    int             head;
    int             x, y;
    int             w, h;
} EScreen;

static EScreen *p_screens = NULL;
static int      n_screens = 0;

static void
_ScreenAdd(int type, int head, int x, int y, unsigned int w, unsigned int h)
{
    EScreen        *es;

    n_screens++;
    p_screens = EREALLOC(EScreen, p_screens, n_screens);

    es = p_screens + n_screens - 1;
    es->type = type;
    es->head = head;
    es->x = x;
    es->y = y;
    es->w = w;
    es->h = h;
}

int
ScreenGetCurrent(void)
{
    int             px, py;

    if (n_screens <= 1)
        return -1;              /* Only one head */

    EQueryPointer(NULL, &px, &py, NULL, NULL);

    return ScreenGetHead(px, py);
}

#if USE_XRANDR
#include <X11/extensions/Xrandr.h>
#define RANDR_VERSION VERS(RANDR_MAJOR, RANDR_MINOR)

static void
_ScreenInitXrandr(void)
{
#if RANDR_VERSION >= VERS(1, 2) /* >= 1.2 */
    XRRScreenResources *psr;
    XRRCrtcInfo    *pci;
    int             i;

    psr = XRRGetScreenResources(disp, WinGetXwin(VROOT));
    if (!psr)
        return;

    for (i = 0; i < psr->ncrtc; i++)
    {
        pci = XRRGetCrtcInfo(disp, psr, psr->crtcs[i]);
        if (!pci)
            break;
        if (pci->width == 0 || pci->height == 0 || pci->noutput == 0)
            goto next;
        _ScreenAdd(0, i, pci->x, pci->y, pci->width, pci->height);
      next:
        XRRFreeCrtcInfo(pci);
    }

    XRRFreeScreenResources(psr);
#endif
}

static void
_ScreenShowInfoXrandr(void)
{
#if RANDR_VERSION >= VERS(1, 2) /* >= 1.2 */
    char            buf[4096];
    XRRScreenResources *psr;
    XRRCrtcInfo    *pci;
    XRROutputInfo  *poi;
    XRRModeInfo    *pmi;
    int             i, j, l;

    psr = XRRGetScreenResources(disp, WinGetXwin(VROOT));
    if (!psr)
        return;

    IpcPrintf("Crtc   ID      X,Y         WxH      mode   rot   nout\n");
    for (i = 0; i < psr->ncrtc; i++)
    {
        pci = XRRGetCrtcInfo(disp, psr, psr->crtcs[i]);
        if (!pci)
            break;
        IpcPrintf("%3d  %#05lx  %4d,%4d   %4ux%4u  %#05lx %4d %5d\n",
                  i, psr->crtcs[i],
                  pci->x, pci->y, pci->width, pci->height,
                  pci->mode, pci->rotation, pci->noutput);
        XRRFreeCrtcInfo(pci);
    }

    IpcPrintf
        ("Outp   ID  Name            WxH      crtc    Crtcs clOnes Modes\n");
    for (i = 0; i < psr->noutput; i++)
    {
        poi = XRRGetOutputInfo(disp, psr, psr->outputs[i]);
        if (!poi)
            break;
        l = Esnprintf(buf, sizeof(buf),
                      "%3d  %#05lx %-10s   %4lux%4lu  %#05lx",
                      i, psr->outputs[i],
                      poi->name, poi->mm_width, poi->mm_height, poi->crtc);
        l += Esnprintf(buf + l, sizeof(buf) - l, " c:");
        for (j = 0; j < poi->ncrtc; j++)
            l += Esnprintf(buf + l, sizeof(buf) - l, " %#05lx", poi->crtcs[j]);
        l += Esnprintf(buf + l, sizeof(buf) - l, " o:");
        for (j = 0; j < poi->nclone; j++)
            l += Esnprintf(buf + l, sizeof(buf) - l, " %#05lx", poi->clones[j]);
        l += Esnprintf(buf + l, sizeof(buf) - l, " m:");
        for (j = 0; j < poi->nmode; j++)
            l += Esnprintf(buf + l, sizeof(buf) - l, " %#05lx", poi->modes[j]);
        IpcPrintf("%s\n", buf);
        XRRFreeOutputInfo(poi);
    }

    IpcPrintf("Mode   ID  Name            WxH\n");
    for (i = 0; i < psr->nmode; i++)
    {
        pmi = psr->modes + i;
        IpcPrintf("%3d  %#05lx %-10s   %4ux%4u\n",
                  i, pmi->id, pmi->name, pmi->width, pmi->height);
    }

    XRRFreeScreenResources(psr);
#endif
}

#endif                          /* USE_XRANDR */

#if USE_XINERAMA
#include <X11/extensions/Xinerama.h>

static XineramaScreenInfo *
_EXineramaQueryScreens(int *number)
{
    int             event_base, error_base;

    *number = 0;

    if (!XineramaQueryExtension(disp, &event_base, &error_base))
        return NULL;

    return XineramaQueryScreens(disp, number);
}

static void
_ScreenInitXinerama(void)
{
    XineramaScreenInfo *screens;
    int             i, num_screens;

    screens = _EXineramaQueryScreens(&num_screens);

    if (num_screens > 1)
    {
        for (i = 0; i < num_screens; i++)
            _ScreenAdd(0, screens[i].screen_number, screens[i].x_org,
                       screens[i].y_org, screens[i].width, screens[i].height);
    }

    if (screens)
        XFree(screens);
}

static void
_ScreenShowInfoXinerama(void)
{
    static const char *const mt[] = { "Off", "On", "TV", "???" };
    XineramaScreenInfo *scrns;
    int             i, num, mode;

    scrns = _EXineramaQueryScreens(&num);

    mode = (XineramaIsActive(disp)) ? 1 : 0;
    if (!mode && num > 1)
        mode = 2;

    IpcPrintf("Xinerama mode: %s\n", mt[mode]);

    if (scrns)
    {
        IpcPrintf("Xinerama screens:\n");
        for (i = 0; i < num; i++)
            IpcPrintf(" %2d     %2d       %5d     %5d     %5d     %5d\n",
                      i, scrns[i].screen_number,
                      scrns[i].x_org, scrns[i].y_org, scrns[i].width,
                      scrns[i].height);
        XFree(scrns);
    }
}

#endif                          /* USE_XINERAMA */

void
ScreenInit(void)
{
    n_screens = 0;              /* Causes reconfiguration */

    if (Mode.wm.window)
        return;

#if USE_XRANDR
    _ScreenInitXrandr();
#endif
#if USE_XINERAMA
    if (n_screens == 0)         /* Only if not added in randr code */
        _ScreenInitXinerama();
#endif
}

void
ScreenSplit(unsigned int nx, unsigned int ny)
{
    unsigned int    i, j;

    if (nx > 8 || ny > 8)       /* At least some limit */
        return;

    if (nx == 0 || ny == 0)
    {
        ScreenInit();
        return;
    }

    n_screens = 0;              /* Causes reconfiguration */

    for (i = 0; i < nx; i++)
        for (j = 0; j < ny; j++)
            _ScreenAdd(1, Dpy.screen,
                       i * WinGetW(VROOT) / nx, j * WinGetH(VROOT) / ny,
                       WinGetW(VROOT) / nx, WinGetH(VROOT) / ny);
}

void
ScreenShowInfo(const char *prm __UNUSED__)
{
    int             i;

    IpcPrintf("Head  Screen  X-Origin  Y-Origin     Width    Height\n");
    IpcPrintf("Screen:\n");
    IpcPrintf(" %2d     %2d       %5d     %5d     %5d     %5d\n",
              0, Dpy.screen, 0, 0, WinGetW(VROOT), WinGetH(VROOT));

#if USE_XRANDR
    _ScreenShowInfoXrandr();
#endif
#if USE_XINERAMA
    _ScreenShowInfoXinerama();
#endif

    if (n_screens)
    {
        IpcPrintf("E-screens:\n");
        for (i = 0; i < n_screens; i++)
        {
            EScreen        *ps = p_screens + i;

            IpcPrintf(" %2d     %2d       %5d     %5d     %5d     %5d\n",
                      i, ps->head, ps->x, ps->y, ps->w, ps->h);
        }
    }
}

void
ScreenGetGeometryByHead(int head, Area *pa)
{
    EScreen        *ps;

    if (head >= 0 && head < n_screens)
    {
        ps = p_screens + head;
        pa->x = ps->x;
        pa->y = ps->y;
        pa->w = ps->w;
        pa->h = ps->h;
    }
    else
    {
        pa->x = 0;
        pa->y = 0;
        pa->w = WinGetW(VROOT);
        pa->h = WinGetH(VROOT);
    }
}

int
ScreenGetHead(int xi, int yi)
{
    int             i, dx, dy, dist, head;
    EScreen        *ps;

    head = 0;
    dist = 2147483647;

    if (n_screens > 1)
    {
        for (i = 0; i < n_screens; i++)
        {
            ps = p_screens + i;

            if (xi >= ps->x && xi < ps->x + ps->w &&
                yi >= ps->y && yi < ps->y + ps->h)
            {
                /* Inside - done */
                head = i;
                break;
            }
            dx = xi - (ps->x + ps->w / 2);
            dy = yi - (ps->y + ps->h / 2);
            dx = dx * dx + dy * dy;
            if (dx >= dist)
                continue;
            dist = dx;
            head = i;
        }
    }

    return head;
}

int
ScreenGetGeometry(int xi, int yi, Area *pa)
{
    int             head;

    head = ScreenGetHead(xi, yi);
    ScreenGetGeometryByHead(head, pa);

    return head;
}

static void
_VRootGetAvailableArea(Area *pa)
{
    EWin           *const *lst, *ewin;
    int             i, num, l, r, t, b;

    l = Conf.place.screen_struts.left;
    r = Conf.place.screen_struts.right;
    t = Conf.place.screen_struts.top;
    b = Conf.place.screen_struts.bottom;

    lst = EwinListGetAll(&num);

    for (i = 0; i < num; i++)
    {
        ewin = lst[i];

        if (l < ewin->strut.left)
            l = ewin->strut.left;
        if (r < ewin->strut.right)
            r = ewin->strut.right;
        if (t < ewin->strut.top)
            t = ewin->strut.top;
        if (b < ewin->strut.bottom)
            b = ewin->strut.bottom;
    }

    pa->x = l;
    pa->y = t;
    pa->w = WinGetW(VROOT) - (l + r);
    pa->h = WinGetH(VROOT) - (t + b);
}

int
ScreenGetAvailableArea(int xi, int yi, Area *pa, int ignore_struts)
{
    int             head;
    Area            area;       /* Available */

    head = ScreenGetGeometry(xi, yi, pa);

    if (!ignore_struts)
    {
        _VRootGetAvailableArea(&area);
        if (pa->x < area.x)
            pa->x = area.x;
        if (pa->y < area.y)
            pa->y = area.y;
        if (pa->w > area.w)
            pa->w = area.w;
        if (pa->h > area.h)
            pa->h = area.h;

    }

    return head;
}

int
ScreenGetGeometryByPointer(Area *pa)
{
    int             pointer_x, pointer_y;

    EQueryPointer(NULL, &pointer_x, &pointer_y, NULL, NULL);

    return ScreenGetGeometry(pointer_x, pointer_y, pa);
}

int
ScreenGetAvailableAreaByPointer(Area *pa, int ignore_struts)
{
    int             pointer_x, pointer_y;

    EQueryPointer(NULL, &pointer_x, &pointer_y, NULL, NULL);

    return ScreenGetAvailableArea(pointer_x, pointer_y, pa, ignore_struts);
}
