/*
 * Copyright (c) 1999-2003 by The XFree86 Project, Inc.
 *
 * 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 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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.
 *
 * Except as contained in this notice, the name of the copyright holder(s)
 * and author(s) shall not be used in advertising or otherwise to promote
 * the sale, use or other dealings in this Software without prior written
 * authorization from the copyright holder(s) and author(s).
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include <X11/X.h>
#include "misc.h"
#include "os.h"
#include "extinit.h"

#ifdef XF86VIDMODE

#include "randrstr.h"
#include "vidmodestr.h"

#include "xwayland-screen.h"
#include "xwayland-vidmode.h"

static DevPrivateKeyRec xwlVidModePrivateKeyRec;
#define xwlVidModePrivateKey (&xwlVidModePrivateKeyRec)

/* Taken from xrandr, h sync frequency in KHz */
static double
mode_hsync(const xRRModeInfo *mode_info)
{
    double rate;

    if (mode_info->hTotal)
        rate = (double) mode_info->dotClock / (double) mode_info->hTotal;
    else
        rate = 0.0;

    return rate / 1000.0;
}

/* Taken from xrandr, v refresh frequency in Hz */
static double
mode_refresh(const xRRModeInfo *mode_info)
{
    double rate;
    double vTotal = mode_info->vTotal;

    if (mode_info->modeFlags & RR_DoubleScan)
	vTotal *= 2.0;

    if (mode_info->modeFlags & RR_Interlace)
	vTotal /= 2.0;

    if (mode_info->hTotal > 0.0 && vTotal > 0.0)
	rate = ((double) mode_info->dotClock /
		((double) mode_info->hTotal * (double) vTotal));
    else
        rate = 0.0;

    return rate;
}

static void
xwlRRModeToDisplayMode(RRModePtr rrmode, DisplayModePtr mode)
{
    const xRRModeInfo *mode_info = &rrmode->mode;

    mode->next = mode;
    mode->prev = mode;
    mode->name = "";
    mode->VScan = 1;
    mode->Private = NULL;
    mode->HDisplay = mode_info->width;
    mode->HSyncStart = mode_info->hSyncStart;
    mode->HSyncEnd = mode_info->hSyncEnd;
    mode->HTotal = mode_info->hTotal;
    mode->HSkew = mode_info->hSkew;
    mode->VDisplay = mode_info->height;
    mode->VSyncStart = mode_info->vSyncStart;
    mode->VSyncEnd = mode_info->vSyncEnd;
    mode->VTotal = mode_info->vTotal;
    mode->Flags = mode_info->modeFlags;
    mode->Clock = mode_info->dotClock / 1000.0;
    mode->VRefresh = mode_refresh(mode_info); /* Or RRVerticalRefresh() */
    mode->HSync = mode_hsync(mode_info);
}

static RRModePtr
xwlVidModeGetRRMode(ScreenPtr pScreen, int32_t width, int32_t height)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);

    if (!xwl_output)
        return NULL;

    return xwl_output_find_mode(xwl_output, width, height);
}

static RRModePtr
xwlVidModeGetCurrentRRMode(ScreenPtr pScreen)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);
    struct xwl_emulated_mode *emulated_mode;

    if (!xwl_output)
        return NULL;

    emulated_mode =
        xwl_output_get_emulated_mode_for_client(xwl_output, GetCurrentClient());

    if (emulated_mode) {
        return xwl_output_find_mode(xwl_output,
                                    emulated_mode->width,
                                    emulated_mode->height);
    } else {
        return xwl_output_find_mode(xwl_output, -1, -1);
    }
}

static Bool
xwlVidModeGetCurrentModeline(ScreenPtr pScreen, DisplayModePtr *mode, int *dotClock)
{
    DisplayModePtr pMod;
    RRModePtr rrmode;

    pMod = dixLookupPrivate(&pScreen->devPrivates, xwlVidModePrivateKey);
    if (pMod == NULL)
        return FALSE;

    rrmode = xwlVidModeGetCurrentRRMode(pScreen);
    if (rrmode == NULL)
        return FALSE;

    xwlRRModeToDisplayMode(rrmode, pMod);

    *mode = pMod;
    if (dotClock != NULL)
        *dotClock = pMod->Clock;

    return TRUE;
}

static vidMonitorValue
xwlVidModeGetMonitorValue(ScreenPtr pScreen, int valtyp, int indx)
{
    vidMonitorValue ret = { NULL, };
    RRModePtr rrmode;

    rrmode = xwlVidModeGetCurrentRRMode(pScreen);
    if (rrmode == NULL)
        return ret;

    switch (valtyp) {
    case VIDMODE_MON_VENDOR:
        ret.ptr = XVENDORNAME;
        break;
    case VIDMODE_MON_MODEL:
        ret.ptr = "XWAYLAND";
        break;
    case VIDMODE_MON_NHSYNC:
        ret.i = 1;
        break;
    case VIDMODE_MON_NVREFRESH:
        ret.i = 1;
        break;
    case VIDMODE_MON_HSYNC_LO:
    case VIDMODE_MON_HSYNC_HI:
        ret.f = mode_hsync(&rrmode->mode) * 100.0;
        break;
    case VIDMODE_MON_VREFRESH_LO:
    case VIDMODE_MON_VREFRESH_HI:
        ret.f = mode_refresh(&rrmode->mode) * 100.0;
        break;
    }
    return ret;
}

static int
xwlVidModeGetDotClock(ScreenPtr pScreen, int Clock)
{
    return Clock;
}

static int
xwlVidModeGetNumOfClocks(ScreenPtr pScreen, Bool *progClock)
{
    /* We emulate a programmable clock, rather then a fixed set of clocks */
    *progClock = TRUE;
    return 0;
}

static Bool
xwlVidModeGetClocks(ScreenPtr pScreen, int *Clocks)
{
    return FALSE; /* Programmable clock, no clock list */
}

/* GetFirstModeline and GetNextModeline are used from Xext/vidmode.c like this:
 *  if (pVidMode->GetFirstModeline(pScreen, &mode, &dotClock)) {
 *      do {
 *          ...
 *          if (...)
 *              break;
 *      } while (pVidMode->GetNextModeline(pScreen, &mode, &dotClock));
 *  }
 * IOW our caller basically always loops over all the modes. There never is a
 * return to the mainloop between GetFirstModeline and NextModeline calls where
 * other parts of the server may change our state so we do not need to worry
 * about xwl_output->randr_output->modes changing underneath us.
 * Thus we can simply implement these two callbacks by storing the enumeration
 * index in pVidMode->Next.
 */

static Bool
xwlVidModeGetNextModeline(ScreenPtr pScreen, DisplayModePtr *mode, int *dotClock)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);
    VidModePtr pVidMode;
    DisplayModePtr pMod;
    intptr_t index;

    pMod = dixLookupPrivate(&pScreen->devPrivates, xwlVidModePrivateKey);
    pVidMode = VidModeGetPtr(pScreen);
    if (xwl_output == NULL || pMod == NULL || pVidMode == NULL)
        return FALSE;

    index = (intptr_t)pVidMode->Next;
    if (index >= xwl_output->randr_output->numModes)
        return FALSE;
    xwlRRModeToDisplayMode(xwl_output->randr_output->modes[index], pMod);
    index++;
    pVidMode->Next = (void *)index;

    *mode = pMod;
    if (dotClock != NULL)
        *dotClock = pMod->Clock;

    return TRUE;
}

static Bool
xwlVidModeGetFirstModeline(ScreenPtr pScreen, DisplayModePtr *mode, int *dotClock)
{
    VidModePtr pVidMode;
    intptr_t index = 0;

    pVidMode = VidModeGetPtr(pScreen);
    if (pVidMode == NULL)
        return FALSE;

    pVidMode->Next = (void *)index; /* 0 */
    return xwlVidModeGetNextModeline(pScreen, mode, dotClock);
}

static Bool
xwlVidModeDeleteModeline(ScreenPtr pScreen, DisplayModePtr mode)
{
    /* Unsupported */
    return FALSE;
}

static Bool
xwlVidModeZoomViewport(ScreenPtr pScreen, int zoom)
{
    /* Support only no zoom */
    return (zoom == 1);
}

static Bool
xwlVidModeSetViewPort(ScreenPtr pScreen, int x, int y)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);

    if (!xwl_output)
        return FALSE;

    /* Support only default viewport */
    return (x == xwl_output->x && y == xwl_output->y);
}

static Bool
xwlVidModeGetViewPort(ScreenPtr pScreen, int *x, int *y)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);

    if (!xwl_output)
        return FALSE;

    *x = xwl_output->x;
    *y = xwl_output->y;

    return TRUE;
}

static Bool
xwlVidModeSwitchMode(ScreenPtr pScreen, DisplayModePtr mode)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);
    RRModePtr rrmode;

    if (!xwl_output)
        return FALSE;

    rrmode = xwl_output_find_mode(xwl_output, mode->HDisplay, mode->VDisplay);
    if (rrmode == NULL)
        return FALSE;

    xwl_output_set_emulated_mode(xwl_output, GetCurrentClient(), rrmode, TRUE);
    return TRUE;
}

static Bool
xwlVidModeLockZoom(ScreenPtr pScreen, Bool lock)
{
    /* Unsupported for now, but pretend it works */
    return TRUE;
}

static ModeStatus
xwlVidModeCheckModeForMonitor(ScreenPtr pScreen, DisplayModePtr mode)
{
    RRModePtr rrmode;

    rrmode = xwlVidModeGetRRMode(pScreen, mode->HDisplay, mode->VDisplay);
    if (rrmode == NULL)
        return MODE_ERROR;

    /* Only support mode with the same HSync/VRefresh as we advertise */
    if (mode->HSync == mode_hsync(&rrmode->mode) &&
        mode->VRefresh == mode_refresh(&rrmode->mode))
        return MODE_OK;

    /* All the rest is unsupported - If we want to succeed, return MODE_OK instead */
    return MODE_ONE_SIZE;
}

static ModeStatus
xwlVidModeCheckModeForDriver(ScreenPtr pScreen, DisplayModePtr mode)
{
    RRModePtr rrmode;

    rrmode = xwlVidModeGetRRMode(pScreen, mode->HDisplay, mode->VDisplay);
    return rrmode ? MODE_OK : MODE_ERROR;
}

static void
xwlVidModeSetCrtcForMode(ScreenPtr pScreen, DisplayModePtr mode)
{
    /* Unsupported */
    return;
}

static Bool
xwlVidModeAddModeline(ScreenPtr pScreen, DisplayModePtr mode)
{
    /* Unsupported */
    return FALSE;
}

static int
xwlVidModeGetNumOfModes(ScreenPtr pScreen)
{
    struct xwl_screen *xwl_screen = xwl_screen_get(pScreen);
    struct xwl_output *xwl_output = xwl_screen_get_first_output(xwl_screen);

    return xwl_output ? xwl_output->randr_output->numModes : 0;
}

static Bool
xwlVidModeSetGamma(ScreenPtr pScreen, float red, float green, float blue)
{
    /* Unsupported for now, but pretend it works */
    return TRUE;
}

static Bool
xwlVidModeGetGamma(ScreenPtr pScreen, float *red, float *green, float *blue)
{
    /* Unsupported for now, but pretend it works */
    *red = *green = *blue = 1.0f;
    return TRUE;
}

static Bool
xwlVidModeSetGammaRamp(ScreenPtr pScreen, int size, CARD16 *r, CARD16 *g, CARD16 *b)
{
    /* Unsupported for now */
    return FALSE;
}

static Bool
xwlVidModeGetGammaRamp(ScreenPtr pScreen, int size, CARD16 *r, CARD16 *g, CARD16 *b)
{
    /* Unsupported for now */
    return FALSE;
}

static int
xwlVidModeGetGammaRampSize(ScreenPtr pScreen)
{
    /* Unsupported for now */
    return 0;
}

static Bool
xwlVidModeInit(ScreenPtr pScreen)
{
    VidModePtr pVidMode = NULL;

    pVidMode = VidModeInit(pScreen);
    if (!pVidMode)
        return FALSE;

    pVidMode->Flags = 0;
    pVidMode->Next = NULL;

    pVidMode->GetMonitorValue = xwlVidModeGetMonitorValue;
    pVidMode->GetCurrentModeline = xwlVidModeGetCurrentModeline;
    pVidMode->GetFirstModeline = xwlVidModeGetFirstModeline;
    pVidMode->GetNextModeline = xwlVidModeGetNextModeline;
    pVidMode->DeleteModeline = xwlVidModeDeleteModeline;
    pVidMode->ZoomViewport = xwlVidModeZoomViewport;
    pVidMode->GetViewPort = xwlVidModeGetViewPort;
    pVidMode->SetViewPort = xwlVidModeSetViewPort;
    pVidMode->SwitchMode = xwlVidModeSwitchMode;
    pVidMode->LockZoom = xwlVidModeLockZoom;
    pVidMode->GetNumOfClocks = xwlVidModeGetNumOfClocks;
    pVidMode->GetClocks = xwlVidModeGetClocks;
    pVidMode->CheckModeForMonitor = xwlVidModeCheckModeForMonitor;
    pVidMode->CheckModeForDriver = xwlVidModeCheckModeForDriver;
    pVidMode->SetCrtcForMode = xwlVidModeSetCrtcForMode;
    pVidMode->AddModeline = xwlVidModeAddModeline;
    pVidMode->GetDotClock = xwlVidModeGetDotClock;
    pVidMode->GetNumOfModes = xwlVidModeGetNumOfModes;
    pVidMode->SetGamma = xwlVidModeSetGamma;
    pVidMode->GetGamma = xwlVidModeGetGamma;
    pVidMode->SetGammaRamp = xwlVidModeSetGammaRamp;
    pVidMode->GetGammaRamp = xwlVidModeGetGammaRamp;
    pVidMode->GetGammaRampSize = xwlVidModeGetGammaRampSize;

    return TRUE;
}

void
xwlVidModeExtensionInit(void)
{
    int i;
    Bool enabled = FALSE;

    for (i = 0; i < screenInfo.numScreens; i++) {
        if (xwlVidModeInit (screenInfo.screens[i]))
            enabled = TRUE;
    }
    /* This means that the DDX doesn't want the vidmode extension enabled */
    if (!enabled)
        return;

    if (!dixRegisterPrivateKey(xwlVidModePrivateKey, PRIVATE_SCREEN,
                               sizeof(DisplayModeRec)))
        return;

    VidModeAddExtension(FALSE);
}

#endif                          /* XF86VIDMODE */
