/*
 * Copyright 1998-1999 Precision Insight, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 * Copyright 2010 George Sapountzis <gsapountzis@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * \file drisw_util.c
 *
 * DRISW utility functions, i.e. dri_util.c stripped from drm-specific bits.
 */

#include "dri_util.h"
#include "utils.h"


/**
 * Screen functions
 */

static void
setupLoaderExtensions(__DRIscreen *psp,
		      const __DRIextension **extensions)
{
    int i;

    for (i = 0; extensions[i]; i++) {
	if (strcmp(extensions[i]->name, __DRI_SWRAST_LOADER) == 0)
	    psp->swrast_loader = (__DRIswrastLoaderExtension *) extensions[i];
    }
}

static __DRIscreen *
driCreateNewScreen(int scrn, const __DRIextension **extensions,
		   const __DRIconfig ***driver_configs, void *data)
{
    static const __DRIextension *emptyExtensionList[] = { NULL };
    __DRIscreen *psp;

    psp = CALLOC_STRUCT(__DRIscreenRec);
    if (!psp)
	return NULL;

    setupLoaderExtensions(psp, extensions);

    psp->loaderPrivate = data;

    psp->extensions = emptyExtensionList;
    psp->fd = -1;
    psp->myNum = scrn;

    *driver_configs = driDriverAPI.InitScreen(psp);
    if (*driver_configs == NULL) {
	free(psp);
	return NULL;
    }

    return psp;
}

static void driDestroyScreen(__DRIscreen *psp)
{
    if (psp) {
	driDriverAPI.DestroyScreen(psp);
	free(psp);
    }
}

static const __DRIextension **driGetExtensions(__DRIscreen *psp)
{
    return psp->extensions;
}


/**
 * Context functions
 */

static __DRIcontext *
driCreateContextAttribs(__DRIscreen *screen, int api,
			const __DRIconfig *config,
			__DRIcontext *shared,
			unsigned num_attribs,
			const uint32_t *attribs,
			unsigned *error,
			void *data)
{
    __DRIcontext *pcp;
    const struct gl_config *modes = (config != NULL) ? &config->modes : NULL;
    void * const shareCtx = (shared != NULL) ? shared->driverPrivate : NULL;
    gl_api mesa_api;
    unsigned major_version = 1;
    unsigned minor_version = 0;
    uint32_t flags = 0;

    /* Either num_attribs is zero and attribs is NULL, or num_attribs is not
     * zero and attribs is not NULL.
     */
    assert((num_attribs == 0) == (attribs == NULL));

    switch (api) {
    case __DRI_API_OPENGL:
            mesa_api = API_OPENGL_COMPAT;
            break;
    case __DRI_API_GLES:
            mesa_api = API_OPENGLES;
            break;
    case __DRI_API_GLES2:
    case __DRI_API_GLES3:
            mesa_api = API_OPENGLES2;
            break;
    case __DRI_API_OPENGL_CORE:
            mesa_api = API_OPENGL_CORE;
            break;
    default:
            *error = __DRI_CTX_ERROR_BAD_API;
            return NULL;
    }

    for (unsigned i = 0; i < num_attribs; i++) {
	switch (attribs[i * 2]) {
	case __DRI_CTX_ATTRIB_MAJOR_VERSION:
	    major_version = attribs[i * 2 + 1];
	    break;
	case __DRI_CTX_ATTRIB_MINOR_VERSION:
	    minor_version = attribs[i * 2 + 1];
	    break;
	case __DRI_CTX_ATTRIB_FLAGS:
	    flags = attribs[i * 2 + 1];
	    break;
	default:
	    /* We can't create a context that satisfies the requirements of an
	     * attribute that we don't understand.  Return failure.
	     */
	    return NULL;
	}
    }

    /* Mesa does not support the GL_ARB_compatibilty extension or the
     * compatibility profile.  This means that we treat a API_OPENGL_COMPAT 3.1 as
     * API_OPENGL_CORE and reject API_OPENGL_COMPAT 3.2+.
     */
    if (mesa_api == API_OPENGL_COMPAT && major_version == 3 && minor_version == 1)
       mesa_api = API_OPENGL_CORE;

    if (mesa_api == API_OPENGL_COMPAT
        && ((major_version > 3)
            || (major_version == 3 && minor_version >= 2))) {
       *error = __DRI_CTX_ERROR_BAD_API;
       return NULL;
    }
    /* There are no forward-compatible contexts before OpenGL 3.0.  The
     * GLX_ARB_create_context spec says:
     *
     *     "Forward-compatible contexts are defined only for OpenGL versions
     *     3.0 and later."
     *
     * Moreover, Mesa can't fulfill the requirements of a forward-looking
     * context.  Return failure if a forward-looking context is requested.
     *
     * In Mesa, a debug context is the same as a regular context.
     */
    if (major_version >= 3) {
	if ((flags & ~__DRI_CTX_FLAG_DEBUG) != 0)
	    return NULL;
    }

    pcp = CALLOC_STRUCT(__DRIcontextRec);
    if (!pcp)
        return NULL;

    pcp->loaderPrivate = data;

    pcp->driScreenPriv = screen;
    pcp->driDrawablePriv = NULL;
    pcp->driReadablePriv = NULL;

    if (!driDriverAPI.CreateContext(mesa_api, modes, pcp,
				    major_version, minor_version,
				    flags, error, shareCtx)) {
        free(pcp);
        return NULL;
    }

    return pcp;
}

static __DRIcontext *
driCreateNewContextForAPI(__DRIscreen *psp, int api,
                          const __DRIconfig *config,
                          __DRIcontext *shared, void *data)
{
    unsigned error;

    return driCreateContextAttribs(psp, api, config, shared, 0, NULL,
				   &error, data);
}

static __DRIcontext *
driCreateNewContext(__DRIscreen *psp, const __DRIconfig *config,
		    __DRIcontext *shared, void *data)
{
    return driCreateNewContextForAPI(psp, __DRI_API_OPENGL,
				     config, shared, data);
}

static void
driDestroyContext(__DRIcontext *pcp)
{
    if (pcp) {
	driDriverAPI.DestroyContext(pcp);
	free(pcp);
    }
}

static int
driCopyContext(__DRIcontext *dst, __DRIcontext *src, unsigned long mask)
{
    return GL_FALSE;
}

static void dri_get_drawable(__DRIdrawable *pdp);
static void dri_put_drawable(__DRIdrawable *pdp);

static int driBindContext(__DRIcontext *pcp,
			  __DRIdrawable *pdp,
			  __DRIdrawable *prp)
{
    /* Bind the drawable to the context */
    if (pcp) {
	pcp->driDrawablePriv = pdp;
	pcp->driReadablePriv = prp;
	if (pdp) {
	    pdp->driContextPriv = pcp;
	    dri_get_drawable(pdp);
	}
	if (prp && pdp != prp) {
	    dri_get_drawable(prp);
	}
    }

    return driDriverAPI.MakeCurrent(pcp, pdp, prp);
}

static int driUnbindContext(__DRIcontext *pcp)
{
    __DRIdrawable *pdp;
    __DRIdrawable *prp;

    if (pcp == NULL)
	return GL_FALSE;

    pdp = pcp->driDrawablePriv;
    prp = pcp->driReadablePriv;

    /* already unbound */
    if (!pdp && !prp)
	return GL_TRUE;

    driDriverAPI.UnbindContext(pcp);

    dri_put_drawable(pdp);

    if (prp != pdp) {
	dri_put_drawable(prp);
    }

    pcp->driDrawablePriv = NULL;
    pcp->driReadablePriv = NULL;

    return GL_TRUE;
}


/**
 * Drawable functions
 */

static void dri_get_drawable(__DRIdrawable *pdp)
{
    pdp->refcount++;
}

static void dri_put_drawable(__DRIdrawable *pdp)
{
    if (pdp) {
	pdp->refcount--;
	if (pdp->refcount)
	    return;

	driDriverAPI.DestroyBuffer(pdp);
	free(pdp);
    }
}

static __DRIdrawable *
driCreateNewDrawable(__DRIscreen *psp,
		     const __DRIconfig *config, void *data)
{
    __DRIdrawable *pdp;

    pdp = CALLOC_STRUCT(__DRIdrawableRec);
    if (!pdp)
	return NULL;

    pdp->loaderPrivate = data;

    pdp->driScreenPriv = psp;
    pdp->driContextPriv = NULL;

    dri_get_drawable(pdp);

    if (!driDriverAPI.CreateBuffer(psp, pdp, &config->modes, GL_FALSE)) {
	free(pdp);
	return NULL;
    }

    pdp->lastStamp = 1; /* const */

    return pdp;
}

static void
driDestroyDrawable(__DRIdrawable *pdp)
{
    dri_put_drawable(pdp);
}

static void driSwapBuffers(__DRIdrawable *pdp)
{
    driDriverAPI.SwapBuffers(pdp);
}

const __DRIcoreExtension driCoreExtension = {
    { __DRI_CORE, __DRI_CORE_VERSION },
    NULL, /* driCreateNewScreen */
    driDestroyScreen,
    driGetExtensions,
    driGetConfigAttrib,
    driIndexConfigAttrib,
    NULL, /* driCreateNewDrawable */
    driDestroyDrawable,
    driSwapBuffers,
    driCreateNewContext,
    driCopyContext,
    driDestroyContext,
    driBindContext,
    driUnbindContext
};

const __DRIswrastExtension driSWRastExtension = {
    { __DRI_SWRAST, __DRI_SWRAST_VERSION },
    driCreateNewScreen,
    driCreateNewDrawable,
    driCreateNewContextForAPI,
    driCreateContextAttribs
};
