/*
 * Cogl
 *
 * An object oriented GL/GLES Abstraction/Utility Layer
 *
 * Copyright (C) 2008,2009 Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

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

#include "cogl.h"

#include "cogl-internal.h"
#include "cogl-context.h"
#include "cogl-handle.h"

#ifdef HAVE_COGL_GLES2

#include <string.h>

#include "cogl-shader-private.h"
#include "cogl-program-gles.h"

static void _cogl_program_free (CoglProgram *program);

COGL_HANDLE_DEFINE (Program, program);
COGL_OBJECT_DEFINE_DEPRECATED_REF_COUNTING (program);

static void
_cogl_program_free (CoglProgram *program)
{
  int i;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  /* Unref all of the attached shaders */
  g_slist_foreach (program->attached_shaders, (GFunc) cogl_handle_unref, NULL);
  /* Destroy the list */
  g_slist_free (program->attached_shaders);

  _cogl_gles2_clear_cache_for_program ((CoglHandle) program);

  if (ctx->drv.gles2.settings.user_program == (CoglHandle) program)
    {
      ctx->drv.gles2.settings.user_program = COGL_INVALID_HANDLE;
      ctx->drv.gles2.settings_dirty = TRUE;
    }

  for (i = 0; i < COGL_PROGRAM_NUM_CUSTOM_UNIFORMS; i++)
    {
      if (program->custom_uniform_names[i])
        g_free (program->custom_uniform_names[i]);
      if (program->custom_uniforms[i].count > 1)
        g_free (program->custom_uniforms[i].v.array);
    }

  g_slice_free (CoglProgram, program);
}

CoglHandle
cogl_create_program (void)
{
  CoglProgram *program;

  program = g_slice_new0 (CoglProgram);

  return _cogl_program_handle_new (program);
}

void
cogl_program_attach_shader (CoglHandle program_handle,
                            CoglHandle shader_handle)
{
  CoglProgram *program;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  if (!cogl_is_program (program_handle) || !cogl_is_shader (shader_handle))
    return;

  program = _cogl_program_pointer_from_handle (program_handle);
  program->attached_shaders
    = g_slist_prepend (program->attached_shaders,
		       cogl_handle_ref (shader_handle));

  /* Whenever the shader changes we will need to relink the program
     with the fixed functionality shaders so we should forget the
     cached programs */
  _cogl_gles2_clear_cache_for_program (program);
}

void
cogl_program_link (CoglHandle handle)
{
  /* There's no point in linking the program here because it will have
     to be relinked with a different fixed functionality shader
     whenever the settings change */
}

void
cogl_program_use (CoglHandle handle)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  g_return_if_fail (handle == COGL_INVALID_HANDLE ||
                    cogl_is_program (handle));

  if (ctx->current_program == 0 && handle != 0)
    ctx->legacy_state_set++;
  else if (handle == 0 && ctx->current_program != 0)
    ctx->legacy_state_set--;

  if (handle != COGL_INVALID_HANDLE)
    cogl_handle_ref (handle);
  if (ctx->current_program != COGL_INVALID_HANDLE)
    cogl_handle_unref (ctx->current_program);
  ctx->current_program = handle;
}

int
cogl_program_get_uniform_location (CoglHandle   handle,
                                   const char *uniform_name)
{
  int i;
  CoglProgram *program;

  if (!cogl_is_program (handle))
    return -1;

  program = _cogl_program_pointer_from_handle (handle);

  /* We can't just ask the GL program object for the uniform location
     directly because it will change every time the program is linked
     with a new fixed functionality shader. Instead we make our own
     mapping of uniform numbers and cache the names */
  for (i = 0; program->custom_uniform_names[i]
	 && i < COGL_PROGRAM_NUM_CUSTOM_UNIFORMS; i++)
    if (!strcmp (program->custom_uniform_names[i], uniform_name))
      return i;

  if (i < COGL_PROGRAM_NUM_CUSTOM_UNIFORMS)
    {
      program->custom_uniform_names[i] = g_strdup (uniform_name);
      return i;
    }
  else
    /* We've run out of space for new uniform names so just pretend it
       isn't there */
    return -1;
}

static void
cogl_program_uniform_x (CoglHandle handle,
                        int uniform_no,
			int size,
			int count,
			CoglBoxedType type,
			gsize value_size,
			gconstpointer value)
{
  CoglProgram *program = handle;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  g_return_if_fail (cogl_is_program (handle));
  g_return_if_fail (program != NULL);

  if (uniform_no >= 0 && uniform_no < COGL_PROGRAM_NUM_CUSTOM_UNIFORMS
      && size >= 1 && size <= 4 && count >= 1)
    {
      CoglBoxedValue *bv = program->custom_uniforms + uniform_no;

      if (count == 1)
	{
	  if (bv->count > 1)
	    g_free (bv->v.array);

	  memcpy (bv->v.float_value, value, value_size);
	}
      else
	{
	  if (bv->count > 1)
	    {
	      if (bv->count != count || bv->size != size || bv->type != type)
		{
		  g_free (bv->v.array);
		  bv->v.array = g_malloc (count * value_size);
		}
	    }
	  else
	    bv->v.array = g_malloc (count * value_size);

	  memcpy (bv->v.array, value, count * value_size);
	}

      bv->type = type;
      bv->size = size;
      bv->count = count;

      program->dirty_custom_uniforms |= 1 << uniform_no;
    }
}

void
cogl_program_uniform_1f (int uniform_no,
                         float  value)
{
  cogl_program_uniform_float (uniform_no, 1, 1, &value);
}

void
cogl_program_set_uniform_1f (CoglHandle handle,
                             int uniform_location,
                             float value)
{
  cogl_program_uniform_x (handle,
                          uniform_location, 1, 1, COGL_BOXED_FLOAT,
			  sizeof (float), &value);
}

void
cogl_program_uniform_1i (int uniform_no,
                         int    value)
{
  cogl_program_uniform_int (uniform_no, 1, 1, &value);
}

void
cogl_program_set_uniform_1i (CoglHandle handle,
                             int uniform_location,
                             int value)
{
  cogl_program_uniform_x (handle,
                          uniform_location, 1, 1, COGL_BOXED_INT,
			  sizeof (int), &value);
}

void
cogl_program_uniform_float (int  uniform_no,
                            int     size,
                            int     count,
                            const GLfloat *value)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
  cogl_program_uniform_x (ctx->current_program,
                          uniform_no, size, count, COGL_BOXED_FLOAT,
			  sizeof (float) * size, value);
}

void
cogl_program_set_uniform_float (CoglHandle handle,
                                int uniform_location,
                                int n_components,
                                int count,
                                const float *value)
{
  cogl_program_uniform_x (handle,
                          uniform_location, n_components, count,
                          COGL_BOXED_FLOAT,
			  sizeof (float) * n_components, value);
}

void
cogl_program_uniform_int (int  uniform_no,
			  int   size,
			  int   count,
			  const GLint *value)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
  cogl_program_uniform_x (ctx->current_program,
                          uniform_no, size, count, COGL_BOXED_INT,
			  sizeof (int) * size, value);
}

void
cogl_program_set_uniform_int (CoglHandle handle,
                              int uniform_location,
                              int n_components,
                              int count,
                              const int *value)
{
  cogl_program_uniform_x (handle,
                          uniform_location, n_components, count,
                          COGL_BOXED_INT,
			  sizeof (int) * n_components, value);
}

void
cogl_program_set_uniform_matrix (CoglHandle handle,
                                 int uniform_location,
                                 int dimensions,
                                 int count,
                                 gboolean transpose,
                                 const float *value)
{
  CoglProgram *program = handle;
  CoglBoxedValue *bv;

  _COGL_GET_CONTEXT (ctx, NO_RETVAL);

  g_return_if_fail (cogl_is_program (handle));

  bv = program->custom_uniforms + uniform_location;

  cogl_program_uniform_x (ctx->current_program,
                          uniform_location, dimensions, count,
                          COGL_BOXED_MATRIX,
			  sizeof (float) * dimensions * dimensions , value);

  bv->transpose = transpose;
}

void
cogl_program_uniform_matrix (int   uniform_no,
                             int      size,
                             int      count,
                             gboolean  transpose,
                             const float  *value)
{
  _COGL_GET_CONTEXT (ctx, NO_RETVAL);
  cogl_program_set_uniform_matrix (ctx->current_program,
                                   uniform_no, size, count, transpose, value);
}

#else /* HAVE_COGL_GLES2 */

/* No support on regular OpenGL 1.1 */

CoglHandle
cogl_create_program (void)
{
  return COGL_INVALID_HANDLE;
}

gboolean
cogl_is_program (CoglHandle handle)
{
  return FALSE;
}

CoglHandle
cogl_program_ref (CoglHandle handle)
{
  return COGL_INVALID_HANDLE;
}

void
cogl_program_unref (CoglHandle handle)
{
}

void
cogl_program_attach_shader (CoglHandle program_handle,
                            CoglHandle shader_handle)
{
}

void
cogl_program_link (CoglHandle program_handle)
{
}

void
cogl_program_use (CoglHandle program_handle)
{
}

int
cogl_program_get_uniform_location (CoglHandle   program_handle,
                                   const char *uniform_name)
{
  return 0;
}

void
cogl_program_uniform_1f (int uniform_no,
                         float  value)
{
}

void
cogl_program_uniform_1i (int uniform_no,
                         int    value)
{
}

void
cogl_program_uniform_float (int  uniform_no,
                            int     size,
                            int     count,
                            const GLfloat *value)
{
}

void
cogl_program_uniform_int (int  uniform_no,
                          int     size,
                          int     count,
                          const int *value)
{
}

void
cogl_program_uniform_matrix (int   uniform_no,
                             int      size,
                             int      count,
                             gboolean  transpose,
                             const GLfloat  *value)
{
}


#endif /* HAVE_COGL_GLES2 */

CoglShaderLanguage
_cogl_program_get_language (CoglHandle handle)
{
  return COGL_SHADER_LANGUAGE_GLSL;
}

