/*
 * Copyright © 2006 Sun Microsystems, Inc.  All rights reserved.
 *
 * 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 (including the next
 * paragraph) 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 AUTHORS OR COPYRIGHT HOLDERS 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.
 *
 * Copyright © 2003 Keith Packard
 *
 * 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 Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD 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.
 */

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

#include "compint.h"

static void
compReportDamage (DamagePtr pDamage, RegionPtr pRegion, void *closure)
{
    WindowPtr	    pWin = (WindowPtr) closure;
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    CompScreenPtr   cs = GetCompScreen (pScreen);
    CompWindowPtr   cw = GetCompWindow (pWin);

    cs->damaged = TRUE;
    cw->damaged = TRUE;
}

static void
compDestroyDamage (DamagePtr pDamage, void *closure)
{
    WindowPtr	    pWin = (WindowPtr) closure;
    CompWindowPtr   cw = GetCompWindow (pWin);

    cw->damage = 0;
}

/*
 * Redirect one window for one client
 */
int
compRedirectWindow (ClientPtr pClient, WindowPtr pWin, int update)
{
    CompWindowPtr	cw = GetCompWindow (pWin);
    CompClientWindowPtr	ccw;
    Bool		wasMapped = pWin->mapped;
    CompScreenPtr       cs = GetCompScreen(pWin->drawable.pScreen);
    
    if (pWin == cs->pOverlayWin) {
	return Success;
    }

    if (!pWin->parent)
	return BadMatch;

    /*
     * Only one Manual update is allowed
     */
    if (cw && update == CompositeRedirectManual)
	for (ccw = cw->clients; ccw; ccw = ccw->next)
	    if (ccw->update == CompositeRedirectManual)
		return BadAccess;
    
    /*
     * Allocate per-client per-window structure 
     * The client *could* allocate multiple, but while supported,
     * it is not expected to be common
     */
    ccw = xalloc (sizeof (CompClientWindowRec));
    if (!ccw)
	return BadAlloc;
    ccw->id = FakeClientID (pClient->index);
    ccw->update = update;
    /*
     * Now make sure there's a per-window structure to hang this from
     */
    if (!cw)
    {
	cw = xalloc (sizeof (CompWindowRec));
	if (!cw)
	{
	    xfree (ccw);
	    return BadAlloc;
	}
	cw->damage = DamageCreate (compReportDamage,
				   compDestroyDamage,
				   DamageReportNonEmpty,
				   FALSE,
				   pWin->drawable.pScreen,
				   pWin);
	if (!cw->damage)
	{
	    xfree (ccw);
	    xfree (cw);
	    return BadAlloc;
	}
	if (wasMapped)
	{
	    DisableMapUnmapEvents (pWin);
	    UnmapWindow (pWin, FALSE);
	    EnableMapUnmapEvents (pWin);
	}

	REGION_NULL (pScreen, &cw->borderClip);
	cw->borderClipX = 0;
	cw->borderClipY = 0;
	cw->update = CompositeRedirectAutomatic;
	cw->clients = 0;
	cw->oldx = COMP_ORIGIN_INVALID;
	cw->oldy = COMP_ORIGIN_INVALID;
	cw->damageRegistered = FALSE;
	cw->damaged = FALSE;
	dixSetPrivate(&pWin->devPrivates, CompWindowPrivateKey, cw);
    }
    ccw->next = cw->clients;
    cw->clients = ccw;
    if (!AddResource (ccw->id, CompositeClientWindowType, pWin))
	return BadAlloc;
    if (ccw->update == CompositeRedirectManual)
    {
	/* If the window was CompositeRedirectAutomatic, then
	 * unmap the window so that the parent clip list will
	 * be correctly recomputed.
	 */
	if (pWin->mapped) 
	{
	    DisableMapUnmapEvents (pWin);
	    UnmapWindow (pWin, FALSE);
	    EnableMapUnmapEvents (pWin);
	}
	if (cw->damageRegistered)
	{
	    DamageUnregister (&pWin->drawable, cw->damage);
	    cw->damageRegistered = FALSE;
	}
	cw->update = CompositeRedirectManual;
    }

    if (!compCheckRedirect (pWin))
    {
	FreeResource (ccw->id, RT_NONE);
	return BadAlloc;
    }
    if (wasMapped && !pWin->mapped)
    {
	Bool	overrideRedirect = pWin->overrideRedirect;
	pWin->overrideRedirect = TRUE;
	DisableMapUnmapEvents (pWin);
	MapWindow (pWin, pClient);
	EnableMapUnmapEvents (pWin);
	pWin->overrideRedirect = overrideRedirect;
    }
    
    return Success;
}

/*
 * Free one of the per-client per-window resources, clearing
 * redirect and the per-window pointer as appropriate
 */
void
compFreeClientWindow (WindowPtr pWin, XID id)
{
    CompWindowPtr	cw = GetCompWindow (pWin);
    CompClientWindowPtr	ccw, *prev;
    Bool		wasMapped = pWin->mapped;

    if (!cw)
	return;
    for (prev = &cw->clients; (ccw = *prev); prev = &ccw->next)
    {
	if (ccw->id == id)
	{
	    *prev = ccw->next;
	    if (ccw->update == CompositeRedirectManual)
		cw->update = CompositeRedirectAutomatic;
	    xfree (ccw);
	    break;
	}
    }
    if (!cw->clients)
    {
	if (wasMapped)
	{
	    DisableMapUnmapEvents (pWin);
	    UnmapWindow (pWin, FALSE);
	    EnableMapUnmapEvents (pWin);
	}
    
	if (pWin->redirectDraw != RedirectDrawNone)
	    compFreePixmap (pWin);

	if (cw->damage)
	    DamageDestroy (cw->damage);
	
	REGION_UNINIT (pScreen, &cw->borderClip);
    
	dixSetPrivate(&pWin->devPrivates, CompWindowPrivateKey, NULL);
	xfree (cw);
    }
    else if (cw->update == CompositeRedirectAutomatic &&
	     !cw->damageRegistered && pWin->redirectDraw != RedirectDrawNone)
    {
	DamageRegister (&pWin->drawable, cw->damage);
	cw->damageRegistered = TRUE;
	pWin->redirectDraw = RedirectDrawAutomatic;
	DamageRegionAppend(&pWin->drawable, &pWin->borderSize);
    }
    if (wasMapped && !pWin->mapped)
    {
	Bool	overrideRedirect = pWin->overrideRedirect;
	pWin->overrideRedirect = TRUE;
	DisableMapUnmapEvents (pWin);
	MapWindow (pWin, clients[CLIENT_ID(id)]);
	EnableMapUnmapEvents (pWin);
	pWin->overrideRedirect = overrideRedirect;
    }
}

/*
 * This is easy, just free the appropriate resource.
 */

int
compUnredirectWindow (ClientPtr pClient, WindowPtr pWin, int update)
{
    CompWindowPtr	cw = GetCompWindow (pWin);
    CompClientWindowPtr	ccw;

    if (!cw)
	return BadValue;

    for (ccw = cw->clients; ccw; ccw = ccw->next)
	if (ccw->update == update && CLIENT_ID(ccw->id) == pClient->index)
	{
	    FreeResource (ccw->id, RT_NONE);
	    return Success;
	}
    return BadValue;
}
	
/*
 * Redirect all subwindows for one client
 */

int
compRedirectSubwindows (ClientPtr pClient, WindowPtr pWin, int update)
{
    CompSubwindowsPtr	csw = GetCompSubwindows (pWin);
    CompClientWindowPtr	ccw;
    WindowPtr		pChild;

    /*
     * Only one Manual update is allowed
     */
    if (csw && update == CompositeRedirectManual)
	for (ccw = csw->clients; ccw; ccw = ccw->next)
	    if (ccw->update == CompositeRedirectManual)
		return BadAccess;
    /*
     * Allocate per-client per-window structure 
     * The client *could* allocate multiple, but while supported,
     * it is not expected to be common
     */
    ccw = xalloc (sizeof (CompClientWindowRec));
    if (!ccw)
	return BadAlloc;
    ccw->id = FakeClientID (pClient->index);
    ccw->update = update;
    /*
     * Now make sure there's a per-window structure to hang this from
     */
    if (!csw)
    {
	csw = xalloc (sizeof (CompSubwindowsRec));
	if (!csw)
	{
	    xfree (ccw);
	    return BadAlloc;
	}
	csw->update = CompositeRedirectAutomatic;
	csw->clients = 0;
	dixSetPrivate(&pWin->devPrivates, CompSubwindowsPrivateKey, csw);
    }
    /*
     * Redirect all existing windows
     */
    for (pChild = pWin->lastChild; pChild; pChild = pChild->prevSib)
    {
	int ret = compRedirectWindow (pClient, pChild, update);
	if (ret != Success)
	{
	    for (pChild = pChild->nextSib; pChild; pChild = pChild->nextSib)
		(void) compUnredirectWindow (pClient, pChild, update);
	    if (!csw->clients)
	    {
		xfree (csw);
		dixSetPrivate(&pWin->devPrivates, CompSubwindowsPrivateKey, 0);
	    }
	    xfree (ccw);
	    return ret;
	}
    }
    /*
     * Hook into subwindows list
     */
    ccw->next = csw->clients;
    csw->clients = ccw;
    if (!AddResource (ccw->id, CompositeClientSubwindowsType, pWin))
	return BadAlloc;
    if (ccw->update == CompositeRedirectManual)
    {
	csw->update = CompositeRedirectManual;
	/* 
	 * tell damage extension that damage events for this client are
	 * critical output
	 */
	DamageExtSetCritical (pClient, TRUE);
    }
    return Success;
}

/*
 * Free one of the per-client per-subwindows resources,
 * which frees one redirect per subwindow
 */
void
compFreeClientSubwindows (WindowPtr pWin, XID id)
{
    CompSubwindowsPtr	csw = GetCompSubwindows (pWin);
    CompClientWindowPtr	ccw, *prev;
    WindowPtr		pChild;

    if (!csw)
	return;
    for (prev = &csw->clients; (ccw = *prev); prev = &ccw->next)
    {
	if (ccw->id == id)
	{
	    ClientPtr	pClient = clients[CLIENT_ID(id)];
	    
	    *prev = ccw->next;
	    if (ccw->update == CompositeRedirectManual)
	    {
		/* 
		 * tell damage extension that damage events for this client are
		 * critical output
		 */
		DamageExtSetCritical (pClient, FALSE);
		csw->update = CompositeRedirectAutomatic;
		if (pWin->mapped)
		    (*pWin->drawable.pScreen->ClearToBackground)(pWin, 0, 0, 0, 0, TRUE);
	    }

	    /*
	     * Unredirect all existing subwindows
	     */
	    for (pChild = pWin->lastChild; pChild; pChild = pChild->prevSib)
		(void) compUnredirectWindow (pClient, pChild, ccw->update);

	    xfree (ccw);
	    break;
	}
    }

    /*
     * Check if all of the per-client records are gone
     */
    if (!csw->clients)
    {
	dixSetPrivate(&pWin->devPrivates, CompSubwindowsPrivateKey, NULL);
	xfree (csw);
    }
}

/*
 * This is easy, just free the appropriate resource.
 */

int
compUnredirectSubwindows (ClientPtr pClient, WindowPtr pWin, int update)
{
    CompSubwindowsPtr	csw = GetCompSubwindows (pWin);
    CompClientWindowPtr	ccw;
    
    if (!csw)
	return BadValue;
    for (ccw = csw->clients; ccw; ccw = ccw->next)
	if (ccw->update == update && CLIENT_ID(ccw->id) == pClient->index)
	{
	    FreeResource (ccw->id, RT_NONE);
	    return Success;
	}
    return BadValue;
}

/*
 * Add redirection information for one subwindow (during reparent)
 */

int
compRedirectOneSubwindow (WindowPtr pParent, WindowPtr pWin)
{
    CompSubwindowsPtr	csw = GetCompSubwindows (pParent);
    CompClientWindowPtr	ccw;

    if (!csw)
	return Success;
    for (ccw = csw->clients; ccw; ccw = ccw->next)
    {
	int ret = compRedirectWindow (clients[CLIENT_ID(ccw->id)],
				      pWin, ccw->update);
	if (ret != Success)
	    return ret;
    }
    return Success;
}

/*
 * Remove redirection information for one subwindow (during reparent)
 */

int
compUnredirectOneSubwindow (WindowPtr pParent, WindowPtr pWin)
{
    CompSubwindowsPtr	csw = GetCompSubwindows (pParent);
    CompClientWindowPtr	ccw;

    if (!csw)
	return Success;
    for (ccw = csw->clients; ccw; ccw = ccw->next)
    {
	int ret = compUnredirectWindow (clients[CLIENT_ID(ccw->id)],
					pWin, ccw->update);
	if (ret != Success)
	    return ret;
    }
    return Success;
}

static PixmapPtr
compNewPixmap (WindowPtr pWin, int x, int y, int w, int h)
{
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    WindowPtr	    pParent = pWin->parent;
    PixmapPtr	    pPixmap;

    pPixmap = (*pScreen->CreatePixmap) (pScreen, w, h, pWin->drawable.depth,
					CREATE_PIXMAP_USAGE_BACKING_PIXMAP);

    if (!pPixmap)
	return 0;
    
    pPixmap->screen_x = x;
    pPixmap->screen_y = y;
    
    if (pParent->drawable.depth == pWin->drawable.depth)
    {
	GCPtr	pGC = GetScratchGC (pWin->drawable.depth, pScreen);
	
	/*
	 * Copy bits from the parent into the new pixmap so that it will
	 * have "reasonable" contents in case for background None areas.
	 */
	if (pGC)
	{
	    XID val = IncludeInferiors;
	    
	    ValidateGC(&pPixmap->drawable, pGC);
	    dixChangeGC (serverClient, pGC, GCSubwindowMode, &val, NULL);
	    (*pGC->ops->CopyArea) (&pParent->drawable,
				   &pPixmap->drawable,
				   pGC,
				   x - pParent->drawable.x,
				   y - pParent->drawable.y,
				   w, h, 0, 0);
	    FreeScratchGC (pGC);
	}
    }
    else
    {
	PictFormatPtr	pSrcFormat = compWindowFormat (pParent);
	PictFormatPtr	pDstFormat = compWindowFormat (pWin);
	XID		inferiors = IncludeInferiors;
	int		error;

	PicturePtr	pSrcPicture = CreatePicture (None,
						     &pParent->drawable,
						     pSrcFormat,
						     CPSubwindowMode,
						     &inferiors,
						     serverClient, &error);
						    
	PicturePtr	pDstPicture = CreatePicture (None,
						     &pPixmap->drawable,
						     pDstFormat,
						     0, 0,
						     serverClient, &error);

	if (pSrcPicture && pDstPicture)
	{
	    CompositePicture (PictOpSrc,
			      pSrcPicture,
			      NULL,
			      pDstPicture,
			      x - pParent->drawable.x,
			      y - pParent->drawable.y,
			      0, 0, 0, 0, w, h);
	}
	if (pSrcPicture)
	    FreePicture (pSrcPicture, 0);
	if (pDstPicture)
	    FreePicture (pDstPicture, 0);
    }
    return pPixmap;
}

Bool
compAllocPixmap (WindowPtr pWin)
{
    int		    bw = (int) pWin->borderWidth;
    int		    x = pWin->drawable.x - bw;
    int		    y = pWin->drawable.y - bw;
    int		    w = pWin->drawable.width + (bw << 1);
    int		    h = pWin->drawable.height + (bw << 1);
    PixmapPtr	    pPixmap = compNewPixmap (pWin, x, y, w, h);
    CompWindowPtr   cw = GetCompWindow (pWin);

    if (!pPixmap)
	return FALSE;
    if (cw->update == CompositeRedirectAutomatic)
	pWin->redirectDraw = RedirectDrawAutomatic;
    else
	pWin->redirectDraw = RedirectDrawManual;

    compSetPixmap (pWin, pPixmap);
    cw->oldx = COMP_ORIGIN_INVALID;
    cw->oldy = COMP_ORIGIN_INVALID;
    cw->damageRegistered = FALSE;
    if (cw->update == CompositeRedirectAutomatic)
    {
	DamageRegister (&pWin->drawable, cw->damage);
	cw->damageRegistered = TRUE;
    }
    return TRUE;
}

void
compFreePixmap (WindowPtr pWin)
{
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    PixmapPtr	    pRedirectPixmap, pParentPixmap;
    CompWindowPtr   cw = GetCompWindow (pWin);

    if (cw->damageRegistered)
    {
	DamageUnregister (&pWin->drawable, cw->damage);
	cw->damageRegistered = FALSE;
	DamageEmpty (cw->damage);
    }
    /*
     * Move the parent-constrained border clip region back into
     * the window so that ValidateTree will handle the unmap
     * case correctly.  Unmap adds the window borderClip to the
     * parent exposed area; regions beyond the parent cause crashes
     */
    REGION_COPY (pScreen, &pWin->borderClip, &cw->borderClip);
    pRedirectPixmap = (*pScreen->GetWindowPixmap) (pWin);
    pParentPixmap = (*pScreen->GetWindowPixmap) (pWin->parent);
    pWin->redirectDraw = RedirectDrawNone;
    compSetPixmap (pWin, pParentPixmap);
    (*pScreen->DestroyPixmap) (pRedirectPixmap);
}

/*
 * Make sure the pixmap is the right size and offset.  Allocate a new
 * pixmap to change size, adjust origin to change offset, leaving the
 * old pixmap in cw->pOldPixmap so bits can be recovered
 */
Bool
compReallocPixmap (WindowPtr pWin, int draw_x, int draw_y,
		   unsigned int w, unsigned int h, int bw)
{
    ScreenPtr	    pScreen = pWin->drawable.pScreen;
    PixmapPtr	    pOld = (*pScreen->GetWindowPixmap) (pWin);
    PixmapPtr	    pNew;
    CompWindowPtr   cw = GetCompWindow (pWin);
    int		    pix_x, pix_y;
    int		    pix_w, pix_h;

    assert (cw && pWin->redirectDraw != RedirectDrawNone);
    cw->oldx = pOld->screen_x;
    cw->oldy = pOld->screen_y;
    pix_x = draw_x - bw;
    pix_y = draw_y - bw;
    pix_w = w + (bw << 1);
    pix_h = h + (bw << 1);
    if (pix_w != pOld->drawable.width || pix_h != pOld->drawable.height)
    {
	pNew = compNewPixmap (pWin, pix_x, pix_y, pix_w, pix_h);
	if (!pNew)
	    return FALSE;
	cw->pOldPixmap = pOld;
	compSetPixmap (pWin, pNew);
    }
    else
    {
	pNew = pOld;
	cw->pOldPixmap = 0;
    }
    pNew->screen_x = pix_x;
    pNew->screen_y = pix_y;
    return TRUE;
}