/*
 * Copyright © 2004 David Reveman
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of
 * David Reveman not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * David Reveman makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * DAVID REVEMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL DAVID REVEMAN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#ifdef HAVE_CONFIG_H
#  include "../config.h"
#endif

#include "glitzint.h"

static glitz_status_t
_glitz_buffer_init (glitz_buffer_t      *buffer,
		    glitz_drawable_t    *drawable,
		    void                *data,
		    unsigned int        size,
		    glitz_buffer_hint_t hint)
{
    glitz_gl_enum_t usage;

    buffer->ref_count = 1;
    buffer->name = 0;

    if (drawable)
    {
	GLITZ_GL_DRAWABLE (drawable);

	switch (hint) {
	case GLITZ_BUFFER_HINT_STREAM_DRAW:
	    usage = GLITZ_GL_STREAM_DRAW;
	    break;
	case GLITZ_BUFFER_HINT_STREAM_READ:
	    usage = GLITZ_GL_STREAM_READ;
	    break;
	case GLITZ_BUFFER_HINT_STREAM_COPY:
	    usage = GLITZ_GL_STREAM_COPY;
	    break;
	case GLITZ_BUFFER_HINT_STATIC_DRAW:
	    usage = GLITZ_GL_STATIC_DRAW;
	    break;
	case GLITZ_BUFFER_HINT_STATIC_READ:
	    usage = GLITZ_GL_STATIC_READ;
	    break;
	case GLITZ_BUFFER_HINT_STATIC_COPY:
	    usage = GLITZ_GL_STATIC_COPY;
	    break;
	case GLITZ_BUFFER_HINT_DYNAMIC_DRAW:
	    usage = GLITZ_GL_DYNAMIC_DRAW;
	    break;
	case GLITZ_BUFFER_HINT_DYNAMIC_READ:
	    usage = GLITZ_GL_DYNAMIC_READ;
	    break;
	default:
	    usage = GLITZ_GL_DYNAMIC_COPY;
	    break;
	}

	buffer->owns_data = 1;
	buffer->drawable = drawable;
	glitz_drawable_reference (drawable);

	drawable->backend->push_current (drawable, NULL,
					 GLITZ_ANY_CONTEXT_CURRENT, NULL);

	gl->gen_buffers (1, &buffer->name);
	if (buffer->name) {
	    gl->bind_buffer (buffer->target, buffer->name);
	    gl->buffer_data (buffer->target, size, data, usage);
	    gl->bind_buffer (buffer->target, 0);
	}

	drawable->backend->pop_current (drawable);
    }
    else
    {
	buffer->drawable = NULL;
	usage = GLITZ_GL_DYNAMIC_COPY;
    }

    if (size > 0 && buffer->name == 0)
    {
	buffer->data = malloc (size);
	if (buffer->data == NULL)
	    return GLITZ_STATUS_NO_MEMORY;

	if (data)
	    memcpy (buffer->data, data, size);

	buffer->owns_data = 1;
    }
    else
    {
	buffer->owns_data = 0;
	buffer->data = data;
    }

    return GLITZ_STATUS_SUCCESS;
}

glitz_buffer_t *
glitz_vertex_buffer_create (glitz_drawable_t    *drawable,
			    void                *data,
			    unsigned int        size,
			    glitz_buffer_hint_t hint)
{
    glitz_buffer_t *buffer;
    glitz_status_t status;

    if (size == 0)
	return NULL;

    buffer = (glitz_buffer_t *) malloc (sizeof (glitz_buffer_t));
    if (buffer == NULL)
	return NULL;

    buffer->target = GLITZ_GL_ARRAY_BUFFER;

    if (drawable->backend->feature_mask &
	GLITZ_FEATURE_VERTEX_BUFFER_OBJECT_MASK)
	status = _glitz_buffer_init (buffer, drawable, data, size, hint);
    else
	status = _glitz_buffer_init (buffer, NULL, data, size, hint);

    if (status != GLITZ_STATUS_SUCCESS) {
	free (buffer);
	return NULL;
    }

    return buffer;
}

glitz_buffer_t *
glitz_pixel_buffer_create (glitz_drawable_t    *drawable,
			   void                *data,
			   unsigned int        size,
			   glitz_buffer_hint_t hint)
{
    glitz_buffer_t *buffer;
    glitz_status_t status;

    if (size == 0)
	return NULL;

    buffer = (glitz_buffer_t *) malloc (sizeof (glitz_buffer_t));
    if (buffer == NULL)
	return NULL;

    switch (hint) {
    case GLITZ_BUFFER_HINT_STREAM_READ:
    case GLITZ_BUFFER_HINT_STATIC_READ:
    case GLITZ_BUFFER_HINT_DYNAMIC_READ:
	buffer->target = GLITZ_GL_PIXEL_PACK_BUFFER;
	break;
    default:
	buffer->target = GLITZ_GL_PIXEL_UNPACK_BUFFER;
	break;
    }

    if (drawable->backend->feature_mask &
	GLITZ_FEATURE_PIXEL_BUFFER_OBJECT_MASK)
	status = _glitz_buffer_init (buffer, drawable, data, size, hint);
    else
	status = _glitz_buffer_init (buffer, NULL, data, size, hint);

    if (status != GLITZ_STATUS_SUCCESS) {
	free (buffer);
	return NULL;
    }

    return buffer;
}

glitz_buffer_t *
glitz_buffer_create_for_data (void *data)
{
    glitz_buffer_t *buffer;

    buffer = (glitz_buffer_t *) malloc (sizeof (glitz_buffer_t));
    if (buffer == NULL)
	return NULL;

    if (_glitz_buffer_init (buffer, NULL, data, 0, 0)) {
	free (buffer);
	return NULL;
    }

    return buffer;
}

void
glitz_buffer_destroy (glitz_buffer_t *buffer)
{
    if (!buffer)
	return;

    buffer->ref_count--;
    if (buffer->ref_count)
	return;

    if (buffer->drawable) {
	buffer->drawable->backend->push_current (buffer->drawable, NULL,
						 GLITZ_ANY_CONTEXT_CURRENT,
						 NULL);
	buffer->drawable->backend->gl->delete_buffers (1, &buffer->name);
	buffer->drawable->backend->pop_current (buffer->drawable);
	glitz_drawable_destroy (buffer->drawable);
    } else if (buffer->owns_data)
	free (buffer->data);

    free (buffer);
}

void
glitz_buffer_reference (glitz_buffer_t *buffer)
{
    if (!buffer)
	return;

    buffer->ref_count++;
}

void
glitz_buffer_set_data (glitz_buffer_t *buffer,
		       int            offset,
		       unsigned int   size,
		       const void     *data)
{
    if (buffer->drawable) {
	GLITZ_GL_DRAWABLE (buffer->drawable);

	buffer->drawable->backend->push_current (buffer->drawable, NULL,
						 GLITZ_ANY_CONTEXT_CURRENT,
						 NULL);
	gl->bind_buffer (buffer->target, buffer->name);
	gl->buffer_sub_data (buffer->target, offset, size, data);
	gl->bind_buffer (buffer->target, 0);
	buffer->drawable->backend->pop_current (buffer->drawable);
    } else if (buffer->data)
	memcpy ((char *) buffer->data + offset, data, size);
}
slim_hidden_def(glitz_buffer_set_data);

void
glitz_buffer_get_data (glitz_buffer_t *buffer,
		       int            offset,
		       unsigned int   size,
		       void           *data)
{
    if (buffer->drawable) {
	GLITZ_GL_DRAWABLE (buffer->drawable);

	buffer->drawable->backend->push_current (buffer->drawable, NULL,
						 GLITZ_ANY_CONTEXT_CURRENT,
						 NULL);
	gl->bind_buffer (buffer->target, buffer->name);
	gl->get_buffer_sub_data (buffer->target, offset, size, data);
	gl->bind_buffer (buffer->target, 0);

	buffer->drawable->backend->pop_current (buffer->drawable);
    } else if (buffer->data)
	memcpy (data, (char *) buffer->data + offset, size);
}
slim_hidden_def(glitz_buffer_get_data);

void *
glitz_buffer_map (glitz_buffer_t        *buffer,
		  glitz_buffer_access_t access)
{
    void *pointer = NULL;

    if (buffer->drawable) {
	glitz_gl_enum_t buffer_access;

	GLITZ_GL_DRAWABLE (buffer->drawable);

	buffer->drawable->backend->push_current (buffer->drawable, NULL,
						 GLITZ_ANY_CONTEXT_CURRENT,
						 NULL);

	switch (access) {
	case GLITZ_BUFFER_ACCESS_READ_ONLY:
	    buffer_access = GLITZ_GL_READ_ONLY;
	    break;
	case GLITZ_BUFFER_ACCESS_WRITE_ONLY:
	    buffer_access = GLITZ_GL_WRITE_ONLY;
	    break;
	default:
	    buffer_access = GLITZ_GL_READ_WRITE;
	    break;
	}

	gl->bind_buffer (buffer->target, buffer->name);
	pointer = gl->map_buffer (buffer->target, buffer_access);
	gl->bind_buffer (buffer->target, 0);

	buffer->drawable->backend->pop_current (buffer->drawable);
    }

    if (pointer == NULL)
	pointer = buffer->data;

    return pointer;
}

glitz_status_t
glitz_buffer_unmap (glitz_buffer_t *buffer)
{
    glitz_status_t status = GLITZ_STATUS_SUCCESS;

    if (buffer->drawable) {
	GLITZ_GL_DRAWABLE (buffer->drawable);

	buffer->drawable->backend->push_current (buffer->drawable, NULL,
						 GLITZ_ANY_CONTEXT_CURRENT,
						 NULL);

	gl->bind_buffer (buffer->target, buffer->name);

	if (gl->unmap_buffer (buffer->target) == GLITZ_GL_FALSE)
	    status = GLITZ_STATUS_CONTENT_DESTROYED;

	gl->bind_buffer (buffer->target, 0);

	buffer->drawable->backend->pop_current (buffer->drawable);
    }

    return status;
}

void *
glitz_buffer_bind (glitz_buffer_t *buffer,
		   glitz_gl_enum_t target)
{
    if (buffer->drawable) {
	buffer->drawable->backend->gl->bind_buffer (target, buffer->name);
	buffer->target = target;

	return NULL;
    }

    return buffer->data;
}

void
glitz_buffer_unbind (glitz_buffer_t *buffer)
{
    if (buffer->drawable)
	buffer->drawable->backend->gl->bind_buffer (buffer->target, 0);
}
