/*
 * Copyright © 2000 Compaq Computer Corporation
 * Copyright © 2002 Hewlett-Packard Company
 * Copyright © 2006 Intel Corporation
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 *
 * Author:  Jim Gettys, Hewlett-Packard Company, Inc.
 *	    Keith Packard, Intel Corporation
 */

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

#include "randrstr.h"

/* From render.h */
#ifndef SubPixelUnknown
#define SubPixelUnknown 0
#endif

#define RR_VALIDATE
static int	RRNScreens;

#define wrap(priv,real,mem,func) {\
    priv->mem = real->mem; \
    real->mem = func; \
}

#define unwrap(priv,real,mem) {\
    real->mem = priv->mem; \
}

static int ProcRRDispatch (ClientPtr pClient);
static int SProcRRDispatch (ClientPtr pClient);

int	RREventBase;
int	RRErrorBase;
RESTYPE RRClientType, RREventType; /* resource types for event masks */
static int RRClientPrivateKeyIndex;
DevPrivateKey RRClientPrivateKey = &RRClientPrivateKeyIndex;

static int rrPrivKeyIndex;
DevPrivateKey rrPrivKey = &rrPrivKeyIndex;

static void
RRClientCallback (CallbackListPtr	*list,
		  pointer		closure,
		  pointer		data)
{
    NewClientInfoRec	*clientinfo = (NewClientInfoRec *) data;
    ClientPtr		pClient = clientinfo->client;
    rrClientPriv(pClient);
    RRTimesPtr		pTimes = (RRTimesPtr) (pRRClient + 1);
    int			i;

    pRRClient->major_version = 0;
    pRRClient->minor_version = 0;
    for (i = 0; i < screenInfo.numScreens; i++)
    {
	ScreenPtr   pScreen = screenInfo.screens[i];
	rrScrPriv(pScreen);

	if (pScrPriv)
	{
	    pTimes[i].setTime = pScrPriv->lastSetTime;
	    pTimes[i].configTime = pScrPriv->lastConfigTime;
	}
    }
}

static Bool
RRCloseScreen (int i, ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    int		    j;

    unwrap (pScrPriv, pScreen, CloseScreen);
    for (j = pScrPriv->numCrtcs - 1; j >= 0; j--)
	RRCrtcDestroy (pScrPriv->crtcs[j]);
    for (j = pScrPriv->numOutputs - 1; j >= 0; j--)
	RROutputDestroy (pScrPriv->outputs[j]);
    
    xfree (pScrPriv->crtcs);
    xfree (pScrPriv->outputs);
    xfree (pScrPriv);
    RRNScreens -= 1;	/* ok, one fewer screen with RandR running */
    return (*pScreen->CloseScreen) (i, pScreen);    
}

static void
SRRScreenChangeNotifyEvent(xRRScreenChangeNotifyEvent *from,
			   xRRScreenChangeNotifyEvent *to)
{
    to->type = from->type;
    to->rotation = from->rotation;
    cpswaps(from->sequenceNumber, to->sequenceNumber);
    cpswapl(from->timestamp, to->timestamp);
    cpswapl(from->configTimestamp, to->configTimestamp);
    cpswapl(from->root, to->root);
    cpswapl(from->window, to->window);
    cpswaps(from->sizeID, to->sizeID);
    cpswaps(from->subpixelOrder, to->subpixelOrder);
    cpswaps(from->widthInPixels, to->widthInPixels);
    cpswaps(from->heightInPixels, to->heightInPixels);
    cpswaps(from->widthInMillimeters, to->widthInMillimeters);
    cpswaps(from->heightInMillimeters, to->heightInMillimeters);
}

static void
SRRCrtcChangeNotifyEvent(xRRCrtcChangeNotifyEvent *from,
			 xRRCrtcChangeNotifyEvent *to)
{
    to->type = from->type;
    to->subCode = from->subCode;
    cpswaps(from->sequenceNumber, to->sequenceNumber);
    cpswapl(from->timestamp, to->timestamp);
    cpswapl(from->window, to->window);
    cpswapl(from->crtc, to->crtc);
    cpswapl(from->mode, to->mode);
    cpswaps(from->rotation, to->rotation);
    /* pad1 */
    cpswaps(from->x, to->x);
    cpswaps(from->y, to->y);
    cpswaps(from->width, to->width);
    cpswaps(from->height, to->height);
}

static void
SRROutputChangeNotifyEvent(xRROutputChangeNotifyEvent *from,
			   xRROutputChangeNotifyEvent *to)
{
    to->type = from->type;
    to->subCode = from->subCode;
    cpswaps(from->sequenceNumber, to->sequenceNumber);
    cpswapl(from->timestamp, to->timestamp);
    cpswapl(from->configTimestamp, to->configTimestamp);
    cpswapl(from->window, to->window);
    cpswapl(from->output, to->output);
    cpswapl(from->crtc, to->crtc);
    cpswapl(from->mode, to->mode);
    cpswaps(from->rotation, to->rotation);
    to->connection = from->connection;
    to->subpixelOrder = from->subpixelOrder;
}

static void
SRROutputPropertyNotifyEvent(xRROutputPropertyNotifyEvent *from,
			     xRROutputPropertyNotifyEvent *to)
{
    to->type = from->type;
    to->subCode = from->subCode;
    cpswaps(from->sequenceNumber, to->sequenceNumber);
    cpswapl(from->window, to->window);
    cpswapl(from->output, to->output);
    cpswapl(from->atom, to->atom);
    cpswapl(from->timestamp, to->timestamp);
    to->state = from->state;
    /* pad1 */
    /* pad2 */
    /* pad3 */
    /* pad4 */
}

static void
SRRNotifyEvent (xEvent *from,
		xEvent *to)
{
    switch (from->u.u.detail) {
    case RRNotify_CrtcChange:
	SRRCrtcChangeNotifyEvent ((xRRCrtcChangeNotifyEvent *) from,
				  (xRRCrtcChangeNotifyEvent *) to);
	break;
    case RRNotify_OutputChange:
	SRROutputChangeNotifyEvent ((xRROutputChangeNotifyEvent *) from,
				    (xRROutputChangeNotifyEvent *) to);
	break;
    case RRNotify_OutputProperty:
	SRROutputPropertyNotifyEvent ((xRROutputPropertyNotifyEvent *) from,
				      (xRROutputPropertyNotifyEvent *) to);
	break;
    default:
	break;
    }
}

static int RRGeneration;

Bool RRInit (void)
{
    if (RRGeneration != serverGeneration)
    {
	if (!RRModeInit ())
	    return FALSE;
	if (!RRCrtcInit ())
	    return FALSE;
	if (!RROutputInit ())
	    return FALSE;
	RRGeneration = serverGeneration;
    }
    return TRUE;
}

Bool RRScreenInit(ScreenPtr pScreen)
{
    rrScrPrivPtr   pScrPriv;

    if (!RRInit ())
	return FALSE;

    pScrPriv = (rrScrPrivPtr) xcalloc (1, sizeof (rrScrPrivRec));
    if (!pScrPriv)
	return FALSE;

    SetRRScreen(pScreen, pScrPriv);

    /*
     * Calling function best set these function vectors
     */
    pScrPriv->rrGetInfo = 0;
    pScrPriv->maxWidth = pScrPriv->minWidth = pScreen->width;
    pScrPriv->maxHeight = pScrPriv->minHeight = pScreen->height;
    
    pScrPriv->width = pScreen->width;
    pScrPriv->height = pScreen->height;
    pScrPriv->mmWidth = pScreen->mmWidth;
    pScrPriv->mmHeight = pScreen->mmHeight;
#if RANDR_12_INTERFACE
    pScrPriv->rrScreenSetSize = NULL;
    pScrPriv->rrCrtcSet = NULL;
    pScrPriv->rrCrtcSetGamma = NULL;
#endif
#if RANDR_10_INTERFACE    
    pScrPriv->rrSetConfig = 0;
    pScrPriv->rotations = RR_Rotate_0;
    pScrPriv->reqWidth = pScreen->width;
    pScrPriv->reqHeight = pScreen->height;
    pScrPriv->nSizes = 0;
    pScrPriv->pSizes = NULL;
    pScrPriv->rotation = RR_Rotate_0;
    pScrPriv->rate = 0;
    pScrPriv->size = 0;
#endif
    
    /*
     * This value doesn't really matter -- any client must call
     * GetScreenInfo before reading it which will automatically update
     * the time
     */
    pScrPriv->lastSetTime = currentTime;
    pScrPriv->lastConfigTime = currentTime;
    
    wrap (pScrPriv, pScreen, CloseScreen, RRCloseScreen);

    pScrPriv->numOutputs = 0;
    pScrPriv->outputs = NULL;
    pScrPriv->numCrtcs = 0;
    pScrPriv->crtcs = NULL;
    
    RRNScreens += 1;	/* keep count of screens that implement randr */
    return TRUE;
}

/*ARGSUSED*/
static int
RRFreeClient (pointer data, XID id)
{
    RREventPtr   pRREvent;
    WindowPtr	    pWin;
    RREventPtr   *pHead, pCur, pPrev;

    pRREvent = (RREventPtr) data;
    pWin = pRREvent->window;
    dixLookupResourceByType((pointer *)&pHead, pWin->drawable.id,
			    RREventType, serverClient, DixDestroyAccess);
    if (pHead) {
	pPrev = 0;
	for (pCur = *pHead; pCur && pCur != pRREvent; pCur=pCur->next)
	    pPrev = pCur;
	if (pCur)
	{
	    if (pPrev)
	    	pPrev->next = pRREvent->next;
	    else
	    	*pHead = pRREvent->next;
	}
    }
    xfree ((pointer) pRREvent);
    return 1;
}

/*ARGSUSED*/
static int
RRFreeEvents (pointer data, XID id)
{
    RREventPtr   *pHead, pCur, pNext;

    pHead = (RREventPtr *) data;
    for (pCur = *pHead; pCur; pCur = pNext) {
	pNext = pCur->next;
	FreeResource (pCur->clientResource, RRClientType);
	xfree ((pointer) pCur);
    }
    xfree ((pointer) pHead);
    return 1;
}

void
RRExtensionInit (void)
{
    ExtensionEntry *extEntry;

    if (RRNScreens == 0) return;

    if (!dixRequestPrivate(RRClientPrivateKey,
				sizeof (RRClientRec) +
				screenInfo.numScreens * sizeof (RRTimesRec)))
	return;
    if (!AddCallback (&ClientStateCallback, RRClientCallback, 0))
	return;

    RRClientType = CreateNewResourceType(RRFreeClient, "RandRClient");
    if (!RRClientType)
	return;
    RREventType = CreateNewResourceType(RRFreeEvents, "RandREvent");
    if (!RREventType)
	return;
    extEntry = AddExtension (RANDR_NAME, RRNumberEvents, RRNumberErrors,
			     ProcRRDispatch, SProcRRDispatch,
			     NULL, StandardMinorOpcode);
    if (!extEntry)
	return;
    RRErrorBase = extEntry->errorBase;
    RREventBase = extEntry->eventBase;
    EventSwapVector[RREventBase + RRScreenChangeNotify] = (EventSwapPtr) 
	SRRScreenChangeNotifyEvent;
    EventSwapVector[RREventBase + RRNotify] = (EventSwapPtr)
	SRRNotifyEvent;
#ifdef PANORAMIX
    RRXineramaExtensionInit();
#endif
}

static int
TellChanged (WindowPtr pWin, pointer value)
{
    RREventPtr			*pHead, pRREvent;
    ClientPtr			client;
    ScreenPtr			pScreen = pWin->drawable.pScreen;
    rrScrPriv(pScreen);
    int				i;

    dixLookupResourceByType((pointer *)&pHead, pWin->drawable.id,
			    RREventType, serverClient, DixReadAccess);
    if (!pHead)
	return WT_WALKCHILDREN;

    for (pRREvent = *pHead; pRREvent; pRREvent = pRREvent->next) 
    {
	client = pRREvent->client;
	if (client == serverClient || client->clientGone)
	    continue;

	if (pRREvent->mask & RRScreenChangeNotifyMask)
	    RRDeliverScreenEvent (client, pWin, pScreen);
	
	if (pRREvent->mask & RRCrtcChangeNotifyMask)
	{
	    for (i = 0; i < pScrPriv->numCrtcs; i++)
	    {
		RRCrtcPtr   crtc = pScrPriv->crtcs[i];
		if (crtc->changed)
		    RRDeliverCrtcEvent (client, pWin, crtc);
	    }
	}
	
	if (pRREvent->mask & RROutputChangeNotifyMask)
	{
	    for (i = 0; i < pScrPriv->numOutputs; i++)
	    {
		RROutputPtr   output = pScrPriv->outputs[i];
		if (output->changed)
		    RRDeliverOutputEvent (client, pWin, output);
	    }
	}
    }
    return WT_WALKCHILDREN;
}

/*
 * Something changed; send events and adjust pointer position
 */
void
RRTellChanged (ScreenPtr pScreen)
{
    rrScrPriv (pScreen);
    int i;
    
    if (pScrPriv->changed)
    {
	UpdateCurrentTime ();
	if (pScrPriv->configChanged)
	{
	    pScrPriv->lastConfigTime = currentTime;
	    pScrPriv->configChanged = FALSE;
	}
	pScrPriv->changed = FALSE;
	WalkTree (pScreen, TellChanged, (pointer) pScreen);
	for (i = 0; i < pScrPriv->numOutputs; i++)
	    pScrPriv->outputs[i]->changed = FALSE;
	for (i = 0; i < pScrPriv->numCrtcs; i++)
	    pScrPriv->crtcs[i]->changed = FALSE;
	if (pScrPriv->layoutChanged)
	{
	    pScrPriv->layoutChanged = FALSE;
	    RRPointerScreenConfigured (pScreen);
	    RRSendConfigNotify (pScreen);
	}
    }
}

/*
 * Return the first output which is connected to an active CRTC
 * Used in emulating 1.0 behaviour
 */
RROutputPtr
RRFirstOutput (ScreenPtr pScreen)
{
    rrScrPriv(pScreen);
    RROutputPtr		    output;
    int	i, j;
    
    if (pScrPriv->primaryOutput && pScrPriv->primaryOutput->crtc)
	return pScrPriv->primaryOutput;

    for (i = 0; i < pScrPriv->numCrtcs; i++)
    {
	RRCrtcPtr   crtc = pScrPriv->crtcs[i];
	for (j = 0; j < pScrPriv->numOutputs; j++)
	{
	    output = pScrPriv->outputs[j];
	    if (output->crtc == crtc)
		return output;
	}
    }
    return NULL;
}

CARD16
RRVerticalRefresh (xRRModeInfo *mode)
{
    CARD32  refresh;
    CARD32  dots = mode->hTotal * mode->vTotal;
    if (!dots)
	return 0;
    refresh = (mode->dotClock + dots/2) / dots;
    if (refresh > 0xffff)
	refresh = 0xffff;
    return (CARD16) refresh;
}

static int
ProcRRDispatch (ClientPtr client)
{
    REQUEST(xReq);
    if (stuff->data >= RRNumberRequests || !ProcRandrVector[stuff->data])
	return BadRequest;
    return (*ProcRandrVector[stuff->data]) (client);
}

static int
SProcRRDispatch (ClientPtr client)
{
    REQUEST(xReq);
    if (stuff->data >= RRNumberRequests || !ProcRandrVector[stuff->data])
	return BadRequest;
    return (*SProcRandrVector[stuff->data]) (client);
}