/*
 * Copyright © 2000 SuSE, 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 SuSE not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  SuSE makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * SuSE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL SuSE
 * 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:  Keith Packard, SuSE, Inc.
 */

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

#include <stdlib.h>

#include "fb.h"
#include "fboverlay.h"

#ifdef MITSHM
#include "shmint.h"
#endif

int	fbOverlayGeneration;
int	fbOverlayScreenPrivateIndex = -1;

int fbOverlayGetScreenPrivateIndex(void)
{
    return fbOverlayScreenPrivateIndex;
}

/*
 * Replace this if you want something supporting
 * multiple overlays with the same depth
 */
Bool
fbOverlayCreateWindow(WindowPtr pWin)
{
    FbOverlayScrPrivPtr	pScrPriv = fbOverlayGetScrPriv(pWin->drawable.pScreen);
    int			i;
    PixmapPtr		pPixmap;
    
    if (pWin->drawable.class != InputOutput)
	return TRUE;

#ifdef FB_SCREEN_PRIVATE
    if (pWin->drawable.bitsPerPixel == 32)
	pWin->drawable.bitsPerPixel = fbGetScreenPrivate(pWin->drawable.pScreen)->win32bpp;
#endif

    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	pPixmap = pScrPriv->layer[i].u.run.pixmap;
	if (pWin->drawable.depth == pPixmap->drawable.depth)
	{
	    pWin->devPrivates[fbWinPrivateIndex].ptr = (void *) pPixmap;
	    /*
	     * Make sure layer keys are written correctly by
	     * having non-root layers set to full while the
	     * root layer is set to empty.  This will cause
	     * all of the layers to get painted when the root
	     * is mapped
	     */
	    if (!pWin->parent)
	    {
		RegionEmpty(
			      &pScrPriv->layer[i].u.run.region);
	    }
	    return TRUE;
	}
    }
    return FALSE;
}

Bool
fbOverlayCloseScreen (ScreenPtr pScreen)
{
    FbOverlayScrPrivPtr	pScrPriv = fbOverlayGetScrPriv(pScreen);
    int			i;

    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	(*pScreen->DestroyPixmap)(pScrPriv->layer[i].u.run.pixmap);
	RegionUninit(&pScrPriv->layer[i].u.run.region);
    }
    return TRUE;
}

/*
 * Return layer containing this window
 */
int
fbOverlayWindowLayer(WindowPtr pWin)
{
    FbOverlayScrPrivPtr pScrPriv = fbOverlayGetScrPriv(pWin->drawable.pScreen);
    int                 i;

    for (i = 0; i < pScrPriv->nlayers; i++)
	if (pWin->devPrivates[fbWinPrivateIndex].ptr ==
	    (void *) pScrPriv->layer[i].u.run.pixmap)
	    return i;
    return 0;
}

Bool
fbOverlayCreateScreenResources(ScreenPtr pScreen)
{
    int			i;
    FbOverlayScrPrivPtr	pScrPriv = fbOverlayGetScrPriv(pScreen);
    PixmapPtr		pPixmap;
    void *		pbits;
    int			width;
    int			depth;
    BoxRec		box;
    
    if (!miCreateScreenResources(pScreen))
	return FALSE;

    box.x1 = 0;
    box.y1 = 0;
    box.x2 = pScreen->width;
    box.y2 = pScreen->height;
    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	pbits = pScrPriv->layer[i].u.init.pbits;
	width = pScrPriv->layer[i].u.init.width;
	depth = pScrPriv->layer[i].u.init.depth;
	pPixmap = (*pScreen->CreatePixmap)(pScreen, 0, 0, depth, 0);
	if (!pPixmap)
	    return FALSE;
	if (!(*pScreen->ModifyPixmapHeader)(pPixmap, pScreen->width,
					    pScreen->height, depth,
					    BitsPerPixel(depth),
					    PixmapBytePad(width, depth),
					    pbits))
	    return FALSE;
	pScrPriv->layer[i].u.run.pixmap = pPixmap;
	RegionInit(&pScrPriv->layer[i].u.run.region, &box, 0);
    }
    pScreen->devPrivate = pScrPriv->layer[0].u.run.pixmap;
    return TRUE;
}

void
fbOverlayPaintKey (DrawablePtr	pDrawable,
		   RegionPtr	pRegion,
		   CARD32	pixel,
		   int		layer)
{
    fbFillRegionSolid (pDrawable, pRegion, 0, 
		       fbReplicatePixel (pixel, pDrawable->bitsPerPixel));
}

/*
 * Track visible region for each layer
 */
void
fbOverlayUpdateLayerRegion (ScreenPtr	pScreen,
			    int		layer,
			    RegionPtr	prgn)
{
    FbOverlayScrPrivPtr pScrPriv = fbOverlayGetScrPriv(pScreen);
    int			i;
    RegionRec		rgnNew;
    
    if (!prgn || !RegionNotEmpty(prgn))
	return;
    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	if (i == layer)
	{
	    /* add new piece to this fb */
	    RegionUnion(
			  &pScrPriv->layer[i].u.run.region,
			  &pScrPriv->layer[i].u.run.region,
			  prgn);
	}
	else if (RegionNotEmpty(
				  &pScrPriv->layer[i].u.run.region))
	{
	    /* paint new piece with chroma key */
	    RegionNull(&rgnNew);
	    RegionIntersect(
			      &rgnNew, 
			      prgn, 
			      &pScrPriv->layer[i].u.run.region);
	    (*pScrPriv->PaintKey) (&pScrPriv->layer[i].u.run.pixmap->drawable,
				   &rgnNew,
				   pScrPriv->layer[i].key,
				   i);
	    RegionUninit(&rgnNew);
	    /* remove piece from other fbs */
	    RegionSubtract(
			     &pScrPriv->layer[i].u.run.region,
			     &pScrPriv->layer[i].u.run.region,
			     prgn);
	}
    }
}

/*
 * Copy only areas in each layer containing real bits
 */
void
fbOverlayCopyWindow(WindowPtr	pWin,
		    DDXPointRec	ptOldOrg,
		    RegionPtr	prgnSrc)
{
    ScreenPtr		pScreen = pWin->drawable.pScreen;
    FbOverlayScrPrivPtr	pScrPriv = fbOverlayGetScrPriv(pWin->drawable.pScreen);
    RegionRec		rgnDst;
    int			dx, dy;
    int			i;
    RegionRec		layerRgn[FB_OVERLAY_MAX];
    PixmapPtr		pPixmap;

    dx = ptOldOrg.x - pWin->drawable.x;
    dy = ptOldOrg.y - pWin->drawable.y;

    /*
     * Clip to existing bits
     */
    RegionTranslate(prgnSrc, -dx, -dy);
    RegionNull(&rgnDst);
    RegionIntersect(&rgnDst, &pWin->borderClip, prgnSrc);
    RegionTranslate(&rgnDst, dx, dy);
    /*
     * Compute the portion of each fb affected by this copy
     */
    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	RegionNull(&layerRgn[i]);
	RegionIntersect(&layerRgn[i], &rgnDst,
			 &pScrPriv->layer[i].u.run.region);
	if (RegionNotEmpty(&layerRgn[i]))
	{
	    RegionTranslate(&layerRgn[i], -dx, -dy);
	    pPixmap = pScrPriv->layer[i].u.run.pixmap;
	    fbCopyRegion (&pPixmap->drawable, &pPixmap->drawable,
			  0,
			  &layerRgn[i], dx, dy, pScrPriv->CopyWindow, 0,
			  (void *)(long) i);
	}
    }
    /*
     * Update regions
     */
    for (i = 0; i < pScrPriv->nlayers; i++)
    {
	if (RegionNotEmpty(&layerRgn[i]))
	    fbOverlayUpdateLayerRegion (pScreen, i, &layerRgn[i]);

	RegionUninit(&layerRgn[i]);
    }
    RegionUninit(&rgnDst);
}   

void
fbOverlayWindowExposures (WindowPtr	pWin,
			  RegionPtr	prgn,
			  RegionPtr	other_exposed)
{
    fbOverlayUpdateLayerRegion (pWin->drawable.pScreen,
				fbOverlayWindowLayer (pWin),
				prgn);
    miWindowExposures(pWin, prgn, other_exposed);
}

void
fbOverlayPaintWindow(WindowPtr pWin, RegionPtr pRegion, int what)
{
    if (what == PW_BORDER)
	fbOverlayUpdateLayerRegion (pWin->drawable.pScreen,
				    fbOverlayWindowLayer (pWin),
				    pRegion);
    fbPaintWindow (pWin, pRegion, what);
}

Bool
fbOverlaySetupScreen(ScreenPtr	pScreen,
		     void *	pbits1,
		     void *	pbits2,
		     int	xsize,
		     int	ysize,
		     int	dpix,
		     int	dpiy,
		     int	width1,
		     int	width2,
		     int	bpp1,
		     int	bpp2)
{
    return fbSetupScreen (pScreen,
			  pbits1,
			  xsize,
			  ysize,
			  dpix,
			  dpiy,
			  width1,
			  bpp1);
}

static Bool
fb24_32OverlayCreateScreenResources(ScreenPtr pScreen)
{
    FbOverlayScrPrivPtr	pScrPriv = fbOverlayGetScrPriv(pScreen);
    int pitch;
    Bool retval;
    int i;

    if((retval = fbOverlayCreateScreenResources(pScreen))) {
	for (i = 0; i < pScrPriv->nlayers; i++)
	{
	    /* fix the screen pixmap */
	    PixmapPtr pPix = (PixmapPtr) pScrPriv->layer[i].u.run.pixmap;
	    if (pPix->drawable.bitsPerPixel == 32) {
		pPix->drawable.bitsPerPixel = 24;
		pitch = BitmapBytePad(pPix->drawable.width * 24);
		pPix->devKind = pitch;
	    }
	}
    }

    return retval;
}

Bool
fbOverlayFinishScreenInit(ScreenPtr	pScreen,
			  void *	pbits1,
			  void *	pbits2,
			  int		xsize,
			  int		ysize,
			  int		dpix,
			  int		dpiy,
			  int		width1,
			  int		width2,
			  int		bpp1,
			  int		bpp2,
			  int		depth1,
			  int		depth2)
{
    VisualPtr	visuals;
    DepthPtr	depths;
    int		nvisuals;
    int		ndepths;
    int		bpp = 0, imagebpp = 32;
    VisualID	defaultVisual;
    FbOverlayScrPrivPtr	pScrPriv;

    if (fbOverlayGeneration != serverGeneration)
    {
	fbOverlayScreenPrivateIndex = AllocateScreenPrivateIndex ();
	fbOverlayGeneration = serverGeneration;
    }

    pScrPriv = malloc (sizeof (FbOverlayScrPrivRec));
    if (!pScrPriv)
	return FALSE;
 
#ifdef FB_24_32BIT
    if (bpp1 == 32 || bpp2 == 32)
	bpp = 32;
    else if (bpp1 == 24 || bpp2 == 24)
	bpp = 24;

    if (bpp == 24)
    {
	int	f;
	
	imagebpp = 32;
	/*
	 * Check to see if we're advertising a 24bpp image format,
	 * in which case windows will use it in preference to a 32 bit
	 * format.
	 */
	for (f = 0; f < screenInfo.numPixmapFormats; f++)
	{
	    if (screenInfo.formats[f].bitsPerPixel == 24)
	    {
		imagebpp = 24;
		break;
	    }
	}	    
    }
#endif
#ifdef FB_SCREEN_PRIVATE
    if (imagebpp == 32)
    {
	fbGetScreenPrivate(pScreen)->win32bpp = bpp;
	fbGetScreenPrivate(pScreen)->pix32bpp = bpp;
    }
    else
    {
	fbGetScreenPrivate(pScreen)->win32bpp = 32;
	fbGetScreenPrivate(pScreen)->pix32bpp = 32;
    }
#endif
   
    if (!fbInitVisuals (&visuals, &depths, &nvisuals, &ndepths, &depth1,
			&defaultVisual, ((unsigned long)1<<(bpp1-1)) |
			((unsigned long)1<<(bpp2-1)), 8)) {
	free(pScrPriv);
	return FALSE;
    }
    if (! miScreenInit(pScreen, 0, xsize, ysize, dpix, dpiy, 0,
			depth1, ndepths, depths,
			defaultVisual, nvisuals, visuals
#ifdef FB_OLD_MISCREENINIT
		       , (miBSFuncPtr) 0
#endif
		       )) {
	free(pScrPriv);
	return FALSE;
    }
    /* MI thinks there's no frame buffer */
#ifdef MITSHM
    ShmRegisterFbFuncs(pScreen);
#endif
    pScreen->minInstalledCmaps = 1;
    pScreen->maxInstalledCmaps = 2;
    
    pScrPriv->nlayers = 2;
    pScrPriv->PaintKey = fbOverlayPaintKey;
    pScrPriv->CopyWindow = fbCopyWindowProc;
    pScrPriv->layer[0].u.init.pbits = pbits1;
    pScrPriv->layer[0].u.init.width = width1;
    pScrPriv->layer[0].u.init.depth = depth1;

    pScrPriv->layer[1].u.init.pbits = pbits2;
    pScrPriv->layer[1].u.init.width = width2;
    pScrPriv->layer[1].u.init.depth = depth2;
    
    pScreen->devPrivates[fbOverlayScreenPrivateIndex].ptr = (void *) pScrPriv;
    
    /* overwrite miCloseScreen with our own */
    pScreen->CloseScreen = fbOverlayCloseScreen;
    pScreen->CreateScreenResources = fbOverlayCreateScreenResources;
    pScreen->CreateWindow = fbOverlayCreateWindow;
    pScreen->WindowExposures = fbOverlayWindowExposures;
    pScreen->CopyWindow = fbOverlayCopyWindow;
    pScreen->PaintWindowBorder = fbOverlayPaintWindow;
#ifdef FB_24_32BIT
    if (bpp == 24 && imagebpp == 32)
    {
	pScreen->ModifyPixmapHeader = fb24_32ModifyPixmapHeader;
  	pScreen->CreateScreenResources = fb24_32OverlayCreateScreenResources;
    }
#endif

    return TRUE;
}
