/*
 * Copyright © 1998 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 <stdlib.h>

#include "fb.h"

PixmapPtr
fbCreatePixmapBpp(ScreenPtr pScreen, int width, int height, int depth, int bpp,
                  unsigned usage_hint)
{
    PixmapPtr pPixmap;
    size_t datasize;
    size_t paddedWidth;
    int adjust;
    int base;

    paddedWidth = ((width * bpp + FB_MASK) >> FB_SHIFT) * sizeof(FbBits);
    if (paddedWidth / 4 > 32767 || height > 32767)
        return NullPixmap;
    datasize = height * paddedWidth;
    base = pScreen->totalPixmapSize;
    adjust = 0;
    if (base & 7)
        adjust = 8 - (base & 7);
    datasize += adjust;
#ifdef FB_DEBUG
    datasize += 2 * paddedWidth;
#endif
    pPixmap = AllocatePixmap(pScreen, datasize);
    if (!pPixmap)
        return NullPixmap;
    pPixmap->drawable.type = DRAWABLE_PIXMAP;
    pPixmap->drawable.class = 0;
    pPixmap->drawable.pScreen = pScreen;
    pPixmap->drawable.depth = depth;
    pPixmap->drawable.bitsPerPixel = bpp;
    pPixmap->drawable.id = 0;
    pPixmap->drawable.serialNumber = NEXT_SERIAL_NUMBER;
    pPixmap->drawable.x = 0;
    pPixmap->drawable.y = 0;
    pPixmap->drawable.width = width;
    pPixmap->drawable.height = height;
    pPixmap->devKind = paddedWidth;
    pPixmap->refcnt = 1;
    pPixmap->devPrivate.ptr = (pointer) ((char *) pPixmap + base + adjust);

#ifdef FB_DEBUG
    pPixmap->devPrivate.ptr =
        (void *) ((char *) pPixmap->devPrivate.ptr + paddedWidth);
    fbInitializeDrawable(&pPixmap->drawable);
#endif

#ifdef COMPOSITE
    pPixmap->screen_x = 0;
    pPixmap->screen_y = 0;
#endif

    pPixmap->usage_hint = usage_hint;

    return pPixmap;
}

PixmapPtr
fbCreatePixmap(ScreenPtr pScreen, int width, int height, int depth,
               unsigned usage_hint)
{
    int bpp;

    bpp = BitsPerPixel(depth);
    if (bpp == 32 && depth <= 24)
        bpp = fbGetScreenPrivate(pScreen)->pix32bpp;
    return fbCreatePixmapBpp(pScreen, width, height, depth, bpp, usage_hint);
}

Bool
fbDestroyPixmap(PixmapPtr pPixmap)
{
    if (--pPixmap->refcnt)
        return TRUE;
    FreePixmap(pPixmap);
    return TRUE;
}

#define ADDRECT(reg,r,fr,rx1,ry1,rx2,ry2)			\
if (((rx1) < (rx2)) && ((ry1) < (ry2)) &&			\
    (!((reg)->data->numRects &&					\
       ((r-1)->y1 == (ry1)) &&					\
       ((r-1)->y2 == (ry2)) &&					\
       ((r-1)->x1 <= (rx1)) &&					\
       ((r-1)->x2 >= (rx2)))))					\
{								\
    if ((reg)->data->numRects == (reg)->data->size)		\
    {								\
	RegionRectAlloc(reg, 1);					\
	fr = RegionBoxptr(reg);				\
	r = fr + (reg)->data->numRects;				\
    }								\
    r->x1 = (rx1);						\
    r->y1 = (ry1);						\
    r->x2 = (rx2);						\
    r->y2 = (ry2);						\
    (reg)->data->numRects++;					\
    if(r->x1 < (reg)->extents.x1)				\
	(reg)->extents.x1 = r->x1;				\
    if(r->x2 > (reg)->extents.x2)				\
	(reg)->extents.x2 = r->x2;				\
    r++;							\
}

/* Convert bitmap clip mask into clipping region. 
 * First, goes through each line and makes boxes by noting the transitions
 * from 0 to 1 and 1 to 0.
 * Then it coalesces the current line with the previous if they have boxes
 * at the same X coordinates.
 */
RegionPtr
fbPixmapToRegion(PixmapPtr pPix)
{
    register RegionPtr pReg;
    FbBits *pw, w;
    register int ib;
    int width, h, base, rx1 = 0, crects;
    FbBits *pwLineEnd;
    int irectPrevStart, irectLineStart;
    register BoxPtr prectO, prectN;
    BoxPtr FirstRect, rects, prectLineStart;
    Bool fInBox, fSame;
    register FbBits mask0 = FB_ALLONES & ~FbScrRight(FB_ALLONES, 1);
    FbBits *pwLine;
    int nWidth;

    pReg = RegionCreate(NULL, 1);
    if (!pReg)
        return NullRegion;
    FirstRect = RegionBoxptr(pReg);
    rects = FirstRect;

    fbPrepareAccess(&pPix->drawable);

    pwLine = (FbBits *) pPix->devPrivate.ptr;
    nWidth = pPix->devKind >> (FB_SHIFT - 3);

    width = pPix->drawable.width;
    pReg->extents.x1 = width - 1;
    pReg->extents.x2 = 0;
    irectPrevStart = -1;
    for (h = 0; h < pPix->drawable.height; h++) {
        pw = pwLine;
        pwLine += nWidth;
        irectLineStart = rects - FirstRect;
        /* If the Screen left most bit of the word is set, we're starting in
         * a box */
        if (READ(pw) & mask0) {
            fInBox = TRUE;
            rx1 = 0;
        }
        else
            fInBox = FALSE;
        /* Process all words which are fully in the pixmap */
        pwLineEnd = pw + (width >> FB_SHIFT);
        for (base = 0; pw < pwLineEnd; base += FB_UNIT) {
            w = READ(pw++);
            if (fInBox) {
                if (!~w)
                    continue;
            }
            else {
                if (!w)
                    continue;
            }
            for (ib = 0; ib < FB_UNIT; ib++) {
                /* If the Screen left most bit of the word is set, we're
                 * starting a box */
                if (w & mask0) {
                    if (!fInBox) {
                        rx1 = base + ib;
                        /* start new box */
                        fInBox = TRUE;
                    }
                }
                else {
                    if (fInBox) {
                        /* end box */
                        ADDRECT(pReg, rects, FirstRect,
                                rx1, h, base + ib, h + 1);
                        fInBox = FALSE;
                    }
                }
                /* Shift the word VISUALLY left one. */
                w = FbScrLeft(w, 1);
            }
        }
        if (width & FB_MASK) {
            /* Process final partial word on line */
            w = READ(pw++);
            for (ib = 0; ib < (width & FB_MASK); ib++) {
                /* If the Screen left most bit of the word is set, we're
                 * starting a box */
                if (w & mask0) {
                    if (!fInBox) {
                        rx1 = base + ib;
                        /* start new box */
                        fInBox = TRUE;
                    }
                }
                else {
                    if (fInBox) {
                        /* end box */
                        ADDRECT(pReg, rects, FirstRect,
                                rx1, h, base + ib, h + 1);
                        fInBox = FALSE;
                    }
                }
                /* Shift the word VISUALLY left one. */
                w = FbScrLeft(w, 1);
            }
        }
        /* If scanline ended with last bit set, end the box */
        if (fInBox) {
            ADDRECT(pReg, rects, FirstRect,
                    rx1, h, base + (width & FB_MASK), h + 1);
        }
        /* if all rectangles on this line have the same x-coords as
         * those on the previous line, then add 1 to all the previous  y2s and 
         * throw away all the rectangles from this line 
         */
        fSame = FALSE;
        if (irectPrevStart != -1) {
            crects = irectLineStart - irectPrevStart;
            if (crects == ((rects - FirstRect) - irectLineStart)) {
                prectO = FirstRect + irectPrevStart;
                prectN = prectLineStart = FirstRect + irectLineStart;
                fSame = TRUE;
                while (prectO < prectLineStart) {
                    if ((prectO->x1 != prectN->x1) ||
                        (prectO->x2 != prectN->x2)) {
                        fSame = FALSE;
                        break;
                    }
                    prectO++;
                    prectN++;
                }
                if (fSame) {
                    prectO = FirstRect + irectPrevStart;
                    while (prectO < prectLineStart) {
                        prectO->y2 += 1;
                        prectO++;
                    }
                    rects -= crects;
                    pReg->data->numRects -= crects;
                }
            }
        }
        if (!fSame)
            irectPrevStart = irectLineStart;
    }
    if (!pReg->data->numRects)
        pReg->extents.x1 = pReg->extents.x2 = 0;
    else {
        pReg->extents.y1 = RegionBoxptr(pReg)->y1;
        pReg->extents.y2 = RegionEnd(pReg)->y2;
        if (pReg->data->numRects == 1) {
            free(pReg->data);
            pReg->data = (RegDataPtr) NULL;
        }
    }

    fbFinishAccess(&pPix->drawable);
#ifdef DEBUG
    if (!RegionIsValid(pReg))
        FatalError("Assertion failed file %s, line %d: expr\n", __FILE__,
                   __LINE__);
#endif
    return pReg;
}

#ifdef FB_DEBUG

#ifndef WIN32
#include <stdio.h>
#else
#include <dbg.h>
#endif

static Bool
fbValidateBits(FbStip * bits, int stride, FbStip data)
{
    while (stride--) {
        if (*bits != data) {
#ifdef WIN32
            NCD_DEBUG((DEBUG_FAILURE,
                       "fdValidateBits failed at 0x%x (is 0x%x want 0x%x)",
                       bits, *bits, data));
#else
            fprintf(stderr, "fbValidateBits failed\n");
#endif
            return FALSE;
        }
        bits++;
    }
}

void
fbValidateDrawable(DrawablePtr pDrawable)
{
    FbStip *bits, *first, *last;
    int stride, bpp;
    int xoff, yoff;
    int height;
    Bool failed;

    if (pDrawable->type != DRAWABLE_PIXMAP)
        pDrawable = (DrawablePtr) fbGetWindowPixmap(pDrawable);
    fbGetStipDrawable(pDrawable, bits, stride, bpp, xoff, yoff);
    first = bits - stride;
    last = bits + stride * pDrawable->height;
    if (!fbValidateBits(first, stride, FB_HEAD_BITS) ||
        !fbValidateBits(last, stride, FB_TAIL_BITS))
        fbInitializeDrawable(pDrawable);
    fbFinishAccess(pDrawable);
}

void
fbSetBits(FbStip * bits, int stride, FbStip data)
{
    while (stride--)
        *bits++ = data;
}

void
fbInitializeDrawable(DrawablePtr pDrawable)
{
    FbStip *bits, *first, *last;
    int stride, bpp;
    int xoff, yoff;

    fbGetStipDrawable(pDrawable, bits, stride, bpp, xoff, yoff);
    first = bits - stride;
    last = bits + stride * pDrawable->height;
    fbSetBits(first, stride, FB_HEAD_BITS);
    fbSetBits(last, stride, FB_TAIL_BITS);
    fbFinishAccess(pDrawable);
}
#endif                          /* FB_DEBUG */
