/*
 * COPYRIGHT:         See COPYING in the top level directory
 * PROJECT:           ReactOS Win32k subsystem
 * PURPOSE:           Functions for creation and destruction of DCs
 * FILE:              win32ss/gdi/ntgdi/dcobjs.c
 * PROGRAMER:         Timo Kreuzer (timo.kreuzer@rectos.org)
 */

#include <win32k.h>

#define NDEBUG
#include <debug.h>

VOID
FASTCALL
DC_vUpdateFillBrush(PDC pdc)
{
    PDC_ATTR pdcattr = pdc->pdcattr;
    PBRUSH pbrFill;

    /* Check if the brush handle has changed */
    if (pdcattr->hbrush != pdc->dclevel.pbrFill->BaseObject.hHmgr)
    {
        /* Try to lock the new brush */
        pbrFill = BRUSH_ShareLockBrush(pdcattr->hbrush);
        if (pbrFill)
        {
            /* Unlock old brush, set new brush */
            BRUSH_ShareUnlockBrush(pdc->dclevel.pbrFill);
            pdc->dclevel.pbrFill = pbrFill;

            /* Mark eboFill as dirty */
            pdcattr->ulDirty_ |= DIRTY_FILL;
        }
        else
        {
            /* Invalid brush handle, restore old one */
            pdcattr->hbrush = pdc->dclevel.pbrFill->BaseObject.hHmgr;
        }
    }

    /* Check if the EBRUSHOBJ needs update */
    if (pdcattr->ulDirty_ & DIRTY_FILL)
    {
        /* Update eboFill */
        EBRUSHOBJ_vUpdateFromDC(&pdc->eboFill, pdc->dclevel.pbrFill, pdc);
    }

    /* Check for DC brush */
    if (pdcattr->hbrush == StockObjects[DC_BRUSH])
    {
        /* Update the eboFill's solid color */
        EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboFill, pdcattr->crBrushClr);
    }

    /* Clear flags */
    pdcattr->ulDirty_ &= ~(DIRTY_FILL | DC_BRUSH_DIRTY);
}

VOID
FASTCALL
DC_vUpdateLineBrush(PDC pdc)
{
    PDC_ATTR pdcattr = pdc->pdcattr;
    PBRUSH pbrLine;

    /* Check if the pen handle has changed */
    if (pdcattr->hpen != pdc->dclevel.pbrLine->BaseObject.hHmgr)
    {
        /* Try to lock the new pen */
        pbrLine = PEN_ShareLockPen(pdcattr->hpen);
        if (pbrLine)
        {
            /* Unlock old brush, set new brush */
            BRUSH_ShareUnlockBrush(pdc->dclevel.pbrLine);
            pdc->dclevel.pbrLine = pbrLine;

            /* Mark eboLine as dirty */
            pdcattr->ulDirty_ |= DIRTY_LINE;
        }
        else
        {
            /* Invalid pen handle, restore old one */
            pdcattr->hpen = pdc->dclevel.pbrLine->BaseObject.hHmgr;
        }
    }

    /* Check if the EBRUSHOBJ needs update */
    if (pdcattr->ulDirty_ & DIRTY_LINE)
    {
        /* Update eboLine */
        EBRUSHOBJ_vUpdateFromDC(&pdc->eboLine, pdc->dclevel.pbrLine, pdc);
    }

    /* Check for DC pen */
    if (pdcattr->hpen == StockObjects[DC_PEN])
    {
        /* Update the eboLine's solid color */
        EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboLine, pdcattr->crPenClr);
    }

    /* Clear flags */
    pdcattr->ulDirty_ &= ~(DIRTY_LINE | DC_PEN_DIRTY);
}

VOID
FASTCALL
DC_vUpdateTextBrush(PDC pdc)
{
    PDC_ATTR pdcattr = pdc->pdcattr;

    /* Timo : The text brush should never be changed.
     * Jérôme : Yeah, but its palette must be updated anyway! */
    if(pdcattr->ulDirty_ & DIRTY_TEXT)
        EBRUSHOBJ_vUpdateFromDC(&pdc->eboText, pbrDefaultBrush, pdc);

    /* Update the eboText's solid color */
    EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboText, pdcattr->crForegroundClr);

    /* Clear flag */
    pdcattr->ulDirty_ &= ~DIRTY_TEXT;
}

VOID
FASTCALL
DC_vUpdateBackgroundBrush(PDC pdc)
{
    PDC_ATTR pdcattr = pdc->pdcattr;

    if(pdcattr->ulDirty_ & DIRTY_BACKGROUND)
        EBRUSHOBJ_vUpdateFromDC(&pdc->eboBackground, pbrDefaultBrush, pdc);

    /* Update the eboBackground's solid color */
    EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboBackground, pdcattr->crBackgroundClr);

    /* Clear flag */
    pdcattr->ulDirty_ &= ~DIRTY_BACKGROUND;
}

VOID
NTAPI
DC_vSetBrushOrigin(PDC pdc, LONG x, LONG y)
{
    /* Set the brush origin */
    pdc->dclevel.ptlBrushOrigin.x = x;
    pdc->dclevel.ptlBrushOrigin.y = y;

    /* Set the fill origin */
    pdc->ptlFillOrigin.x = x + pdc->ptlDCOrig.x;
    pdc->ptlFillOrigin.y = y + pdc->ptlDCOrig.y;
}

/**
 * \name NtGdiSetBrushOrg
 *
 * \brief Sets the brush origin that GDI uses when drawing with pattern
 *     brushes. The brush origin is relative to the DC origin.
 *
 * @implemented
 */
_Success_(return!=FALSE)
__kernel_entry
BOOL
APIENTRY
NtGdiSetBrushOrg(
    _In_ HDC hdc,
    _In_ INT x,
    _In_ INT y,
    _Out_opt_ LPPOINT pptOut)
{

    POINT ptOut;
                /* Call the internal function */
    BOOL  Ret = GreSetBrushOrg( hdc, x, y, &ptOut);
    if (Ret)
    {
       /* Check if the old origin was requested */
       if (pptOut != NULL)
       {
           /* Enter SEH for buffer transfer */
           _SEH2_TRY
           {
               /* Probe and copy the old origin */
               ProbeForWrite(pptOut, sizeof(POINT), 1);
               *pptOut = ptOut;
           }
           _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
           {
               _SEH2_YIELD(return FALSE);
           }
           _SEH2_END;
       }
    }
    return Ret;
}

HPALETTE
NTAPI
GdiSelectPalette(
    HDC hDC,
    HPALETTE hpal,
    BOOL ForceBackground)
{
    PDC pdc;
    HPALETTE oldPal = NULL;
    PPALETTE ppal;

    // FIXME: Mark the palette as a [fore\back]ground pal
    pdc = DC_LockDc(hDC);
    if (!pdc)
    {
        return NULL;
    }

    /* Check if this is a valid palette handle */
    ppal = PALETTE_ShareLockPalette(hpal);
    if (!ppal)
    {
        DC_UnlockDc(pdc);
        return NULL;
    }

    /// FIXME: we shouldn't dereference pSurface when the PDEV is not locked
    /* Is this a valid palette for this depth? */
	if ((!pdc->dclevel.pSurface) ||
        (BitsPerFormat(pdc->dclevel.pSurface->SurfObj.iBitmapFormat) <= 8
            && (ppal->flFlags & PAL_INDEXED)) ||
        (BitsPerFormat(pdc->dclevel.pSurface->SurfObj.iBitmapFormat) > 8))
    {
        /* Get old palette, set new one */
        oldPal = pdc->dclevel.hpal;
        pdc->dclevel.hpal = hpal;
        DC_vSelectPalette(pdc, ppal);

        /* Mark the brushes invalid */
        pdc->pdcattr->ulDirty_ |= DIRTY_FILL | DIRTY_LINE |
                                  DIRTY_BACKGROUND | DIRTY_TEXT;
    }

    if(pdc->dctype == DCTYPE_MEMORY)
    {
        // This didn't work anyway
        //IntGdiRealizePalette(hDC);
    }

    PALETTE_ShareUnlockPalette(ppal);
    DC_UnlockDc(pdc);

    return oldPal;
}

 /*
 * @implemented
 */
HBRUSH
APIENTRY
NtGdiSelectBrush(
    IN HDC hDC,
    IN HBRUSH hBrush)
{
    PDC pDC;
    HBRUSH hOrgBrush;

    if (hDC == NULL || hBrush == NULL) return NULL;

    pDC = DC_LockDc(hDC);
    if (!pDC)
    {
        return NULL;
    }

    /* Simply return the user mode value, without checking */
    hOrgBrush = pDC->pdcattr->hbrush;
    pDC->pdcattr->hbrush = hBrush;
    DC_vUpdateFillBrush(pDC);

    DC_UnlockDc(pDC);

    return hOrgBrush;
}

 /*
 * @implemented
 */
HPEN
APIENTRY
NtGdiSelectPen(
    IN HDC hDC,
    IN HPEN hPen)
{
    PDC pDC;
    HPEN hOrgPen;

    if (hDC == NULL || hPen == NULL) return NULL;

    pDC = DC_LockDc(hDC);
    if (!pDC)
    {
        return NULL;
    }

    /* Simply return the user mode value, without checking */
    hOrgPen = pDC->pdcattr->hpen;
    pDC->pdcattr->hpen = hPen;
    DC_vUpdateLineBrush(pDC);

    DC_UnlockDc(pDC);

    return hOrgPen;
}

BOOL
NTAPI
DC_bIsBitmapCompatible(PDC pdc, PSURFACE psurf)
{
    ULONG cBitsPixel;

    /* Must be an API bitmap */
    if (!(psurf->flags & API_BITMAP)) return FALSE;

    /* DIB sections are always compatible */
    if (psurf->hSecure != NULL) return TRUE;

    /* See if this is the same PDEV */
    if (psurf->SurfObj.hdev == (HDEV)pdc->ppdev)
        return TRUE;

    /* Get the bit depth of the bitmap */
    cBitsPixel = gajBitsPerFormat[psurf->SurfObj.iBitmapFormat];

    /* 1 BPP is compatible */
    if ((cBitsPixel == 1) || (cBitsPixel == pdc->ppdev->gdiinfo.cBitsPixel))
        return TRUE;

    return FALSE;
}

/*
 * @implemented
 */
HBITMAP
APIENTRY
NtGdiSelectBitmap(
    IN HDC hdc,
    IN HBITMAP hbmp)
{
    PDC pdc;
    HBITMAP hbmpOld;
    PSURFACE psurfNew, psurfOld;
    HDC hdcOld;
    ASSERT_NOGDILOCKS();

    /* Verify parameters */
    if (hdc == NULL || hbmp == NULL) return NULL;

    /* First lock the DC */
    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        return NULL;
    }

    /* Must be a memory dc to select a bitmap */
    if (pdc->dctype != DC_TYPE_MEMORY)
    {
        DC_UnlockDc(pdc);
        return NULL;
    }

    /* Save the old bitmap */
    psurfOld = pdc->dclevel.pSurface;

    /* Check if there is a bitmap selected */
    if (psurfOld)
    {
        /* Get the old bitmap's handle */
        hbmpOld = psurfOld->BaseObject.hHmgr;
    }
    else
    {
        /* Use the default bitmap */
        hbmpOld = StockObjects[DEFAULT_BITMAP];
    }

    /* Check if the new bitmap is already selected */
    if (hbmp == hbmpOld)
    {
        /* Unlock the DC and return the old bitmap */
        DC_UnlockDc(pdc);
        return hbmpOld;
    }

    /* Check if the default bitmap was passed */
    if (hbmp == StockObjects[DEFAULT_BITMAP])
    {
        psurfNew = NULL;

        /* Default bitmap is 1x1 pixel */
        pdc->dclevel.sizl.cx = 1;
        pdc->dclevel.sizl.cy = 1;
    }
    else
    {
        /* Reference the new bitmap and check if it's valid */
        psurfNew = SURFACE_ShareLockSurface(hbmp);
        if (!psurfNew)
        {
            DC_UnlockDc(pdc);
            return NULL;
        }

        /* Check if the bitmap is compatible with the dc */
        if (!DC_bIsBitmapCompatible(pdc, psurfNew))
        {
            /* Dereference the bitmap, unlock the DC and fail. */
            SURFACE_ShareUnlockSurface(psurfNew);
            DC_UnlockDc(pdc);
            return NULL;
        }

        /* Set the bitmap's hdc and check if it was set before */
        hdcOld = InterlockedCompareExchangePointer((PVOID*)&psurfNew->hdc, hdc, 0);
        if (hdcOld != NULL)
        {
            /* The bitmap is already selected into a different DC */
            ASSERT(hdcOld != hdc);

            /* Dereference the bitmap, unlock the DC and fail. */
            SURFACE_ShareUnlockSurface(psurfNew);
            DC_UnlockDc(pdc);
            return NULL;
        }

        /* Copy the bitmap size */
        pdc->dclevel.sizl = psurfNew->SurfObj.sizlBitmap;

        /* Check if the bitmap is a dibsection */
        if (psurfNew->hSecure)
        {
            /* Set DIBSECTION attribute */
            pdc->pdcattr->ulDirty_ |= DC_DIBSECTION;
        }
        else
        {
            /* Remove DIBSECTION attribute */
            pdc->pdcattr->ulDirty_ &= ~DC_DIBSECTION;
        }
    }

    /* Select the new bitmap */
    pdc->dclevel.pSurface = psurfNew;

    /* Check if there was a bitmap selected before */
    if (psurfOld)
    {
        /* Reset hdc of the old bitmap, it isn't selected anymore */
        psurfOld->hdc = NULL;

        /* Dereference the old bitmap */
        SURFACE_ShareUnlockSurface(psurfOld);
    }

    /* Mark the DC brushes and the RAO region invalid */
    pdc->pdcattr->ulDirty_ |= DIRTY_FILL | DIRTY_LINE;
    pdc->fs |= DC_FLAG_DIRTY_RAO;

    /* Update the system region */
    REGION_SetRectRgn(pdc->prgnVis,
                      0,
                      0,
                      pdc->dclevel.sizl.cx,
                      pdc->dclevel.sizl.cy);

    /* Unlock the DC */
    DC_UnlockDc(pdc);

    /* Return the old bitmap handle */
    return hbmpOld;
}


BOOL
APIENTRY
NtGdiSelectClipPath(
    HDC hDC,
    int Mode)
{
    PREGION  RgnPath;
    PPATH pPath, pNewPath;
    BOOL  success = FALSE;
    PDC_ATTR pdcattr;
    PDC pdc;

    pdc = DC_LockDc(hDC);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    pdcattr = pdc->pdcattr;

    pPath = PATH_LockPath(pdc->dclevel.hPath);
    if (!pPath)
    {
        DC_UnlockDc(pdc);
        return FALSE;
    }

    /* Check that path is closed */
    if (pPath->state != PATH_Closed)
    {
        EngSetLastError(ERROR_CAN_NOT_COMPLETE);
        success = FALSE;
        goto Exit;
    }

    /* Construct a region from the path */
    RgnPath = IntSysCreateRectpRgn(0, 0, 0, 0);
    if (!RgnPath)
    {
        EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
        DC_UnlockDc(pdc);
        return FALSE;
    }

    pNewPath = PATH_FlattenPath(pPath);

    success = PATH_PathToRegion(pNewPath, pdcattr->jFillMode, RgnPath);

    PATH_UnlockPath(pNewPath);
    PATH_Delete(pNewPath->BaseObject.hHmgr);

    if (success) success = IntGdiExtSelectClipRgn(pdc, RgnPath, Mode) != ERROR;

    REGION_Delete(RgnPath);

Exit:
    PATH_UnlockPath(pPath);
    PATH_Delete(pdc->dclevel.hPath);
    pdc->dclevel.flPath &= ~DCPATH_ACTIVE;
    pdc->dclevel.hPath = NULL;

    DC_UnlockDc(pdc);

    return success;
}

HFONT
NTAPI
DC_hSelectFont(
    _In_ PDC pdc,
    _In_ HFONT hlfntNew)
{
    PLFONT plfntNew;
    HFONT hlfntOld;

    // Legacy crap that will die with font engine rewrite
    if (!NT_SUCCESS(TextIntRealizeFont(hlfntNew, NULL)))
    {
        return NULL;
    }

    /* Get the current selected font */
    hlfntOld = pdc->dclevel.plfnt->BaseObject.hHmgr;

    /* Check if a new font should be selected */
    if (hlfntNew != hlfntOld)
    {
        /* Lock the new font */
        plfntNew = LFONT_ShareLockFont(hlfntNew);
        if (plfntNew)
        {
            /* Success, dereference the old font */
            LFONT_ShareUnlockFont(pdc->dclevel.plfnt);

            /* Select the new font */
            pdc->dclevel.plfnt = plfntNew;
            pdc->pdcattr->hlfntNew = hlfntNew;

            /* Update dirty flags */
            pdc->pdcattr->ulDirty_ |= DIRTY_CHARSET;
            pdc->pdcattr->ulDirty_ &= ~SLOW_WIDTHS;
        }
        else
        {
            /* Failed, restore old, return NULL */
            pdc->pdcattr->hlfntNew = hlfntOld;
            hlfntOld = NULL;
        }
    }

    return hlfntOld;
}

HFONT
APIENTRY
NtGdiSelectFont(
    _In_ HDC hdc,
    _In_ HFONT hfont)
{
    HFONT hfontOld;
    PDC pdc;

    /* Check parameters */
    if ((hdc == NULL) || (hfont == NULL))
    {
        return NULL;
    }

    /* Lock the DC */
    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        return NULL;
    }

    /* Call the internal function */
    hfontOld = DC_hSelectFont(pdc, hfont);

    /* Unlock the DC */
    DC_UnlockDc(pdc);

    /* Return the previously selected font */
    return hfontOld;
}

HANDLE
APIENTRY
NtGdiGetDCObject(HDC hDC, INT ObjectType)
{
    HGDIOBJ SelObject;
    DC *pdc;
    PDC_ATTR pdcattr;

    /* From Wine: GetCurrentObject does not SetLastError() on a null object */
    if(!hDC) return NULL;

    if(!(pdc = DC_LockDc(hDC)))
    {
        return NULL;
    }
    pdcattr = pdc->pdcattr;

    if (pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY))
        DC_vUpdateFillBrush(pdc);

    if (pdcattr->ulDirty_ & (DIRTY_LINE | DC_PEN_DIRTY))
        DC_vUpdateLineBrush(pdc);

    switch(ObjectType)
    {
        case GDI_OBJECT_TYPE_EXTPEN:
        case GDI_OBJECT_TYPE_PEN:
            SelObject = pdcattr->hpen;
            break;

        case GDI_OBJECT_TYPE_BRUSH:
            SelObject = pdcattr->hbrush;
            break;

        case GDI_OBJECT_TYPE_PALETTE:
            SelObject = pdc->dclevel.hpal;
            break;

        case GDI_OBJECT_TYPE_FONT:
            SelObject = pdcattr->hlfntNew;
            break;

        case GDI_OBJECT_TYPE_BITMAP:
        {
            SURFACE *psurf = pdc->dclevel.pSurface;
            SelObject = psurf ? psurf->BaseObject.hHmgr : StockObjects[DEFAULT_BITMAP];
            break;
        }

        case GDI_OBJECT_TYPE_COLORSPACE:
            DPRINT1("FIXME: NtGdiGetCurrentObject() ObjectType OBJ_COLORSPACE not supported yet!\n");
            // SelObject = dc->dclevel.pColorSpace.BaseObject.hHmgr; ?
            SelObject = NULL;
            break;

        default:
            SelObject = NULL;
            EngSetLastError(ERROR_INVALID_PARAMETER);
            break;
    }

    DC_UnlockDc(pdc);
    return SelObject;
}

/* See WINE, MSDN, OSR and Feng Yuan - Windows Graphics Programming Win32 GDI and DirectDraw
 *
 * 1st: http://www.codeproject.com/gdi/cliprgnguide.asp is wrong!
 *
 * The intersection of the clip with the meta region is not Rao it's API!
 * Go back and read 7.2 Clipping pages 418-19:
 * Rao = API & Vis:
 * 1) The Rao region is the intersection of the API region and the system region,
 *    named after the Microsoft engineer who initially proposed it.
 * 2) The Rao region can be calculated from the API region and the system region.
 *
 * API:
 *    API region is the intersection of the meta region and the clipping region,
 *    clearly named after the fact that it is controlled by GDI API calls.
 */
INT
APIENTRY
NtGdiGetRandomRgn(
    HDC hdc,
    HRGN hrgnDest,
    INT iCode)
{
    INT ret = 0;
    PDC pdc;
    PREGION prgnSrc = NULL;

    pdc = DC_LockDc(hdc);
    if (!pdc)
    {
        EngSetLastError(ERROR_INVALID_HANDLE);
        return -1;
    }

    switch (iCode)
    {
        case CLIPRGN:
            prgnSrc = pdc->dclevel.prgnClip;
            break;

        case METARGN:
            prgnSrc = pdc->dclevel.prgnMeta;
            break;

        case APIRGN:
            if (pdc->fs & DC_FLAG_DIRTY_RAO)
                CLIPPING_UpdateGCRegion(pdc);
            if (pdc->prgnAPI)
            {
                prgnSrc = pdc->prgnAPI;
            }
            else if (pdc->dclevel.prgnClip)
            {
                prgnSrc = pdc->dclevel.prgnClip;
            }
            else if (pdc->dclevel.prgnMeta)
            {
                prgnSrc = pdc->dclevel.prgnMeta;
            }
            break;

        case SYSRGN:
            prgnSrc = pdc->prgnVis;
            break;

        default:
            break;
    }

    if (prgnSrc)
    {
        PREGION prgnDest = REGION_LockRgn(hrgnDest);
        if (prgnDest)
        {
            ret = IntGdiCombineRgn(prgnDest, prgnSrc, 0, RGN_COPY) == ERROR ? -1 : 1;
            if ((ret == 1) && (iCode == SYSRGN))
            {
                /// \todo FIXME This is not really correct, since we already modified the region
                ret = REGION_bOffsetRgn(prgnDest, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y);
            }
            REGION_UnlockRgn(prgnDest);
        }
        else
            ret = -1;
    }

    DC_UnlockDc(pdc);

    return ret;
}

ULONG
APIENTRY
NtGdiEnumObjects(
    IN HDC hdc,
    IN INT iObjectType,
    IN ULONG cjBuf,
    OUT OPTIONAL PVOID pvBuf)
{
    UNIMPLEMENTED;
    return 0;
}

/* EOF */