/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 *  Vladimir Vukicevic <vladimir@pobox.com>
 * Portions created by the Initial Developer are Copyright (C) 2004
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsCairoDeviceContext.h"
#include "nsCairoDrawingSurface.h"
#include "cairo-xlib.h"

#if defined(MOZ_ENABLE_GTK2)
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
#ifdef MOZ_ENABLE_XFT
#include <X11/Xft/Xft.h>
#endif


#include "nsMemory.h"

NS_IMPL_ISUPPORTS1(nsCairoDrawingSurface, nsIDrawingSurface)

nsCairoDrawingSurface::nsCairoDrawingSurface()
    : mSurface(nsnull), mImageSurface(nsnull), mDC(nsnull), mNativeWidget(nsnull)
{
#if defined(MOZ_ENABLE_GTK2) || defined(MOZ_ENABLE_XLIB)
    mPixmap = 0;
    mShmInfo.shmid = -1;
#ifdef MOZ_ENABLE_XFT
    mXftDraw = nsnull;
#endif
#endif
}

nsCairoDrawingSurface::~nsCairoDrawingSurface()
{
    fprintf (stderr, "++++ [%p] DESTROY\n", this);

    if (mSurface)
        cairo_surface_destroy (mSurface);
    if (mImageSurface && !mFastAccess) // otherwise, mImageSurface == mSurface
        cairo_surface_destroy (mImageSurface);

#if defined(MOZ_ENABLE_GTK2) || defined(MOZ_ENABLE_XLIB)
    if (mPixmap != 0)
        XFreePixmap(mXDisplay, mPixmap);

    if (mShmInfo.shmid != -1 && mShmInfo.shmaddr != 0) {
        XShmDetach (mXDisplay, &mShmInfo);
        shmdt (mShmInfo.shmaddr);
    }
#endif
}

nsresult
nsCairoDrawingSurface::Init(nsCairoDeviceContext *aDC, PRUint32 aWidth, PRUint32 aHeight, PRBool aFastAccess)
{
    NS_ASSERTION(mSurface == nsnull, "Surface already initialized!");
    NS_ASSERTION(aWidth > 0 && aHeight > 0, "Invalid surface dimensions!");

    mWidth = aWidth;
    mHeight = aHeight;
    mDC = aDC;

    if (aFastAccess) {
        fprintf (stderr, "++++ [%p] Creating IMAGE surface: %dx%d\n", this, aWidth, aHeight);
        mSurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, aWidth, aHeight);
        mFastAccess = PR_TRUE;
        mDrawable = nsnull;
    } else {
        fprintf (stderr, "++++ [%p] Creating PIXMAP surface: %dx%d\n", this, aWidth, aHeight);
        // otherwise, we need to do toolkit-specific stuff
#if defined(MOZ_ENABLE_GTK2) || defined(MOZ_ENABLE_XLIB)
        mXDisplay = aDC->GetXDisplay();
#if 0
        mShmInfo.shmaddr = 0;
        mShmInfo.shmid = shmget (IPC_PRIVATE,
                                 aWidth * 4 * aHeight,
                                 IPC_CREAT | 0600);
        if (mShmInfo.shmid != -1) {
            mShmInfo.shmaddr = (char*) shmat (mShmInfo.shmid, 0, 0);
            mShmInfo.readOnly = False;
        }

        if (mShmInfo.shmid != -1 && mShmInfo.shmaddr != 0) {
            XShmAttach (mXDisplay, &mShmInfo);
            mPixmap = XShmCreatePixmap (mXDisplay, aDC->GetXPixmapParentDrawable(),
                                        mShmInfo.shmaddr, &mShmInfo,
                                        aWidth, aHeight, DefaultDepth(mXDisplay,DefaultScreen(mXDisplay)));
        } else {
#endif
            mPixmap = XCreatePixmap(mXDisplay,
                                    aDC->GetXPixmapParentDrawable(),
                                    aWidth, aHeight, DefaultDepth(mXDisplay,DefaultScreen(mXDisplay)));
#if 0
        }
#endif

        mDrawable = (Drawable)mPixmap;


        // clear the pixmap
        XGCValues gcv;
        gcv.foreground = WhitePixel(mXDisplay,DefaultScreen(mXDisplay));
        gcv.background = 0;
        GC gc = XCreateGC (mXDisplay, mPixmap, GCForeground | GCBackground, &gcv);
        XFillRectangle (mXDisplay, mPixmap, gc, 0, 0, aWidth, aHeight);
        XFreeGC (mXDisplay, gc);

        // create the surface
        mSurface = cairo_xlib_surface_create (mXDisplay,
                                              mPixmap,
                                              aDC->GetXVisual(),
                                              CAIRO_FORMAT_ARGB32,
                                              aDC->GetXColormap());

        mFastAccess = PR_FALSE;
#else
#error write me
#endif
    }

    mLockFlags = 0;

    return NS_OK;
}

nsresult
nsCairoDrawingSurface::Init (nsCairoDeviceContext *aDC, nsIWidget *aWidget)
{
    nsNativeWidget nativeWidget = aWidget->GetNativeData(NS_NATIVE_WIDGET);
    return Init (aDC, nativeWidget);
}

nsresult
nsCairoDrawingSurface::Init (nsCairoDeviceContext *aDC, nsNativeWidget aNativeWidget)
{
    fprintf (stderr, "++++ [%p] Creating DRAWABLE (0x%08x) surface\n", this, aNativeWidget);

    mDC = aDC;

#ifdef MOZ_ENABLE_GTK2
    mNativeWidget = aNativeWidget;
    mDrawable = GDK_DRAWABLE_XID(GDK_DRAWABLE(aNativeWidget));
    NS_ASSERTION (GDK_IS_WINDOW(aNativeWidget), "unsupported native widget type!");
    mSurface = cairo_xlib_surface_create
        (GDK_WINDOW_XDISPLAY(GDK_DRAWABLE(aNativeWidget)),
         GDK_WINDOW_XWINDOW(GDK_DRAWABLE(aNativeWidget)),
         GDK_VISUAL_XVISUAL(gdk_drawable_get_visual(GDK_DRAWABLE(aNativeWidget))),
         CAIRO_FORMAT_ARGB32, // I hope!
         GDK_COLORMAP_XCOLORMAP(gdk_drawable_get_colormap(GDK_DRAWABLE(aNativeWidget))));

    Window root_ignore;
    int x_ignore, y_ignore;
    unsigned int bwidth_ignore, width, height, depth;

    XGetGeometry(GDK_WINDOW_XDISPLAY(GDK_DRAWABLE(aNativeWidget)),
                 GDK_WINDOW_XWINDOW(GDK_DRAWABLE(aNativeWidget)),
                 &root_ignore, &x_ignore, &y_ignore,
                 &width, &height,
                 &bwidth_ignore, &depth);

#if 0
    if (depth != 32)
        fprintf (stderr, "**** nsCairoDrawingSurface::Init with Widget: depth is %d!\n", depth);
#endif

    mWidth = width;
    mHeight = height;
    mFastAccess = PR_FALSE;

#else
#error write me
#endif

    mPixmap = 0;
    mLockFlags = 0;

    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::Lock (PRInt32 aX, PRInt32 aY, PRUint32 aWidth, PRUint32 aHeight,
                             void **aBits, PRInt32 *aStride, PRInt32 *aWidthBytes,
                             PRUint32 aFlags)
{
    NS_ASSERTION(aX + aWidth <= mWidth, "Invalid aX/aWidth");
    NS_ASSERTION(aY + aHeight <= mHeight, "Invalid aY/aHeight");
    NS_ASSERTION(mLockFlags == 0, "nsCairoDrawingSurface::Lock while surface is already locked!");

#if 0
    if (!mFastAccess) {
        mImageSurface = cairo_surface_get_image (mSurface);
    }

    char *data;
    int width, height, stride, depth;

    if (cairo_image_surface_get_data (mImageSurface,
                                      &data, &width, &height, &stride, &depth)
        != 0)
    {
        /* Something went wrong */
        if (!mFastAccess) {
            cairo_surface_destroy(mImageSurface);
            mImageSurface = nsnull;
        }
        return NS_ERROR_FAILURE;
    }
    *aBits = data + (stride * aY) + (aX * (depth / 8));
    *aStride = stride;
    *aWidthBytes = width * (depth / 8);

    mLockFlags = 0;

#endif
    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::Unlock (void)
{
    NS_ASSERTION(mLockFlags != 0, "nsCairoDrawingSurface::Unlock on non-locked surface!");

    if (mFastAccess) {
        mLockFlags = 0;
        return NS_OK;
    }

    if (mLockFlags & NS_LOCK_SURFACE_WRITE_ONLY) {
        /* need to copy back */
        //cairo_surface_set_image (mSurface, mImageSurface);
    }

    cairo_surface_destroy (mImageSurface);
    mImageSurface = nsnull;
    mLockFlags = 0;

    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::GetDimensions (PRUint32 *aWidth, PRUint32 *aHeight)
{
    *aWidth = mWidth;
    *aHeight = mHeight;
    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::IsOffscreen(PRBool *aOffScreen)
{
    *aOffScreen = PR_FALSE;

    if (mFastAccess)
        *aOffScreen = PR_TRUE;
#if defined(MOZ_ENABLE_GTK2) || defined(MOZ_ENABLE_XLIB)
    else if (mPixmap)
        *aOffScreen = PR_TRUE;
#endif

    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::IsPixelAddressable(PRBool *aAddressable)
{
    *aAddressable = mFastAccess;
    return NS_OK;
}

NS_IMETHODIMP
nsCairoDrawingSurface::GetPixelFormat(nsPixelFormat *aFormat)
{
    aFormat->mRedMask = 0x000000ff;
    aFormat->mGreenMask = 0x0000ff00;
    aFormat->mBlueMask = 0x00ff0000;
    aFormat->mAlphaMask = 0xff000000;

    aFormat->mRedCount = 8;
    aFormat->mGreenCount = 8;
    aFormat->mBlueCount = 8;
    aFormat->mAlphaCount = 8;

    aFormat->mRedShift = 0;
    aFormat->mGreenShift = 8;
    aFormat->mBlueShift = 16;
    aFormat->mAlphaCount = 24;

    return NS_OK;
}

#ifdef MOZ_ENABLE_XFT
XftDraw *
nsCairoDrawingSurface::GetXftDraw(void)
{
    if (mDrawable == nsnull) {
        NS_ERROR("GetXftDraw with null drawable!\n");
    }

    if (!mXftDraw) {
        fprintf (stderr, "++++ [%p] Creating XFTDRAW for (0x%08x)\n", this, mDrawable);

        mXftDraw = XftDrawCreate(mDC->GetXDisplay(), mDrawable,
                                 mDC->GetXVisual(),
                                 mDC->GetXColormap());
    }

    return mXftDraw;
}

void
nsCairoDrawingSurface::GetLastXftClip(nsIRegion **aLastRegion)
{
    *aLastRegion = mLastXftClip.get();
    NS_IF_ADDREF(*aLastRegion);
}

void
nsCairoDrawingSurface::SetLastXftClip(nsIRegion *aLastRegion)
{
    mLastXftClip = aLastRegion;
}

#endif /* MOZ_ENABLE_XFT */
