/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */
/**
 * SECTION:ctk-bin
 * @short_description: A container for one child
 * @include: ctk-bin.h
 *
 * CtkBin is a container for one child and should be used to build Ctk widgets
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "ctk-bin.h"

#include <math.h>
#include <clutk/ctk-focusable.h>

static void clutter_container_iface_init (ClutterContainerIface *iface);

G_DEFINE_ABSTRACT_TYPE_WITH_CODE (CtkBin,
                                  ctk_bin,
                                  CTK_TYPE_ACTOR,
                                  G_IMPLEMENT_INTERFACE
                                  (CLUTTER_TYPE_CONTAINER,
                                   clutter_container_iface_init));

#define CTK_BIN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_BIN, \
  CtkBinPrivate))

struct _CtkBinPrivate
{
  ClutterActor *child;
};

/* Globals */

/* Forwards */
static void ctk_bin_add              (ClutterContainer *container,
                                      ClutterActor     *actor);
static void ctk_bin_remove           (ClutterContainer *container,
                                      ClutterActor *actor);
static void ctk_bin_foreach          (ClutterContainer *container,
                                      ClutterCallback   callback,
                                      gpointer          user_data);
static void ctk_bin_foreach_with_internals (ClutterContainer *actor,
    ClutterCallback  callback,
    gpointer         user_data);
static void ctk_bin_raise            (ClutterContainer *container,
                                      ClutterActor     *actor,
                                      ClutterActor     *sibling);
static void ctk_bin_lower            (ClutterContainer *container,
                                      ClutterActor     *actor,
                                      ClutterActor     *sibling);
static void ctk_bin_sort_depth_order (ClutterContainer *container);

static void ctk_bin_focus_in_event   (ClutterActor *actor);

/* GObject stuff */
static void
clutter_container_iface_init (ClutterContainerIface *iface)
{
  iface->add                    = ctk_bin_add;
  iface->remove                 = ctk_bin_remove;
  iface->foreach                = ctk_bin_foreach;
  iface->raise                  = ctk_bin_raise;
  iface->lower                  = ctk_bin_lower;
  iface->sort_depth_order       = ctk_bin_sort_depth_order;
  iface->foreach_with_internals = ctk_bin_foreach_with_internals;
}

static void
ctk_bin_dispose (GObject *object)
{
  CtkBin *bin = CTK_BIN (object);

  if (CLUTTER_IS_ACTOR (bin->priv->child))
    {
      clutter_actor_unparent (bin->priv->child);
      bin->priv->child = NULL;
    }

  G_OBJECT_CLASS (ctk_bin_parent_class)->dispose (object);
}

static void
ctk_bin_paint (ClutterActor *actor)
{
  CtkActor *ctk_actor = CTK_ACTOR (actor);
  GSList   *effects   = NULL;

  if (!ctk_actor_get_effects_painting (ctk_actor)
      && (effects = ctk_actor_get_effects (ctk_actor)))
    {
      GSList *e;
      ctk_actor_set_effects_painting (ctk_actor, TRUE);

      for (e = effects; e; e = e->next)
        {
          gboolean last_effect = e->next ? FALSE : TRUE;
          ctk_effect_paint (e->data, ctk_bin_paint, last_effect);
        }

      ctk_actor_set_effects_painting (ctk_actor, FALSE);
    }
  else
    {
      CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->paint (actor);

      if (!CLUTTER_IS_ACTOR (CTK_BIN (actor)->priv->child))
        return;

      if (CLUTTER_ACTOR_IS_VISIBLE (CTK_BIN (actor)->priv->child))
        clutter_actor_paint (CTK_BIN (actor)->priv->child);
    }
    ctk_actor_set_damaged (ctk_actor, FALSE);
}

static void
ctk_bin_pick (ClutterActor *actor, const ClutterColor *color)
{
  /* Chain up so we get a bounding box painted (if we are reactive) */
  CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->pick (actor, color);

  /* Just forward to the paint call which in turn will trigger
   * the child actors also getting 'picked'.
   */
  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
    {
      ctk_actor_set_effects_painting (CTK_ACTOR (actor), TRUE);
      ctk_bin_paint (actor);
      ctk_actor_set_effects_painting (CTK_ACTOR (actor), FALSE);
    }
}

static void
ctk_bin_map (ClutterActor *actor)
{
  ClutterActor *child;

  CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->map (actor);

  child = CTK_BIN (actor)->priv->child;

  if (CLUTTER_IS_ACTOR (child))
    clutter_actor_map (child);
}

static void
ctk_bin_unmap (ClutterActor *actor)
{
  ClutterActor *child;

  CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->unmap (actor);

  child = CTK_BIN (actor)->priv->child;

  if (CLUTTER_IS_ACTOR (child))
    clutter_actor_unmap (child);
}

static void
ctk_bin_allocate (ClutterActor          *actor,
                  const ClutterActorBox *box,
                  ClutterAllocationFlags flags)
{
  CtkPadding      padding;
  ClutterActorBox child_box;
  gfloat          width;
  gfloat          height;

  CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->allocate (actor, box, flags);

  if (!CLUTTER_IS_ACTOR (ctk_bin_get_child (CTK_BIN (actor))))
    return;

  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);

  width = box->x2 - box->x1;
  height = box->y2 - box->y1;

  child_box.x1 = floor (padding.left);
  child_box.x2 = floor (child_box.x1 + (width - padding.left - padding.right));
  child_box.y1 = floor (padding.top);
  child_box.y2 = floor (child_box.y1 + (height - padding.top - padding.bottom));

  clutter_actor_allocate (CTK_BIN (actor)->priv->child, &child_box, flags);
}

static void
ctk_bin_get_preferred_width (ClutterActor *actor,
                             gfloat   for_height,
                             gfloat  *min_width,
                             gfloat  *nat_width)
{
  ClutterActor *child;

  child = ctk_bin_get_child (CTK_BIN (actor));

  if (min_width)
    *min_width = 0;
  if (nat_width)
    *nat_width = 0;

  if (child)
    {
      clutter_actor_get_preferred_width (child, for_height, min_width, nat_width);
    }
}

static void
ctk_bin_get_preferred_height (ClutterActor *actor,
                              gfloat   for_width,
                              gfloat  *min_height,
                              gfloat  *nat_height)
{
  ClutterActor *child;

  child = ctk_bin_get_child (CTK_BIN (actor));

  if (min_height)
    *min_height = 0;
  if (nat_height)
    *nat_height = 0;

  if (child)
    {
      clutter_actor_get_preferred_height (child,
                                          for_width,
                                          min_height,
                                          nat_height);
    }
}

static void
ctk_bin_class_init (CtkBinClass *klass)
{
  GObjectClass      *obj_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *act_class = CLUTTER_ACTOR_CLASS (klass);

  obj_class->dispose              = ctk_bin_dispose;

  act_class->paint                = ctk_bin_paint;
  act_class->pick                 = ctk_bin_pick;
  act_class->allocate             = ctk_bin_allocate;
  act_class->key_focus_in         = ctk_bin_focus_in_event;
  act_class->get_preferred_width  = ctk_bin_get_preferred_width;
  act_class->get_preferred_height = ctk_bin_get_preferred_height;
  act_class->map                  = ctk_bin_map;
  act_class->unmap                = ctk_bin_unmap;

  g_type_class_add_private (obj_class, sizeof (CtkBinPrivate));
}

static void
ctk_bin_init (CtkBin *bin)
{
  CtkBinPrivate     *priv;

  priv = bin->priv = CTK_BIN_GET_PRIVATE (bin);
}

/*
 * Private methods
 */

static void
ctk_bin_add (ClutterContainer *container,
             ClutterActor     *actor)
{
  CtkBinPrivate *priv;

  g_return_if_fail (CTK_IS_BIN (container));
  priv = CTK_BIN (container)->priv;

  if (priv->child)
    {
      g_warning ("Unable to add child to CtkBin: there is already a "
                 "child set");
      return;
    }

  g_object_ref (actor);

  priv->child = actor;
  clutter_actor_set_parent (actor, CLUTTER_ACTOR (container));

  /* queue a relayout, to get the correct positioning inside
   * the ::actor-removed signal handlers
   */
  clutter_actor_queue_relayout (CLUTTER_ACTOR (container));

  g_signal_emit_by_name (container, "actor-added", actor);

  g_object_unref (actor);
}

static void
ctk_bin_remove (ClutterContainer *container,
                ClutterActor     *actor)
{
  CtkBinPrivate *priv;

  g_return_if_fail (CTK_IS_BIN (container));
  g_return_if_fail (CLUTTER_IS_ACTOR (actor));
  priv = CTK_BIN (container)->priv;

  g_return_if_fail (priv->child == actor);

  g_object_ref (actor);

  priv->child = NULL;
  clutter_actor_unparent (actor);

  /* queue a relayout, to get the correct positioning inside
   * the ::actor-removed signal handlers
   */
  clutter_actor_queue_relayout (CLUTTER_ACTOR (container));

  g_signal_emit_by_name (container, "actor-removed", actor);

  if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (container)))
    clutter_actor_queue_redraw (CLUTTER_ACTOR (container));

  g_object_unref (actor);
}

static void
ctk_bin_foreach (ClutterContainer *container,
                 ClutterCallback   callback,
                 gpointer          user_data)
{
  CtkBinPrivate *priv;

  g_return_if_fail (CTK_IS_BIN (container));
  priv = CTK_BIN (container)->priv;

  if (CLUTTER_IS_ACTOR (priv->child))
    (* callback) (priv->child, user_data);
}

static void
ctk_bin_foreach_with_internals (ClutterContainer *container,
                                ClutterCallback  callback,
                                gpointer         user_data)
{
  CtkBinPrivate *priv;

  g_return_if_fail (CTK_IS_BIN (container));
  priv = CTK_BIN (container)->priv;

  if (CLUTTER_IS_ACTOR (priv->child))
    (* callback) (priv->child, user_data);
}

static void
ctk_bin_raise (ClutterContainer *container,
               ClutterActor     *actor,
               ClutterActor     *sibling)
{
  /* Null op */
}

static void
ctk_bin_lower (ClutterContainer *container,
               ClutterActor     *actor,
               ClutterActor     *sibling)
{
  /* Null op */
}

static void
ctk_bin_sort_depth_order (ClutterContainer *container)
{
  /* Null op */
}

static void
ctk_bin_focus_in_event (ClutterActor *actor)
{
  CtkBinPrivate *priv;

  g_return_if_fail (CTK_IS_BIN (actor));
  priv = CTK_BIN (actor)->priv;

  CLUTTER_ACTOR_CLASS (ctk_bin_parent_class)->key_focus_in (actor);

  if (CTK_IS_FOCUSABLE (priv->child))
    {
      ctk_focusable_set_focused (CTK_FOCUSABLE (priv->child), TRUE);
    }
}

/*
 * Public methods
 */
/**
 * ctk_bin_get_child:
 * @bin: A CtkBin
 *
 * Gets the single child of @bin - if there is no child set this will return NULL
 *
 * Returns: a #ClutterActor or NULL
 */
ClutterActor *
ctk_bin_get_child (CtkBin *bin)
{
  g_return_val_if_fail (CTK_IS_BIN (bin), NULL);
  return bin->priv->child;
}

