/**************************************************************************
 * 
 * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 * 
 * 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, sub license, 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 (including the
 * next paragraph) 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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.
 * 
 **************************************************************************/


#include "pipe/p_util.h"
#include "pipe/p_state.h"
#include "pipe/p_inlines.h"
#include "sct.h"


struct texture_list
{
   struct pipe_texture *texture;
   struct texture_list *next;
};



#define MAX_SURFACES  ((PIPE_MAX_COLOR_BUFS) + 1)

struct sct_context
{
   const struct pipe_context *context;

   /** surfaces the context is drawing into */
   struct pipe_surface *surfaces[MAX_SURFACES];

   /** currently bound textures */
   struct pipe_texture *textures[PIPE_MAX_SAMPLERS];

   /** previously bound textures, used but not flushed */
   struct texture_list *textures_used;

   boolean needs_flush;

   struct sct_context *next;
};



struct sct_surface
{
   const struct pipe_surface *surface;

   /** list of contexts drawing to this surface */
   struct sct_context_list *contexts;

   struct sct_surface *next;
};



/**
 * Find the surface_info for the given pipe_surface
 */
static struct sct_surface *
find_surface_info(struct surface_context_tracker *sct,
                  const struct pipe_surface *surface)
{
   struct sct_surface *si;
   for (si = sct->surfaces; si; si = si->next)
      if (si->surface == surface)
         return si;
   return NULL;
}


/**
 * As above, but create new surface_info if surface is new.
 */
static struct sct_surface *
find_create_surface_info(struct surface_context_tracker *sct,
                         const struct pipe_surface *surface)
{
   struct sct_surface *si = find_surface_info(sct, surface);
   if (si)
      return si;

   /* alloc new */
   si = CALLOC_STRUCT(sct_surface);
   if (si) {
      si->surface = surface;

      /* insert at head */
      si->next = sct->surfaces;
      sct->surfaces = si;
   }

   return si;
}


/**
 * Find a context_info for the given context.
 */
static struct sct_context *
find_context_info(struct surface_context_tracker *sct,
                  const struct pipe_context *context)
{
   struct sct_context *ci;
   for (ci = sct->contexts; ci; ci = ci->next)
      if (ci->context == context)
         return ci;
   return NULL;
}


/**
 * As above, but create new context_info if context is new.
 */
static struct sct_context *
find_create_context_info(struct surface_context_tracker *sct,
                         const struct pipe_context *context)
{
   struct sct_context *ci = find_context_info(sct, context);
   if (ci)
      return ci;

   /* alloc new */
   ci = CALLOC_STRUCT(sct_context);
   if (ci) {
      ci->context = context;

      /* insert at head */
      ci->next = sct->contexts;
      sct->contexts = ci;
   }

   return ci;
}


/**
 * Is the context already bound to the surface?
 */
static boolean
find_surface_context(const struct sct_surface *si,
                     const struct pipe_context *context)
{
   const struct sct_context_list *cl;
   for (cl = si->contexts; cl; cl = cl->next) {
      if (cl->context == context) {
         return TRUE;
      }
   }
   return FALSE;
}


/**
 * Add a context to the list of contexts associated with a surface.
 */
static void
add_context_to_surface(struct sct_surface *si,
                       const struct pipe_context *context)
{
   struct sct_context_list *cl = CALLOC_STRUCT(sct_context_list);
   if (cl) {
      cl->context = context;
      /* insert at head of list of contexts */
      cl->next = si->contexts;
      si->contexts = cl;
   }
}


/**
 * Remove a context from the list of contexts associated with a surface.
 */
static void
remove_context_from_surface(struct sct_surface *si,
                            const struct pipe_context *context)
{
   struct sct_context_list *prev = NULL, *curr, *next;

   for (curr = si->contexts; curr; curr = next) {
      if (curr->context == context) {
         /* remove */
         if (prev)
            prev->next = curr->next;
         else
            si->contexts = curr->next;
         next = curr->next;
         FREE(curr);
      }
      else {
         prev = curr;
      }
   }
}


/**
 * Unbind context from surface.
 */
static void
unbind_context_surface(struct surface_context_tracker *sct,
                       struct pipe_context *context,
                       struct pipe_surface *surface)
{
   struct sct_surface *si = find_surface_info(sct, surface);
   if (si) {
      remove_context_from_surface(si, context);
   }
}


/**
 * Bind context to a set of surfaces (color + Z).
 * Like MakeCurrent().
 */
void
sct_bind_surfaces(struct surface_context_tracker *sct,
                  struct pipe_context *context,
                  uint num_surf,
                  struct pipe_surface **surfaces)
{
   struct sct_context *ci = find_create_context_info(sct, context);
   uint i;

   if (!ci) {
      return; /* out of memory */
   }

   /* unbind currently bound surfaces */
   for (i = 0; i < MAX_SURFACES; i++) {
      if (ci->surfaces[i]) {
         unbind_context_surface(sct, context, ci->surfaces[i]);
      }
   }

   /* bind new surfaces */
   for (i = 0; i < num_surf; i++) {
      struct sct_surface *si = find_create_surface_info(sct, surfaces[i]);
      if (!find_surface_context(si, context)) {
         add_context_to_surface(si, context);
      }
   }
}


/**
 * Return list of contexts bound to a surface.
 */
const struct sct_context_list *
sct_get_surface_contexts(struct surface_context_tracker *sct,
                         const struct pipe_surface *surface)
{
   const struct sct_surface *si = find_surface_info(sct, surface);
   return si->contexts;
}



static boolean
find_texture(const struct sct_context *ci,
             const struct pipe_texture *texture)
{
   const struct texture_list *tl;

   for (tl = ci->textures_used; tl; tl = tl->next) {
      if (tl->texture == texture) {
         return TRUE;
      }
   }
   return FALSE;
}


/**
 * Add the given texture to the context's list of used textures.
 */
static void
add_texture_used(struct sct_context *ci,
                 struct pipe_texture *texture)
{
   if (!find_texture(ci, texture)) {
      /* add to list */
      struct texture_list *tl = CALLOC_STRUCT(texture_list);
      if (tl) {
         pipe_texture_reference(&tl->texture, texture);
         /* insert at head */
         tl->next = ci->textures_used;
         ci->textures_used = tl;
      }
   }
}


/**
 * Bind a texture to a rendering context.
 */
void
sct_bind_texture(struct surface_context_tracker *sct,
                 struct pipe_context *context,
                 uint unit,
                 struct pipe_texture *tex)
{
   struct sct_context *ci = find_context_info(sct, context);

   if (ci->textures[unit] != tex) {
      /* put texture on the 'used' list */
      add_texture_used(ci, tex);
      /* bind new */
      pipe_texture_reference(&ci->textures[unit], tex);
   }
}


/**
 * Check if the given texture has been used by the rendering context
 * since the last call to sct_flush_textures().
 */
boolean
sct_is_texture_used(struct surface_context_tracker *sct,
                    const struct pipe_context *context,
                    const struct pipe_texture *texture)
{
   const struct sct_context *ci = find_context_info(sct, context);
   return find_texture(ci, texture);
}


/**
 * To be called when the image contents of a texture are changed, such
 * as for gl[Copy]TexSubImage().
 * XXX this may not be needed
 */
void
sct_update_texture(struct pipe_texture *tex)
{

}


/**
 * When a scene is flushed/rendered we can release the list of
 * used textures.
 */
void
sct_flush_textures(struct surface_context_tracker *sct,
                   struct pipe_context *context)
{
   struct sct_context *ci = find_context_info(sct, context);
   struct texture_list *tl, *next;
   uint i;

   for (tl = ci->textures_used; tl; tl = next) {
      next = tl->next;
      pipe_texture_release(&tl->texture);
      FREE(tl);
   }
   ci->textures_used = NULL;

   /* put the currently bound textures on the 'used' list */
   for (i = 0; i < PIPE_MAX_SAMPLERS; i++) {
      add_texture_used(ci, ci->textures[i]);
   }
}



void
sct_destroy_context(struct surface_context_tracker *sct,
                    struct pipe_context *context)
{
   /* XXX should we require an unbinding first? */
   {
      struct sct_surface *si;
      for (si = sct->surfaces; si; si = si->next) {
         remove_context_from_surface(si, context);
      }
   }

   /* remove context from context_info list */
   {
      struct sct_context *ci, *next, *prev = NULL;
      for (ci = sct->contexts; ci; ci = next) {
         next = ci->next;
         if (ci->context == context) {
            if (prev)
               prev->next = ci->next;
            else
               sct->contexts = ci->next;
            FREE(ci);
         }
         else {
            prev = ci;
         }
      }
   }

}


void
sct_destroy_surface(struct surface_context_tracker *sct,
                    struct pipe_surface *surface)
{
   if (1) {
      /* debug/sanity: no context should be bound to surface */
      struct sct_context *ci;
      uint i;
      for (ci = sct->contexts; ci; ci = ci->next) {
         for (i = 0; i < MAX_SURFACES; i++) {
            assert(ci->surfaces[i] != surface);
         }
      }
   }

   /* remove surface from sct_surface list */
   {
      struct sct_surface *si, *next, *prev = NULL;
      for (si = sct->surfaces; si; si = next) {
         next = si->next;
         if (si->surface == surface) {
            /* unlink */
            if (prev)
               prev->next = si->next;
            else
               sct->surfaces = si->next;
            FREE(si);
         }
         else {
            prev = si;
         }
      }
   }
}