/*		DirectDrawClipper implementation
 *
 * Copyright 2000 Marcus Meissner
 * Copyright 2000-2003 TransGaming Technologies Inc.
 *
 * Contributions: 
 *   Copyright 2003 Jarmo Nikkanen (Implementation of DirectDrawClipper)
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include "ddraw.h"
#include "winerror.h"

#include "ddraw_private.h"
#include "dclipper/main.h"
#include "ddraw/main.h"

#include "wine/debug.h"

#include "wingdi.h"
#include "winuser.h"
#include "win.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/******************************************************************************
 *			DirectDrawCreateClipper (DDRAW.@)
 */

static ICOM_VTABLE(IDirectDrawClipper) DDRAW_Clipper_VTable;

HRESULT WINAPI DirectDrawCreateClipper(
    DWORD dwFlags, LPDIRECTDRAWCLIPPER *lplpDDClipper, LPUNKNOWN pUnkOuter
) {
    IDirectDrawClipperImpl* This;
    TRACE("(%08lx,%p,%p)\n", dwFlags, lplpDDClipper, pUnkOuter);

    if (pUnkOuter != NULL) return CLASS_E_NOAGGREGATION;

    This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
		     sizeof(IDirectDrawClipperImpl));
    if (This == NULL) return E_OUTOFMEMORY;

    ICOM_INIT_INTERFACE(This, IDirectDrawClipper, DDRAW_Clipper_VTable);
    This->ref = 1;
    This->hWnd = 0;
    This->ddraw_owner = NULL;
    This->rgncopy = 0;

    *lplpDDClipper = ICOM_INTERFACE(This, IDirectDrawClipper);
    return DD_OK;
}

/* This is the classfactory implementation. */
HRESULT DDRAW_CreateDirectDrawClipper(IUnknown* pUnkOuter, REFIID riid,
				      LPVOID* ppObj)
{
    HRESULT hr;
    LPDIRECTDRAWCLIPPER pClip;

    hr = DirectDrawCreateClipper(0, &pClip, pUnkOuter);
    if (FAILED(hr)) return hr;

    hr = IDirectDrawClipper_QueryInterface(pClip, riid, ppObj);
    IDirectDrawClipper_Release(pClip);
    return hr;
}

/******************************************************************************
 *			IDirectDrawClipper
 */
HRESULT WINAPI Main_DirectDrawClipper_SetHWnd(
    LPDIRECTDRAWCLIPPER iface, DWORD dwFlags, HWND hWnd
) {
    WND *wnd;
    HRGN client;
    ICOM_THIS(IDirectDrawClipperImpl,iface);

    TRACE("(%p)->(0x%08lx,0x%08lx)\n",This,dwFlags,(DWORD)hWnd);
    if( dwFlags ) {
      TRACE("dwFlags = 0x%08lx, not supported.\n",dwFlags);
      return DDERR_INVALIDPARAMS;
    }

    This->hWnd = hWnd;
    if (!hWnd) {
      return DDERR_INVALIDPARAMS;
    }
    
    /* get the region which corresponds to this window handle */
    wnd = WIN_GetPtr( hWnd );
    if (!wnd) {
      return DDERR_INVALIDPARAMS;
    }
    client = CreateRectRgnIndirect(&wnd->rectClient);
    if (!client) {
      WIN_ReleasePtr( wnd );
      return DDERR_INVALIDCLIPLIST;
    }

    if (This->rgncopy) {
      DeleteObject(This->rgncopy);
    }
    This->rgncopy = client;
 
    WIN_ReleasePtr( wnd );
    return DD_OK;
}

static void Main_DirectDrawClipper_Destroy(IDirectDrawClipperImpl* This)
{
    if (This->ddraw_owner != NULL)
	Main_DirectDraw_RemoveClipper(This->ddraw_owner, This);

    if (This->rgncopy) DeleteObject(This->rgncopy);
    This->rgncopy = 0;

    HeapFree(GetProcessHeap(), 0 ,This);
}

void Main_DirectDrawClipper_ForceDestroy(IDirectDrawClipperImpl* This)
{
    WARN("deleting clipper %p with refcnt %lu\n", This, This->ref);
    Main_DirectDrawClipper_Destroy(This);
}

ULONG WINAPI Main_DirectDrawClipper_Release(LPDIRECTDRAWCLIPPER iface) {
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->() decrementing from %lu.\n", This, This->ref );

    if (--This->ref == 0)
    {
	Main_DirectDrawClipper_Destroy(This);
	return 0;
    }
    else return This->ref;
}


HRESULT WINAPI Main_DirectDrawClipper_GetClipList(
    LPDIRECTDRAWCLIPPER iface,LPRECT prcClip,LPRGNDATA lprgn,LPDWORD pdwSize
) {
    int ret=DD_OK;
    WND *wnd=NULL;
    HRGN dst=0;
    HRGN hRgn=0;
    ICOM_THIS(IDirectDrawClipperImpl,iface);

    TRACE("(%p)->(%p, %p, %p)\n", This, prcClip, lprgn, pdwSize);

    if (This->hWnd) { /* Window association */
        static BOOL warned=FALSE;

        wnd = WIN_GetPtr( This->hWnd );   
        if (!wnd) { ERR("Critical\n"); ret=DDERR_GENERIC; goto release; }

        if (!warned && (TRACE_ON(ddraw) || WARN_ON(ddraw))) {
          
     /* This software is currently using client rect for a window content.
      * a Visible region of the client area should be used instead of this.
      * It seems that the driver is not yet supporting clipping, because
      * the clipping region and visible region of DC is unchanged even if
      * another window is overlapping it.
      */
          FIXME("Clipping region missing, Using client rect for now\n");
          warned=TRUE;
        }
        
        hRgn = CreateRectRgnIndirect(&wnd->rectClient);
        
        if (hRgn==0) { ret=DDERR_INVALIDCLIPLIST; goto release; }
       
        if (!prcClip) {
            *pdwSize = GetRegionData(hRgn, *pdwSize, lprgn);
            if (*pdwSize==0) { 
	      WARN("provided region is too small or an error has occured\n"); 
	      ret = DDERR_REGIONTOOSMALL; 
	      goto release;
	    }
        } else { /* if (prcClip) */
	    int r;
            dst = CreateRectRgnIndirect(prcClip);
            r = CombineRgn(dst, dst, hRgn, RGN_AND);
            if (r==ERROR || r==NULLREGION) {
              ret = DDERR_INVALIDCLIPLIST;
              goto release;
            }
            *pdwSize = GetRegionData(dst, *pdwSize, lprgn);
            if (*pdwSize==0) { 
	      WARN("provided region is too small or an error has occured\n"); 
	      ret = DDERR_REGIONTOOSMALL; 
	      goto release; 
	    }
        }

    } /* Window association */
    else {

	if (This->rgncopy==0) {
	  *pdwSize = 0; 
	  ret = DDERR_NOCLIPLIST;
	  goto release;
	}

	if (prcClip==NULL) {
	  *pdwSize = GetRegionData(This->rgncopy, *pdwSize, lprgn);
	  if (*pdwSize==0) { 
	    WARN("provided region is too small or an error has occured\n"); 
	    ret = DDERR_REGIONTOOSMALL; 
	    goto release;
	  }
	} else { /* if (prcClip!=NULL) */
	  int r;
	  dst = CreateRectRgnIndirect(prcClip);
	  r = CombineRgn(dst, dst, This->rgncopy, RGN_AND);
	  if (r==ERROR || r==NULLREGION) {
	    ret = DDERR_INVALIDCLIPLIST;
	    goto release;
	  }
	  *pdwSize = GetRegionData(dst, *pdwSize, lprgn);
	  if (*pdwSize==0) { 
	    WARN("provided region is too small or an error has occured\n"); 
	    ret = DDERR_REGIONTOOSMALL; 
	    goto release; 
	  }
	}
    }
    /* fall through */

release:
    if (hRgn) DeleteObject(hRgn);
    if (dst) DeleteObject(dst);
    if (wnd) WIN_ReleasePtr(wnd);
    return ret;
}

HRESULT WINAPI Main_DirectDrawClipper_SetClipList(
    LPDIRECTDRAWCLIPPER iface,LPRGNDATA lprgn,DWORD pdwSize
) {
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->(%p,%ld)\n",This,lprgn,pdwSize);

    if (This->hWnd) return DDERR_CLIPPERISUSINGHWND;
    if (This->rgncopy) DeleteObject(This->rgncopy);
    This->rgncopy=0;
    if (lprgn) {
      This->rgncopy = ExtCreateRegion(NULL, 0, lprgn);
      if (!This->rgncopy) return DDERR_INVALIDCLIPLIST;
    }
    return DD_OK;
}

HRESULT WINAPI Main_DirectDrawClipper_QueryInterface(
    LPDIRECTDRAWCLIPPER iface, REFIID riid, LPVOID* ppvObj
) {
    ICOM_THIS(IDirectDrawClipperImpl,iface);

    if (IsEqualGUID(&IID_IUnknown, riid)
	|| IsEqualGUID(&IID_IDirectDrawClipper, riid))
    {
	*ppvObj = ICOM_INTERFACE(This, IDirectDrawClipper);
	++This->ref;
	return S_OK;
    }
    else
    {
	return E_NOINTERFACE;
    }
}

ULONG WINAPI Main_DirectDrawClipper_AddRef( LPDIRECTDRAWCLIPPER iface )
{
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->() incrementing from %lu.\n", This, This->ref );
    return ++This->ref;
}

HRESULT WINAPI Main_DirectDrawClipper_GetHWnd(
    LPDIRECTDRAWCLIPPER iface, HWND* hWndPtr
) {
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->(%p)\n",This,hWndPtr);

    *hWndPtr = This->hWnd;

    return DD_OK;
}

HRESULT WINAPI Main_DirectDrawClipper_Initialize(
     LPDIRECTDRAWCLIPPER iface, LPDIRECTDRAW lpDD, DWORD dwFlags
) {
    IDirectDrawImpl* pOwner;
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->(%p,0x%08lx)\n",This,lpDD,dwFlags);

    if (This->ddraw_owner != NULL) return DDERR_ALREADYINITIALIZED;

    pOwner = ICOM_OBJECT(IDirectDrawImpl, IDirectDraw, lpDD);
    This->ddraw_owner = pOwner;
    Main_DirectDraw_AddClipper(pOwner, This);

    return DD_OK;
}

HRESULT WINAPI Main_DirectDrawClipper_IsClipListChanged(
    LPDIRECTDRAWCLIPPER iface, BOOL* lpbChanged
) {
    ICOM_THIS(IDirectDrawClipperImpl,iface);
    TRACE("(%p)->(%p),untested !!\n",This,lpbChanged);

    /* Find a better way to do this */
    
    *lpbChanged = FALSE;
    if (This->hWnd) {
      HRGN client;
      WND *wnd = WIN_GetPtr( This->hWnd );
      if (!wnd) return DDERR_INVALIDPARAMS;
      client = CreateRectRgnIndirect(&wnd->rectClient);
      if (client) {
        if (!This->rgncopy || !EqualRgn(This->rgncopy, client)) {
          *lpbChanged = TRUE;
          if (!This->rgncopy) This->rgncopy=CreateRectRgn(0,0,0,0);
          CombineRgn(This->rgncopy, client, 0, RGN_COPY);
        }
        DeleteObject(client);
      }     
      WIN_ReleasePtr( wnd );         
    }
    else return DDERR_INVALIDOBJECT;
    return DD_OK;
}

static ICOM_VTABLE(IDirectDrawClipper) DDRAW_Clipper_VTable =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    Main_DirectDrawClipper_QueryInterface,
    Main_DirectDrawClipper_AddRef,
    Main_DirectDrawClipper_Release,
    Main_DirectDrawClipper_GetClipList,
    Main_DirectDrawClipper_GetHWnd,
    Main_DirectDrawClipper_Initialize,
    Main_DirectDrawClipper_IsClipListChanged,
    Main_DirectDrawClipper_SetClipList,
    Main_DirectDrawClipper_SetHWnd
};
