/* This file is part of GEGL
 *
 * GEGL 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 3 of the License, or (at your option) any later version.
 *
 * GEGL 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 GEGL; if not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2003 Calvin Williamson
 *           2005-2008 Øyvind Kolås
 */

#include "config.h"

#include <glib-object.h>
#include <string.h>

#include "gegl.h"
#include "gegl-types-internal.h"
#include "gegl-utils.h"
#include "gegl-operation.h"
#include "gegl-operations.h"
#include "graph/gegl-node.h"
#include "graph/gegl-pad.h"
#include "gegl-operation-context.h"
#include "buffer/gegl-region.h"


static GHashTable *gtype_hash = NULL;
G_LOCK_DEFINE_STATIC (gtype_hash);


static void
add_operations (GHashTable *hash,
                GType       parent)
{
  GType *types;
  guint  count;
  guint  no;

  types = g_type_children (parent, &count);
  if (!types)
    return;

  for (no = 0; no < count; no++)
    {
      GeglOperationClass *operation_class = g_type_class_ref (types[no]);
      if (operation_class->name)
        {
          g_hash_table_insert (hash, g_strdup (operation_class->name), (gpointer) types[no]);
        }
      if (operation_class->compat_name)
        {
          g_hash_table_insert (hash, g_strdup (operation_class->compat_name), (gpointer) types[no]);
        }
      add_operations (hash, types[no]);
    }
  g_free (types);
}

GType
gegl_operation_gtype_from_name (const gchar *name)
{
  GType ret = 0;

  /* FIXME: We only need mutual exclusion when we initialize. Maybe
   * move initialization to gegl_init()?
   */
  G_LOCK (gtype_hash);
  if (!gtype_hash)
    {
      gtype_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

      add_operations (gtype_hash, GEGL_TYPE_OPERATION);
    }
  ret = (GType) g_hash_table_lookup (gtype_hash, name);
  G_UNLOCK (gtype_hash);

  return ret;
}

static GSList *operations_list = NULL;
static void addop (gpointer key,
                   gpointer value,
                   gpointer user_data)
{
  operations_list = g_slist_prepend (operations_list, key);
}

gchar **gegl_list_operations (guint *n_operations_p)
{
  gchar **pasp = NULL;
  gint    n_operations;
  gint    i;
  GSList *iter;
  gint    pasp_size = 0;
  gint    pasp_pos;

  if (!operations_list)
    {
      gegl_operation_gtype_from_name ("");

      G_LOCK (gtype_hash);
      g_hash_table_foreach (gtype_hash, addop, NULL);
      G_UNLOCK (gtype_hash);

      operations_list = g_slist_sort (operations_list, (GCompareFunc) strcmp);
    }

  n_operations = g_slist_length (operations_list);
  pasp_size   += (n_operations + 1) * sizeof (gchar *);
  for (iter = operations_list; iter != NULL; iter = g_slist_next (iter))
    {
      const gchar *name = iter->data;
      pasp_size += strlen (name) + 1;
    }
  pasp     = g_malloc (pasp_size);
  pasp_pos = (n_operations + 1) * sizeof (gchar *);
  for (iter = operations_list, i = 0; iter != NULL; iter = g_slist_next (iter), i++)
    {
      const gchar *name = iter->data;
      pasp[i] = ((gchar *) pasp) + pasp_pos;
      strcpy (pasp[i], name);
      pasp_pos += strlen (name) + 1;
    }
  pasp[i] = NULL;
  if (n_operations_p)
    *n_operations_p = n_operations;
  return pasp;
}

void
gegl_operation_gtype_cleanup (void)
{
  G_LOCK (gtype_hash);
  if (gtype_hash)
    {
      g_hash_table_destroy (gtype_hash);
      gtype_hash = NULL;
    }
  G_UNLOCK (gtype_hash);
}

/**
 * gegl_operation_set_need_rect:
 * @operation:
 * @context_id:
 * @input_pad_name:
 * @region:
 *
 * Calculates the need rect for the node feeding into @input_pad_name
 * for the given graph evaluation that is identified by
 * @context_id. The need rect of a node mathematically depends on the
 * ROI that is being evaluated and is the area that evaluation of the
 * ROI depends on for the node in question.
 **/
static void
gegl_operation_set_need_rect (GeglOperation       *operation,
                              gpointer             context_id,
                              const gchar         *input_pad_name,
                              const GeglRectangle *region)
{
  GeglNode     *child;         /* the node which need rect we are affecting */
  GeglRectangle child_need;    /* the need rect of the child */
  GeglPad      *output_pad;

  g_return_if_fail (GEGL_IS_OPERATION (operation));
  g_return_if_fail (GEGL_IS_NODE (operation->node));
  g_return_if_fail (input_pad_name != NULL);

  {
    GeglPad *input_pad = gegl_node_get_pad (operation->node, input_pad_name);
    if (!input_pad)
      return;
    output_pad = gegl_pad_get_connected_to (input_pad);
    if (!output_pad)
      return;
    child = gegl_pad_get_node (output_pad);
    if (!child)
      return;
  }

  {
    GeglOperationContext *child_context = gegl_node_get_context (child, context_id);
    gegl_rectangle_bounding_box (&child_need, &child_context->need_rect, region);
    gegl_rectangle_intersect (&child_need, &child->have_rect, &child_need);

      /* If we're cached */
      if (child->cache)
        {
          GeglCache *cache = child->cache;
          GeglRectangle valid_box;
          gegl_region_get_clipbox (cache->valid_region, &valid_box);

          if (child_need.width == 0  ||
              child_need.height == 0 ||
              gegl_region_rect_in (cache->valid_region, &child_need) == GEGL_OVERLAP_RECTANGLE_IN)
            {
              child_context->result_rect = child_need;
              child_context->cached = TRUE;
              child_need.width = 0;
              child_need.height = 0;
            }
        }

    gegl_node_set_need_rect (child, context_id, &child_need);
  }
}

gboolean
gegl_operation_calc_need_rects (GeglOperation *operation,
                                gpointer       context_id)
{
  GSList          *input_pads;
  GeglOperationContext *context;
  GeglRectangle    request;

  context = gegl_node_get_context (operation->node, context_id);
  request = context->need_rect;

  /* For each input, do get_required_for_output() then use
   * gegl_operation_set_need_rect()
   */
  for (input_pads = operation->node->input_pads;input_pads;input_pads=input_pads->next)
    {
      const gchar *pad_name = gegl_pad_get_name (input_pads->data);
      GeglRectangle rect;
      rect = gegl_operation_get_required_for_output (operation, pad_name, &request);

      gegl_operation_set_need_rect (operation, context_id, pad_name, &rect);
    }
  return TRUE;
}

gboolean gegl_can_do_inplace_processing (GeglOperation       *operation,
                                         GeglBuffer          *input,
                                         const GeglRectangle *result)
{
  if (!input ||
      GEGL_IS_CACHE (input))
    return FALSE;
  if (gegl_object_get_has_forked (input))
    return FALSE;

  if (input->format == gegl_operation_get_format (operation, "output") &&
      gegl_rectangle_contains (gegl_buffer_get_extent (input), result))
    return TRUE;
  return FALSE;
}
