/*
 * Copyright © 2006 Keith Packard
 * Copyright © 2008 Red Hat, Inc.
 *
 * 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.
 */

#include "randrstr.h"

RESTYPE	RROutputType;

/*
 * Notify the output of some change
 */
void
RROutputChanged (RROutputPtr output, Bool configChanged)
{
    ScreenPtr	pScreen = output->pScreen;
    
    output->changed = TRUE;
    if (pScreen)
    {
	rrScrPriv (pScreen);
	pScrPriv->changed = TRUE;
	if (configChanged)
	    pScrPriv->configChanged = TRUE;
    }
}

/*
 * Create an output
 */

RROutputPtr
RROutputCreate (ScreenPtr   pScreen,
		const char  *name,
		int	    nameLength,
		void	    *devPrivate)
{
    RROutputPtr	    output;
    RROutputPtr	    *outputs;
    rrScrPrivPtr    pScrPriv;

    if (!RRInit())
	return NULL;
    
    pScrPriv = rrGetScrPriv(pScreen);

    if (pScrPriv->numOutputs)
	outputs = realloc(pScrPriv->outputs,
			    (pScrPriv->numOutputs + 1) * sizeof (RROutputPtr));
    else
	outputs = malloc(sizeof (RROutputPtr));
    if (!outputs)
	return FALSE;

    pScrPriv->outputs = outputs;
    
    output = malloc(sizeof (RROutputRec) + nameLength + 1);
    if (!output)
	return NULL;
    output->id = FakeClientID (0);
    output->pScreen = pScreen;
    output->name = (char *) (output + 1);
    output->nameLength = nameLength;
    memcpy (output->name, name, nameLength);
    output->name[nameLength] = '\0';
    output->connection = RR_UnknownConnection;
    output->subpixelOrder = SubPixelUnknown;
    output->mmWidth = 0;
    output->mmHeight = 0;
    output->crtc = NULL;
    output->numCrtcs = 0;
    output->crtcs = NULL;
    output->numClones = 0;
    output->clones = NULL;
    output->numModes = 0;
    output->numPreferred = 0;
    output->modes = NULL;
    output->numUserModes = 0;
    output->userModes = NULL;
    output->properties = NULL;
    output->pendingProperties = FALSE;
    output->changed = FALSE;
    output->devPrivate = devPrivate;
    
    if (!AddResource (output->id, RROutputType, (pointer) output))
	return NULL;

    pScrPriv->outputs[pScrPriv->numOutputs++] = output;
    return output;
}

/*
 * Notify extension that output parameters have been changed
 */
Bool
RROutputSetClones (RROutputPtr  output,
		   RROutputPtr  *clones,
		   int		numClones)
{
    RROutputPtr	*newClones;
    int		i;

    if (numClones == output->numClones)
    {
	for (i = 0; i < numClones; i++)
	    if (output->clones[i] != clones[i])
		break;
	if (i == numClones)
	    return TRUE;
    }
    if (numClones)
    {
	newClones = malloc(numClones * sizeof (RROutputPtr));
	if (!newClones)
	    return FALSE;
    }
    else
	newClones = NULL;
    free(output->clones);
    memcpy (newClones, clones, numClones * sizeof (RROutputPtr));
    output->clones = newClones;
    output->numClones = numClones;
    RROutputChanged (output, TRUE);
    return TRUE;
}

Bool
RROutputSetModes (RROutputPtr	output,
		  RRModePtr	*modes,
		  int		numModes,
		  int		numPreferred)
{
    RRModePtr	*newModes;
    int		i;

    if (numModes == output->numModes && numPreferred == output->numPreferred)
    {
	for (i = 0; i < numModes; i++)
	    if (output->modes[i] != modes[i])
		break;
	if (i == numModes)
	{
	    for (i = 0; i < numModes; i++)
		RRModeDestroy (modes[i]);
	    return TRUE;
	}
    }

    if (numModes)
    {
	newModes = malloc(numModes * sizeof (RRModePtr));
	if (!newModes)
	    return FALSE;
    }
    else
	newModes = NULL;
    if (output->modes)
    {
	for (i = 0; i < output->numModes; i++)
	    RRModeDestroy (output->modes[i]);
	free(output->modes);
    }
    memcpy (newModes, modes, numModes * sizeof (RRModePtr));
    output->modes = newModes;
    output->numModes = numModes;
    output->numPreferred = numPreferred;
    RROutputChanged (output, TRUE);
    return TRUE;
}

int
RROutputAddUserMode (RROutputPtr    output,
		     RRModePtr	    mode)
{
    int		m;
    ScreenPtr	pScreen = output->pScreen;
    rrScrPriv(pScreen);
    RRModePtr	*newModes;

    /* Check to see if this mode is already listed for this output */
    for (m = 0; m < output->numModes + output->numUserModes; m++)
    {
	RRModePtr   e = (m < output->numModes ?
			 output->modes[m] :
			 output->userModes[m - output->numModes]);
	if (mode == e)
	    return Success;
    }

    /* Check with the DDX to see if this mode is OK */
    if (pScrPriv->rrOutputValidateMode)
	if (!pScrPriv->rrOutputValidateMode (pScreen, output, mode))
	    return BadMatch;

    if (output->userModes)
	newModes = realloc(output->userModes,
			     (output->numUserModes + 1) * sizeof (RRModePtr));
    else
	newModes = malloc(sizeof (RRModePtr));
    if (!newModes)
	return BadAlloc;

    output->userModes = newModes;
    output->userModes[output->numUserModes++] = mode;
    ++mode->refcnt;
    RROutputChanged (output, TRUE);
    RRTellChanged (pScreen);
    return Success;
}

int
RROutputDeleteUserMode (RROutputPtr output,
			RRModePtr   mode)
{
    int		m;
    
    /* Find this mode in the user mode list */
    for (m = 0; m < output->numUserModes; m++)
    {
	RRModePtr   e = output->userModes[m];

	if (mode == e)
	    break;
    }
    /* Not there, access error */
    if (m == output->numUserModes)
	return BadAccess;

    /* make sure the mode isn't active for this output */
    if (output->crtc && output->crtc->mode == mode)
	return BadMatch;

    memmove (output->userModes + m, output->userModes + m + 1,
	     (output->numUserModes - m - 1) * sizeof (RRModePtr));
    output->numUserModes--;
    RRModeDestroy (mode);
    return Success;
}

Bool
RROutputSetCrtcs (RROutputPtr	output,
		  RRCrtcPtr	*crtcs,
		  int		numCrtcs)
{
    RRCrtcPtr	*newCrtcs;
    int		i;

    if (numCrtcs == output->numCrtcs)
    {
	for (i = 0; i < numCrtcs; i++)
	    if (output->crtcs[i] != crtcs[i])
		break;
	if (i == numCrtcs)
	    return TRUE;
    }
    if (numCrtcs)
    {
	newCrtcs = malloc(numCrtcs * sizeof (RRCrtcPtr));
	if (!newCrtcs)
	    return FALSE;
    }
    else
	newCrtcs = NULL;
    free(output->crtcs);
    memcpy (newCrtcs, crtcs, numCrtcs * sizeof (RRCrtcPtr));
    output->crtcs = newCrtcs;
    output->numCrtcs = numCrtcs;
    RROutputChanged (output, TRUE);
    return TRUE;
}

Bool
RROutputSetConnection (RROutputPtr  output,
		       CARD8	    connection)
{
    if (output->connection == connection)
	return TRUE;
    output->connection = connection;
    RROutputChanged (output, TRUE);
    return TRUE;
}

Bool
RROutputSetSubpixelOrder (RROutputPtr output,
			  int	      subpixelOrder)
{
    if (output->subpixelOrder == subpixelOrder)
	return TRUE;

    output->subpixelOrder = subpixelOrder;
    RROutputChanged (output, FALSE);
    return TRUE;
}

Bool
RROutputSetPhysicalSize (RROutputPtr	output,
			 int		mmWidth,
			 int		mmHeight)
{
    if (output->mmWidth == mmWidth && output->mmHeight == mmHeight)
	return TRUE;
    output->mmWidth = mmWidth;
    output->mmHeight = mmHeight;
    RROutputChanged (output, FALSE);
    return TRUE;
}


void
RRDeliverOutputEvent(ClientPtr client, WindowPtr pWin, RROutputPtr output)
{
    ScreenPtr pScreen = pWin->drawable.pScreen;
    rrScrPriv (pScreen);
    xRROutputChangeNotifyEvent	oe;
    RRCrtcPtr	crtc = output->crtc;
    RRModePtr	mode = crtc ? crtc->mode : 0;
    
    oe.type = RRNotify + RREventBase;
    oe.subCode = RRNotify_OutputChange;
    oe.timestamp = pScrPriv->lastSetTime.milliseconds;
    oe.configTimestamp = pScrPriv->lastConfigTime.milliseconds;
    oe.window = pWin->drawable.id;
    oe.output = output->id;
    if (crtc)
    {
	oe.crtc = crtc->id;
	oe.mode = mode ? mode->mode.id : None;
	oe.rotation = crtc->rotation;
    }
    else
    {
	oe.crtc = None;
	oe.mode = None;
	oe.rotation = RR_Rotate_0;
    }
    oe.connection = output->connection;
    oe.subpixelOrder = output->subpixelOrder;
    WriteEventsToClient (client, 1, (xEvent *) &oe);
}

/*
 * Destroy a Output at shutdown
 */
void
RROutputDestroy (RROutputPtr output)
{
    FreeResource (output->id, 0);
}

static int
RROutputDestroyResource (pointer value, XID pid)
{
    RROutputPtr	output = (RROutputPtr) value;
    ScreenPtr	pScreen = output->pScreen;
    int		m;

    if (pScreen)
    {
	rrScrPriv(pScreen);
	int		i;

	if (pScrPriv->primaryOutput == output)
	    pScrPriv->primaryOutput = NULL;
    
	for (i = 0; i < pScrPriv->numOutputs; i++)
	{
	    if (pScrPriv->outputs[i] == output)
	    {
		memmove (pScrPriv->outputs + i, pScrPriv->outputs + i + 1,
			 (pScrPriv->numOutputs - (i + 1)) * sizeof (RROutputPtr));
		--pScrPriv->numOutputs;
		break;
	    }
	}
    }
    if (output->modes)
    {
	for (m = 0; m < output->numModes; m++)
	    RRModeDestroy (output->modes[m]);
	free(output->modes);
    }
    
    for (m = 0; m < output->numUserModes; m++)
	RRModeDestroy (output->userModes[m]);
    free(output->userModes);

    free(output->crtcs);
    free(output->clones);
    RRDeleteAllOutputProperties (output);
    free(output);
    return 1;
}

/*
 * Initialize output type
 */
Bool
RROutputInit (void)
{
    RROutputType = CreateNewResourceType (RROutputDestroyResource, "OUTPUT");
    if (!RROutputType)
	return FALSE;

    return TRUE;
}

/*
 * Initialize output type error value
 */
void
RROutputInitErrorValue(void)
{
    SetResourceTypeErrorValue(RROutputType, RRErrorBase + BadRROutput);
}

#define OutputInfoExtra	(SIZEOF(xRRGetOutputInfoReply) - 32)

int
ProcRRGetOutputInfo (ClientPtr client)
{
    REQUEST(xRRGetOutputInfoReq);
    xRRGetOutputInfoReply	rep;
    RROutputPtr			output;
    CARD8			*extra;
    unsigned long		extraLen;
    ScreenPtr			pScreen;
    rrScrPrivPtr		pScrPriv;
    RRCrtc			*crtcs;
    RRMode			*modes;
    RROutput			*clones;
    char			*name;
    int				i;
    
    REQUEST_SIZE_MATCH(xRRGetOutputInfoReq);
    VERIFY_RR_OUTPUT(stuff->output, output, DixReadAccess);

    pScreen = output->pScreen;
    pScrPriv = rrGetScrPriv(pScreen);

    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.length = bytes_to_int32(OutputInfoExtra);
    rep.timestamp = pScrPriv->lastSetTime.milliseconds;
    rep.crtc = output->crtc ? output->crtc->id : None;
    rep.mmWidth = output->mmWidth;
    rep.mmHeight = output->mmHeight;
    rep.connection = output->connection;
    rep.subpixelOrder = output->subpixelOrder;
    rep.nCrtcs = output->numCrtcs;
    rep.nModes = output->numModes + output->numUserModes;
    rep.nPreferred = output->numPreferred;
    rep.nClones = output->numClones;
    rep.nameLength = output->nameLength;
    
    extraLen = ((output->numCrtcs +
		 output->numModes + output->numUserModes +
		 output->numClones +
		 bytes_to_int32(rep.nameLength)) << 2);

    if (extraLen)
    {
	rep.length += bytes_to_int32(extraLen);
	extra = malloc(extraLen);
	if (!extra)
	    return BadAlloc;
    }
    else
	extra = NULL;

    crtcs = (RRCrtc *) extra;
    modes = (RRMode *) (crtcs + output->numCrtcs);
    clones = (RROutput *) (modes + output->numModes + output->numUserModes);
    name = (char *) (clones + output->numClones);
    
    for (i = 0; i < output->numCrtcs; i++)
    {
	crtcs[i] = output->crtcs[i]->id;
	if (client->swapped)
	    swapl(&crtcs[i]);
    }
    for (i = 0; i < output->numModes + output->numUserModes; i++)
    {
	if (i < output->numModes)
	    modes[i] = output->modes[i]->mode.id;
	else
	    modes[i] = output->userModes[i - output->numModes]->mode.id;
	if (client->swapped)
	    swapl(&modes[i]);
    }
    for (i = 0; i < output->numClones; i++)
    {
	clones[i] = output->clones[i]->id;
	if (client->swapped)
	    swapl(&clones[i]);
    }
    memcpy (name, output->name, output->nameLength);
    if (client->swapped) {
	swaps(&rep.sequenceNumber);
	swapl(&rep.length);
	swapl(&rep.timestamp);
	swapl(&rep.crtc);
	swapl(&rep.mmWidth);
	swapl(&rep.mmHeight);
	swaps(&rep.nCrtcs);
	swaps(&rep.nModes);
	swaps(&rep.nClones);
	swaps(&rep.nameLength);
    }
    WriteToClient(client, sizeof(xRRGetOutputInfoReply), (char *)&rep);
    if (extraLen)
    {
	WriteToClient (client, extraLen, (char *) extra);
	free(extra);
    }
    
    return Success;
}

static void
RRSetPrimaryOutput(ScreenPtr pScreen, rrScrPrivPtr pScrPriv,
		   RROutputPtr output)
{
    if (pScrPriv->primaryOutput == output)
	return;

    /* clear the old primary */
    if (pScrPriv->primaryOutput) {
	RROutputChanged(pScrPriv->primaryOutput, 0);
	pScrPriv->primaryOutput = NULL;
    }

    /* set the new primary */
    if (output) {
	pScrPriv->primaryOutput = output;
	RROutputChanged(output, 0);
    }

    pScrPriv->layoutChanged = TRUE;

    RRTellChanged(pScreen);
}

int
ProcRRSetOutputPrimary(ClientPtr client)
{
    REQUEST(xRRSetOutputPrimaryReq);
    RROutputPtr output = NULL;
    WindowPtr pWin;
    rrScrPrivPtr pScrPriv;
    int rc;

    REQUEST_SIZE_MATCH(xRRSetOutputPrimaryReq);

    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
	return rc;

    if (stuff->output) {
	VERIFY_RR_OUTPUT(stuff->output, output, DixReadAccess);

	if (output->pScreen != pWin->drawable.pScreen) {
	    client->errorValue = stuff->window;
	    return BadMatch;
	}
    }

    pScrPriv = rrGetScrPriv(pWin->drawable.pScreen);
    RRSetPrimaryOutput(pWin->drawable.pScreen, pScrPriv, output);

    return Success;
}

int
ProcRRGetOutputPrimary(ClientPtr client)
{
    REQUEST(xRRGetOutputPrimaryReq);
    WindowPtr pWin;
    rrScrPrivPtr pScrPriv;
    xRRGetOutputPrimaryReply rep;
    RROutputPtr primary = NULL;
    int rc;

    REQUEST_SIZE_MATCH(xRRGetOutputPrimaryReq);

    rc = dixLookupWindow(&pWin, stuff->window, client, DixGetAttrAccess);
    if (rc != Success)
	return rc;

    pScrPriv = rrGetScrPriv(pWin->drawable.pScreen);
    if (pScrPriv)
	primary = pScrPriv->primaryOutput;

    memset(&rep, 0, sizeof(rep));
    rep.type = X_Reply;
    rep.sequenceNumber = client->sequence;
    rep.output = primary ? primary->id : None;

    if (client->swapped) {
	swaps(&rep.sequenceNumber);
	swapl(&rep.output);
    }

    WriteToClient(client, sizeof(xRRGetOutputPrimaryReply), &rep);

    return Success;
}