/*
 * COPYRIGHT:         See COPYING in the top level directory
 * PROJECT:           ReactOS win32 subsystem
 * PURPOSE:           BRUSH class implementation
 * PROGRAMER:         Timo Kreuzer (timo.kreuzer@reactos.org)
 *
 * REFERENCES:        http://support.microsoft.com/kb/kbview/108497
 */

#include "brush.hpp"

DBG_DEFAULT_CHANNEL(GdiBrush);

BRUSH::BRUSH(
    _In_ FLONG flAttrs,
    _In_ COLORREF crColor,
    _In_ ULONG iHatch,
    _In_opt_ HBITMAP hbmPattern,
    _In_opt_ PVOID pvClient,
    _In_ GDILOOBJTYPE loobjtype = GDILoObjType_LO_BRUSH_TYPE)
    : BASEOBJECT(loobjtype)
{
    static ULONG ulGlobalBrushUnique = 0;

    /* Get a unique value */
    this->ulBrushUnique = InterlockedIncrementUL(&ulGlobalBrushUnique);

    /* Start with kmode brush attribute */
    this->pBrushAttr = &this->BrushAttr;

    /* Set parameters */
    this->flAttrs = flAttrs;
    this->iHatch = iHatch;
    this->hbmPattern = hbmPattern;
    this->hbmClient = (HBITMAP)pvClient;
    this->pBrushAttr->lbColor = crColor;

    /* Initialize the other fields */
    this->ptOrigin.x = 0;
    this->ptOrigin.y = 0;
    this->bCacheGrabbed = FALSE;
    this->crBack = 0;
    this->crFore = 0;
    this->ulPalTime = 0;
    this->ulSurfTime = 0;
    this->pvRBrush = NULL;
    this->hdev = NULL;

    /* FIXME: should be done only in PEN constructor,
       but our destructor needs it! */
    this->dwStyleCount = 0;
    this->pStyle = NULL;
}

BRUSH::~BRUSH(
    VOID)
{
    /* Check if we have a user mode brush attribute */
    if (this->pBrushAttr != &this->BrushAttr)
    {
        /* Free memory to the process GDI pool */
        GdiPoolFree(GetBrushAttrPool(), this->pBrushAttr);
    }

    /* Delete the pattern bitmap (may have already been deleted during gdi cleanup) */
    if (this->hbmPattern != NULL && GreIsHandleValid(this->hbmPattern))
    {
        GreSetBitmapOwner(this->hbmPattern, BASEOBJECT::OWNER::POWNED);
        GreDeleteObject(this->hbmPattern);
    }

    /* Delete styles */
    if ((this->pStyle != NULL) && !(this->flAttrs & BR_IS_DEFAULTSTYLE))
    {
        ExFreePoolWithTag(this->pStyle, GDITAG_PENSTYLE);
    }
}

VOID
BRUSH::vDeleteObject(
    _In_ PVOID pvObject)
{
    PBRUSH pbr = static_cast<PBRUSH>(pvObject);
    NT_ASSERT((GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_BRUSH_TYPE) ||
              (GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_PEN_TYPE) ||
              (GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_EXTPEN_TYPE));
    delete pbr;
}

BOOL
BRUSH::bAllocateBrushAttr(
    VOID)
{
    PBRUSH_ATTR pBrushAttr;
    NT_ASSERT(this->pBrushAttr == &this->BrushAttr);

    /* Allocate a brush attribute from the pool */
    pBrushAttr = static_cast<PBRUSH_ATTR>(GdiPoolAllocate(GetBrushAttrPool()));
    if (pBrushAttr == NULL)
    {
        ERR("Could not allocate brush attr\n");
        return FALSE;
    }

    /* Copy the content from the kernel mode brush attribute */
    this->pBrushAttr = pBrushAttr;
    *this->pBrushAttr = this->BrushAttr;

    /* Set the object attribute in the handle table */
    vSetObjectAttr(pBrushAttr);

    return TRUE;
}

VOID
BRUSH::vSetSolidColor(
    _In_ COLORREF crColor)
{
    NT_ASSERT(this->flAttrs & BR_IS_SOLID);

    /* Set new color and reset the pal times */
    this->pBrushAttr->lbColor = crColor & 0xFFFFFF;
    this->ulPalTime = -1;
    this->ulSurfTime = -1;
}

HBITMAP
BRUSH::hbmGetBitmapHandle(
    _Out_ PUINT puUsage) const
{
    /* Return the color usage based on flags */
    *puUsage = (this->flAttrs & BR_IS_DIBPALCOLORS) ? DIB_PAL_COLORS :
               (this->flAttrs & BR_IS_DIBPALINDICES) ? DIB_PAL_INDICES :
               DIB_RGB_COLORS;

    return this->hbmPattern;
}

UINT
BRUSH::cjGetObject(
    _In_ UINT cjSize,
    _Out_bytecap_(cjSize) PLOGBRUSH plb) const
{
    /* Check if only size is requested */
    if (plb == NULL)
        return sizeof(LOGBRUSH);

    /* Check if size is ok */
    if (cjSize == 0)
        return 0;

    /* Set color */
    plb->lbColor = this->BrushAttr.lbColor;

    /* Set style and hatch based on the attribute flags */
    if (this->flAttrs & BR_IS_SOLID)
    {
        plb->lbStyle = BS_SOLID;
        plb->lbHatch = 0;
    }
    else if (this->flAttrs & BR_IS_HATCH)
    {
        plb->lbStyle = BS_HATCHED;
        plb->lbHatch = this->iHatch;
    }
    else if (this->flAttrs & BR_IS_DIB)
    {
        plb->lbStyle = BS_DIBPATTERN;
        plb->lbHatch = (ULONG_PTR)this->hbmClient;
    }
    else if (this->flAttrs & BR_IS_BITMAP)
    {
        plb->lbStyle = BS_PATTERN;
        plb->lbHatch = (ULONG_PTR)this->hbmClient;
    }
    else if (this->flAttrs & BR_IS_NULL)
    {
        plb->lbStyle = BS_NULL;
        plb->lbHatch = 0;
    }
    else
    {
        NT_ASSERT(FALSE);
    }

    return sizeof(LOGBRUSH);
}

static
HBRUSH
CreateBrushInternal(
    _In_ ULONG flAttrs,
    _In_ COLORREF crColor,
    _In_ ULONG iHatch,
    _In_opt_ HBITMAP hbmPattern,
    _In_opt_ PVOID pvClient)
{
    BASEOBJECT::OWNER owner;
    PBRUSH pbr;
    HBRUSH hbr;

    NT_ASSERT(((flAttrs & BR_IS_BITMAP) == 0) || (hbmPattern != NULL));

    /* Create the brush (brush takes ownership of the bitmap) */
    pbr = new BRUSH(flAttrs, crColor, iHatch, hbmPattern, pvClient);
    if (pbr == NULL)
    {
        ERR("Failed to allocate a brush\n");
        GreSetBitmapOwner(hbmPattern, BASEOBJECT::OWNER::POWNED);
        GreDeleteObject(hbmPattern);
        return NULL;
    }

    /* Check if this is a global brush */
    if (!(flAttrs & BR_IS_GLOBAL))
    {
        /* Not a global brush, so allocate a user mode brush attribute */
        if (!pbr->bAllocateBrushAttr())
        {
            ERR("Failed to allocate brush attribute\n");
            delete pbr;
            return NULL;
        }
    }

    /* Set the owner, either public or process owned */
    owner = (flAttrs & BR_IS_GLOBAL) ? BASEOBJECT::OWNER::PUBLIC :
                                       BASEOBJECT::OWNER::POWNED;

    /* Insert the object into the GDI handle table */
    hbr =  static_cast<HBRUSH>(pbr->hInsertObject(owner));
    if (hbr == NULL)
    {
        ERR("Failed to insert brush\n");
        delete pbr;
        return NULL;
    }

    /* Unlock the brush */
    pbr->vUnlock();

    return hbr;
}


/* C interface ***************************************************************/

extern "C" {

VOID
NTAPI
BRUSH_vDeleteObject(
    PVOID pvObject)
{
    BRUSH::vDeleteObject(pvObject);
}

INT
FASTCALL
BRUSH_GetObject(
    PBRUSH pbr,
    INT cjBuffer,
    LPLOGBRUSH plbBuffer)
{
    return pbr->cjGetObject(cjBuffer, plbBuffer);
}

HBRUSH
NTAPI
IntGdiCreateNullBrush(
    VOID)
{
    /* Call the internal function */
    return CreateBrushInternal(BR_IS_NULL | BR_IS_GLOBAL, 0, 0, NULL, NULL);
}

HBRUSH
APIENTRY
IntGdiCreateSolidBrush(
    COLORREF crColor)
{
    /* Call the internal function */
    return CreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL,
                               crColor,
                               0,
                               NULL,
                               NULL);
}

HBRUSH
NTAPI
IntGdiCreatePatternBrush(
    HBITMAP hbmPattern)
{
    NT_ASSERT(hbmPattern != NULL);
    GreSetBitmapOwner(hbmPattern, BASEOBJECT::OWNER::PUBLIC);
    return CreateBrushInternal(BR_IS_BITMAP | BR_IS_GLOBAL,
                               0,
                               0,
                               hbmPattern,
                               NULL);
}

VOID
NTAPI
IntGdiSetSolidBrushColor(
    _In_ HBRUSH hbr,
    _In_ COLORREF crColor)
{
    PBRUSH pbr;

    /* Lock the brush */
    pbr = BRUSH::LockAny(hbr);
    if (pbr == NULL)
    {
        ERR("Failed to lock brush %p\n", hbr);
        return;
    }

    /* Call the member function */
    pbr->vSetSolidColor(crColor);

    /* Unlock the brush */
    pbr->vUnlock();
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiCreateSolidBrush(
    _In_ COLORREF crColor,
    _In_opt_ HBRUSH hbr)
{
    if (hbr != NULL)
    {
        WARN("hbr is not supported, ignoring\n");
    }

    /* Call the internal function */
    return CreateBrushInternal(BR_IS_SOLID, crColor, 0, NULL, NULL);
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiCreateHatchBrushInternal(
    _In_ ULONG iHatch,
    _In_ COLORREF crColor,
    _In_ BOOL bPen)
{
    FLONG flAttr;

    if (bPen)
    {
        WARN("bPen is not supported, ignoring\n");
    }

    /* Check what kind if hatch style this is */
    if (iHatch < HS_DDI_MAX)
    {
        flAttr = BR_IS_HATCH;
    }
    else if (iHatch < HS_API_MAX)
    {
        flAttr = BR_IS_SOLID;
    }
    else
    {
        ERR("Invalid iHatch: %lu\n", iHatch);
        return NULL;
    }

    /* Call the internal function */
    return CreateBrushInternal(flAttr, crColor, iHatch, NULL, NULL);
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiCreatePatternBrushInternal(
    _In_ HBITMAP hbmClient,
    _In_ BOOL bPen,
    _In_ BOOL b8X8)
{
    HBITMAP hbmPattern;

    if (b8X8)
    {
        WARN("b8X8 is not supported, ignoring\n");
    }

    if (bPen)
    {
        WARN("bPen is not supported, ignoring\n");
    }

    /* Copy the bitmap */
    hbmPattern = BITMAP_CopyBitmap(hbmClient);
    if (hbmPattern == NULL)
    {
        ERR("Failed to copy the bitmap %p\n", hbmPattern);
        return NULL;
    }

    /* Call the internal function (will delete hbmPattern on failure) */
    return CreateBrushInternal(BR_IS_BITMAP, 0, 0, hbmPattern, hbmClient);
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiCreateDIBBrush(
    _In_reads_bytes_(cj) PVOID pv,
    _In_ FLONG uUsage,
    _In_ UINT cj,
    _In_ BOOL b8X8,
    _In_ BOOL bPen,
    _In_ PVOID pvClient)
{
    PVOID pvPackedDIB;
    FLONG flAttrs;
    HBITMAP hbm;
    HBRUSH hbr = NULL;

    if (b8X8)
    {
        WARN("b8X8 is not supported, ignoring\n");
    }

    if (bPen)
    {
        WARN("bPen is not supported, ignoring\n");
    }

    if (uUsage > DIB_PAL_INDICES)
    {
        ERR("Invalid uUsage value: %lu\n", uUsage);
        EngSetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    /* Allocate a buffer for the packed DIB */
    pvPackedDIB = ExAllocatePoolWithTag(PagedPool, cj, GDITAG_TEMP);
    if (pvPackedDIB == NULL)
    {
        ERR("Failed to allocate temp buffer of %u bytes\n", cj);
        return NULL;
    }

    /* Probe and copy the packed DIB */
    _SEH2_TRY
    {
        ProbeForRead(pv, cj, 1);
        RtlCopyMemory(pvPackedDIB, pv, cj);
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        ERR("Got exception, pv = %p, cj = %lu\n", pv, cj);
        goto cleanup;
    }
    _SEH2_END;

    flAttrs = BR_IS_BITMAP | BR_IS_DIB;

    /* Check what kind of color table we have */
    if (uUsage == DIB_PAL_COLORS)
    {
        /* Remember it and use DIB_PAL_BRUSHHACK to create a "special" palette */
        flAttrs |= BR_IS_DIBPALCOLORS;
        uUsage = DIB_PAL_BRUSHHACK;
    }
    else if (uUsage == DIB_PAL_INDICES)
    {
        /* No color table, bitmap contains device palette indices */
        flAttrs |= BR_IS_DIBPALINDICES;

        /* FIXME: This makes tests pass, but needs investigation. */
        flAttrs |= BR_IS_NULL;
    }

    /* Create a bitmap from the DIB */
    hbm = GreCreateDIBitmapFromPackedDIB(pvPackedDIB, cj, uUsage);
    if (hbm == NULL)
    {
        ERR("Failed to create bitmap from DIB\n");
        goto cleanup;
    }

    /* Call the internal function (will delete hbm on failure) */
    hbr = CreateBrushInternal(flAttrs, 0, 0, hbm, pvClient);

cleanup:

    ExFreePoolWithTag(pvPackedDIB, GDITAG_TEMP);

    return hbr;
}

__kernel_entry
HBITMAP
APIENTRY
NtGdiGetObjectBitmapHandle(
    _In_ HBRUSH hbr,
    _Out_ UINT *piUsage)
{
    PBRUSH pbr;
    HBITMAP hbm;
    UINT uUsage;

    /* Lock the brush */
    pbr = BRUSH::LockForRead(hbr);
    if (pbr == NULL)
    {
        ERR("Failed to lock brush %p\n", hbr);
        return NULL;
    }

    /* Call the member function */
    hbm = pbr->hbmGetBitmapHandle(&uUsage);

    /* Unlock the brush */
    pbr->vUnlock();

    _SEH2_TRY
    {
        ProbeForWrite(piUsage, sizeof(*piUsage), 1);
        *piUsage = uUsage;
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
        ERR("Got exception! piUsage = %p\n", piUsage);
        hbm = NULL;
    }
    _SEH2_END;

    return hbm;
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiSetBrushAttributes(
    _In_ HBRUSH hbr,
    _In_ DWORD dwFlags)
{
    FIXME("NtGdiSetBrushAttributes is unimplemented\n");
    return NULL;
}

__kernel_entry
HBRUSH
APIENTRY
NtGdiClearBrushAttributes(
    _In_ HBRUSH hbr,
    _In_ DWORD dwFlags)
{
    FIXME("NtGdiClearBrushAttributes is unimplemented\n");
    return NULL;
}

} /* extern "C" */