/* gtksizerequest.c
 * Copyright (C) 2007-2010 Openismus GmbH
 *
 * Authors:
 *      Mathias Hasselmann <mathias@openismus.com>
 *      Tristan Van Berkom <tristan.van.berkom@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "gtksizerequestcacheprivate.h"

#include <string.h>

void
_gtk_size_request_cache_init (SizeRequestCache *cache)
{
  memset (cache, 0, sizeof (SizeRequestCache));
}

static void
free_sizes_x (SizeRequestX **sizes)
{
  int i;

  for (i = 0; i < GTK_SIZE_REQUEST_CACHED_SIZES && sizes[i] != NULL; i++)
    g_free (sizes[i]);

  g_free (sizes);
}

static void
free_sizes_y (SizeRequestY **sizes)
{
  int i;

  for (i = 0; i < GTK_SIZE_REQUEST_CACHED_SIZES && sizes[i] != NULL; i++)
    g_free (sizes[i]);

  g_free (sizes);
}

void
_gtk_size_request_cache_free (SizeRequestCache *cache)
{
  if (cache->requests_x)
    free_sizes_x (cache->requests_x);
  if (cache->requests_y)
    free_sizes_y (cache->requests_y);
}

void
_gtk_size_request_cache_clear (SizeRequestCache *cache)
{
  _gtk_size_request_cache_free (cache);
  _gtk_size_request_cache_init (cache);
}

void
_gtk_size_request_cache_commit (SizeRequestCache *cache,
                                GtkOrientation    orientation,
                                int               for_size,
                                int               minimum_size,
                                int               natural_size,
                                int               minimum_baseline,
                                int               natural_baseline)
{
  guint         i, n_sizes;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      g_assert (minimum_baseline == -1);
      g_assert (natural_baseline == -1);
    }

  /* First handle caching of the base requests */
  if (for_size < 0)
    {
      if (orientation == GTK_ORIENTATION_HORIZONTAL)
	{
	  cache->cached_size_x.minimum_size = minimum_size;
	  cache->cached_size_x.natural_size = natural_size;
	}
      else
	{
	  cache->cached_size_y.minimum_size = minimum_size;
	  cache->cached_size_y.natural_size = natural_size;
	  cache->cached_size_y.minimum_baseline = minimum_baseline;
	  cache->cached_size_y.natural_baseline = natural_baseline;
	}

      cache->flags[orientation].cached_size_valid = TRUE;
      return;
    }

  /* Check if the minimum_size and natural_size is already
   * in the cache and if this result can be used to extend
   * that cache entry 
   */
  n_sizes = cache->flags[orientation].n_cached_requests;


  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      SizeRequestX **cached_sizes = cache->requests_x;
      SizeRequestX  *cached_size;

#ifdef G_ENABLE_DEBUG
      if (cache->flags[GTK_ORIENTATION_HORIZONTAL].cached_size_valid &&
          minimum_size < cache->cached_size_x.minimum_size)
        g_warning ("Widget reports min width of %d for height of %d, but overall min width of %d",
                   minimum_size, for_size, cache->cached_size_x.minimum_size);

      for (i = 0; i < cache->flags[GTK_ORIENTATION_VERTICAL].n_cached_requests; i++)
        {
          if (cache->requests_y[i]->upper_for_size >= minimum_size &&
              cache->requests_y[i]->cached_size.minimum_size > for_size)
            {
              g_warning ("Widget reports min width of %d for height of %d, but min height of %d for width of %d",
                         minimum_size, for_size,
                         cache->requests_y[i]->cached_size.minimum_size,
                         cache->requests_y[i]->upper_for_size);
              break;
            }
          else if (cache->requests_y[i]->lower_for_size < minimum_size &&
                   cache->requests_y[i]->cached_size.minimum_size <= for_size)
            {
              g_warning ("Widget reports min width of %d for height of %d, but min height of %d for width of %d",
                         minimum_size, for_size,
                         cache->requests_y[i]->cached_size.minimum_size,
                         cache->requests_y[i]->lower_for_size);
              break;
            }
        }
#endif

      for (i = 0; i < n_sizes; i++)
	{
	  if (cached_sizes[i]->cached_size.minimum_size == minimum_size &&
	      cached_sizes[i]->cached_size.natural_size == natural_size)
	    {
	      cached_sizes[i]->lower_for_size = MIN (cached_sizes[i]->lower_for_size, for_size);
	      cached_sizes[i]->upper_for_size = MAX (cached_sizes[i]->upper_for_size, for_size);
	      return;
	    }
	}

      /* If not found, pull a new size from the cache, the returned size cache
       * will immediately be used to cache the new computed size so we go ahead
       * and increment the last_cached_request right away */
      if (n_sizes < GTK_SIZE_REQUEST_CACHED_SIZES)
	{
	  cache->flags[orientation].n_cached_requests++;
	  cache->flags[orientation].last_cached_request = cache->flags[orientation].n_cached_requests - 1;
	}
      else
	{
	  if (++cache->flags[orientation].last_cached_request == GTK_SIZE_REQUEST_CACHED_SIZES)
	    cache->flags[orientation].last_cached_request = 0;
	}

      if (cache->requests_x == NULL)
	cache->requests_x = g_new0 (SizeRequestX *, GTK_SIZE_REQUEST_CACHED_SIZES);

      if (cache->requests_x[cache->flags[orientation].last_cached_request] == NULL)
	cache->requests_x[cache->flags[orientation].last_cached_request] = g_new (SizeRequestX, 1);

      cached_size = cache->requests_x[cache->flags[orientation].last_cached_request];
      cached_size->lower_for_size = for_size;
      cached_size->upper_for_size = for_size;
      cached_size->cached_size.minimum_size = minimum_size;
      cached_size->cached_size.natural_size = natural_size;
    }
  else
    {
      SizeRequestY **cached_sizes = cache->requests_y;
      SizeRequestY  *cached_size;

#ifdef G_ENABLE_DEBUG
      if (cache->flags[GTK_ORIENTATION_VERTICAL].cached_size_valid &&
          minimum_size < cache->cached_size_y.minimum_size)
        g_warning ("Widget reports min height of %d for width of %d, but overall min height of %d",
                   minimum_size, for_size, cache->cached_size_y.minimum_size);

      for (i = 0; i < cache->flags[GTK_ORIENTATION_HORIZONTAL].n_cached_requests; i++)
        {
          if (cache->requests_x[i]->upper_for_size >= minimum_size &&
              cache->requests_x[i]->cached_size.minimum_size > for_size)
            {
              g_warning ("Widget reports min height of %d for width of %d, but min width of %d for height of %d",
                         minimum_size, for_size,
                         cache->requests_x[i]->cached_size.minimum_size,
                         cache->requests_x[i]->upper_for_size);
              break;
            }
          else if (cache->requests_x[i]->lower_for_size < minimum_size &&
                   cache->requests_x[i]->cached_size.minimum_size <= for_size)
            {
              g_warning ("Widget reports min height of %d for width of %d, but min width of %d for height of %d",
                         minimum_size, for_size,
                         cache->requests_x[i]->cached_size.minimum_size,
                         cache->requests_x[i]->lower_for_size);
              break;
            }
        }
#endif

      for (i = 0; i < n_sizes; i++)
	{
	  if (cached_sizes[i]->cached_size.minimum_size == minimum_size &&
	      cached_sizes[i]->cached_size.natural_size == natural_size &&
	      cached_sizes[i]->cached_size.minimum_baseline == minimum_baseline &&
	      cached_sizes[i]->cached_size.natural_baseline == natural_baseline)
	    {
	      cached_sizes[i]->lower_for_size = MIN (cached_sizes[i]->lower_for_size, for_size);
	      cached_sizes[i]->upper_for_size = MAX (cached_sizes[i]->upper_for_size, for_size);
	      return;
	    }
	}

      /* If not found, pull a new size from the cache, the returned size cache
       * will immediately be used to cache the new computed size so we go ahead
       * and increment the last_cached_request right away */
      if (n_sizes < GTK_SIZE_REQUEST_CACHED_SIZES)
	{
	  cache->flags[orientation].n_cached_requests++;
	  cache->flags[orientation].last_cached_request = cache->flags[orientation].n_cached_requests - 1;
	}
      else
	{
	  if (++cache->flags[orientation].last_cached_request == GTK_SIZE_REQUEST_CACHED_SIZES)
	    cache->flags[orientation].last_cached_request = 0;
	}

      if (cache->requests_y == NULL)
	cache->requests_y = g_new0 (SizeRequestY *, GTK_SIZE_REQUEST_CACHED_SIZES);

      if (cache->requests_y[cache->flags[orientation].last_cached_request] == NULL)
	cache->requests_y[cache->flags[orientation].last_cached_request] = g_new (SizeRequestY, 1);

      cached_size = cache->requests_y[cache->flags[orientation].last_cached_request];
      cached_size->lower_for_size = for_size;
      cached_size->upper_for_size = for_size;
      cached_size->cached_size.minimum_size = minimum_size;
      cached_size->cached_size.natural_size = natural_size;
      cached_size->cached_size.minimum_baseline = minimum_baseline;
      cached_size->cached_size.natural_baseline = natural_baseline;
    }
}

/* looks for a cached size request for this for_size.
 *
 * Note that this caching code was originally derived from
 * the Clutter toolkit but has evolved for other GTK requirements.
 */
gboolean
_gtk_size_request_cache_lookup (const SizeRequestCache *cache,
                                GtkOrientation          orientation,
                                int                     for_size,
                                int                    *minimum,
                                int                    *natural,
                                int                    *minimum_baseline,
                                int                    *natural_baseline)
{
  guint i, p;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      if (for_size < 0)
        {
          if (cache->flags[GTK_ORIENTATION_HORIZONTAL].cached_size_valid)
            {
              const CachedSizeX *result = &cache->cached_size_x;

              *minimum = result->minimum_size;
              *natural = result->natural_size;
              return TRUE;
            }

          return FALSE;
        }
      else
	{
	  /* Search for an already cached size */
          for (i = 0, p = cache->flags[GTK_ORIENTATION_HORIZONTAL].n_cached_requests; i < p; i++)
            {
              const SizeRequestX *cur = cache->requests_x[i];

	      if (cur->lower_for_size <= for_size &&
		  cur->upper_for_size >= for_size)
		{
                  const CachedSizeX *result = &cur->cached_size;

                  *minimum = result->minimum_size;
                  *natural = result->natural_size;

                  return TRUE;
                }
            }

          return FALSE;
	}
    }
  else
    {
      if (for_size < 0)
        {
          if (cache->flags[GTK_ORIENTATION_VERTICAL].cached_size_valid)
            {
              const CachedSizeY *result = &cache->cached_size_y;

              *minimum = result->minimum_size;
              *natural = result->natural_size;
              *minimum_baseline = result->minimum_baseline;
              *natural_baseline = result->natural_baseline;
              return TRUE;
            }

          return FALSE;
        }
      else
	{
	  /* Search for an already cached size */
          for (i = 0, p = cache->flags[GTK_ORIENTATION_VERTICAL].n_cached_requests; i < p; i++)
            {
              const SizeRequestY *cur = cache->requests_y[i];

	      if (cur->lower_for_size <= for_size &&
		  cur->upper_for_size >= for_size)
		{
                  const CachedSizeY *result = &cur->cached_size;

                  *minimum = result->minimum_size;
                  *natural = result->natural_size;
                  *minimum_baseline = result->minimum_baseline;
                  *natural_baseline = result->natural_baseline;
                  return TRUE;
                }
            }

          return FALSE;
        }
    }
}

