/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * test-theme.c: test program for CSS styling code
 *
 * Copyright 2009, 2010 Red Hat, Inc.
 *
 * This program 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.1 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <clutter/clutter.h>
#include "st-theme.h"
#include "st-theme-context.h"
#include "st-label.h"
#include "st-button.h"
#include <math.h>
#include <string.h>
#include <meta/main.h>
#include <meta/meta-backend.h>

static ClutterActor *stage;
static StThemeNode *root;
static StThemeNode *group1;
static StThemeNode *text1;
static StThemeNode *text2;
static StThemeNode *group2;
static StThemeNode *text3;
static StThemeNode *text4;
static StThemeNode *group3;
static StThemeNode *group4;
static StThemeNode *group5;
static StThemeNode *group6;
static StThemeNode *button;
static gboolean fail;

static const char *test;

static void
assert_font (StThemeNode *node,
	     const char  *node_description,
	     const char  *expected)
{
  char *value = pango_font_description_to_string (st_theme_node_get_font (node));

  if (strcmp (expected, value) != 0)
    {
      g_print ("%s: %s.font: expected: %s, got: %s\n",
	       test, node_description, expected, value);
      fail = TRUE;
    }

  g_free (value);
}

static void
assert_font_features (StThemeNode *node,
                      const char  *node_description,
                      const char  *expected)
{
  char *value = st_theme_node_get_font_features (node);

  if (g_strcmp0 (expected, value) != 0)
    {
      g_print ("%s: %s.font-feature-settings: expected: %s, got: %s\n",
               test, node_description, expected, value);
      fail = TRUE;
    }

  if (value)
    g_free (value);
}

static char *
text_decoration_to_string (StTextDecoration decoration)
{
  GString *result = g_string_new (NULL);

  if (decoration & ST_TEXT_DECORATION_UNDERLINE)
    g_string_append(result, " underline");
  if (decoration & ST_TEXT_DECORATION_OVERLINE)
    g_string_append(result, " overline");
  if (decoration & ST_TEXT_DECORATION_LINE_THROUGH)
    g_string_append(result, " line_through");
  if (decoration & ST_TEXT_DECORATION_BLINK)
    g_string_append(result, " blink");

  if (result->len > 0)
    g_string_erase (result, 0, 1);
  else
    g_string_append(result, "none");

  return g_string_free (result, FALSE);
}

static void
assert_text_decoration (StThemeNode     *node,
			const char      *node_description,
			StTextDecoration expected)
{
  StTextDecoration value = st_theme_node_get_text_decoration (node);
  if (expected != value)
    {
      char *es = text_decoration_to_string (expected);
      char *vs = text_decoration_to_string (value);

      g_print ("%s: %s.text-decoration: expected: %s, got: %s\n",
	       test, node_description, es, vs);
      fail = TRUE;

      g_free (es);
      g_free (vs);
    }
}

static void
assert_foreground_color (StThemeNode *node,
			 const char  *node_description,
			 guint32      expected)
{
  ClutterColor color;
  guint32 value;

  st_theme_node_get_foreground_color (node, &color);
  value = clutter_color_to_pixel (&color);

  if (expected != value)
    {
      g_print ("%s: %s.color: expected: #%08x, got: #%08x\n",
	       test, node_description, expected, value);
      fail = TRUE;
    }
}

static void
assert_background_color (StThemeNode *node,
			 const char  *node_description,
			 guint32      expected)
{
  ClutterColor color;
  guint32 value;

  st_theme_node_get_background_color (node, &color);
  value = clutter_color_to_pixel (&color);

  if (expected != value)
    {
      g_print ("%s: %s.background-color: expected: #%08x, got: #%08x\n",
	       test, node_description, expected, value);
      fail = TRUE;
    }
}

static const char *
side_to_string (StSide side)
{
  switch (side)
    {
    case ST_SIDE_TOP:
      return "top";
    case ST_SIDE_RIGHT:
      return "right";
    case ST_SIDE_BOTTOM:
      return "bottom";
    case ST_SIDE_LEFT:
      return "left";
    default:
      return "<unknown>";
    }
}

static void
assert_border_color (StThemeNode *node,
                     const char  *node_description,
                     StSide       side,
                     guint32      expected)
{
  ClutterColor color;
  guint32 value;

  st_theme_node_get_border_color (node, side, &color);
  value = clutter_color_to_pixel (&color);

  if (expected != value)
    {
      g_print ("%s: %s.border-%s-color: expected: #%08x, got: #%08x\n",
	       test, node_description, side_to_string (side), expected, value);
      fail = TRUE;
    }
}

static void
assert_background_image (StThemeNode *node,
			 const char  *node_description,
			 const char  *expected)
{
  GFile *value = st_theme_node_get_background_image (node);
  GFile *expected_file;

  if (expected != NULL && value != NULL)
    {
      expected_file = g_file_new_for_path (expected);

      if (!g_file_equal (expected_file, value))
        {
          char *uri = g_file_get_uri (expected_file);
          g_print ("%s: %s.background-image: expected: %s, got: %s\n",
                   test, node_description, expected, uri);
          fail = TRUE;
          g_free (uri);
        }
    }
}

#define LENGTH_EPSILON 0.001

static void
assert_length (const char     *node_description,
	       const char     *property_description,
	       double          expected,
	       double          value)
{
  if (fabs (expected - value) > LENGTH_EPSILON)
    {
      g_print ("%s %s.%s: expected: %3f, got: %3f\n",
	       test, node_description, property_description, expected, value);
      fail = TRUE;
    }
}

static void
test_defaults (void)
{
  test = "defaults";
  /* font comes from context */
  assert_font (root, "stage", "sans-serif 12");
  /* black is the default foreground color */
  assert_foreground_color (root, "stage", 0x00000ff);
}

static void
test_lengths (void)
{
  test = "lengths";
  /* 12pt == 16px at 96dpi */
  assert_length ("group1", "padding-top", 16.,
		 st_theme_node_get_padding (group1, ST_SIDE_TOP));
  /* 12px == 12px */
  assert_length ("group1", "padding-right", 12.,
		 st_theme_node_get_padding (group1, ST_SIDE_RIGHT));
  /* 2em == 32px (with a 12pt font) */
  assert_length ("group1", "padding-bottom", 32.,
		 st_theme_node_get_padding (group1, ST_SIDE_BOTTOM));
  /* 1in == 72pt == 96px, at 96dpi */
  assert_length ("group1", "padding-left", 96.,
		 st_theme_node_get_padding (group1, ST_SIDE_LEFT));

  /* 12pt == 16px at 96dpi */
  assert_length ("group1", "margin-top", 16.,
		 st_theme_node_get_margin (group1, ST_SIDE_TOP));
  /* 12px == 12px */
  assert_length ("group1", "margin-right", 12.,
		 st_theme_node_get_margin (group1, ST_SIDE_RIGHT));
  /* 2em == 32px (with a 12pt font) */
  assert_length ("group1", "margin-bottom", 32.,
		 st_theme_node_get_margin (group1, ST_SIDE_BOTTOM));
  /* 1in == 72pt == 96px, at 96dpi */
  assert_length ("group1", "margin-left", 96.,
		 st_theme_node_get_margin (group1, ST_SIDE_LEFT));
}

static void
test_classes (void)
{
  test = "classes";
  /* .special-text class overrides size and style;
   * the StBin.special-text selector doesn't match */
  assert_font (text1, "text1", "sans-serif Italic 32px");
}

static void
test_type_inheritance (void)
{
  test = "type_inheritance";
  /* From StBin element selector */
  assert_length ("button", "padding-top", 10.,
		 st_theme_node_get_padding (button, ST_SIDE_TOP));
  /* From StButton element selector */
  assert_length ("button", "padding-right", 20.,
		 st_theme_node_get_padding (button, ST_SIDE_RIGHT));
}

static void
test_adjacent_selector (void)
{
  test = "adjacent_selector";
  /* #group1 > #text1 matches text1 */
  assert_foreground_color (text1, "text1", 0x00ff00ff);
  /* stage > #text2 doesn't match text2 */
  assert_foreground_color (text2, "text2", 0x000000ff);
}

static void
test_padding (void)
{
  test = "padding";
  /* Test that a 4-sided padding property assigns the right paddings to
   * all sides */
  assert_length ("group2", "padding-top", 1.,
		 st_theme_node_get_padding (group2, ST_SIDE_TOP));
  assert_length ("group2", "padding-right", 2.,
		 st_theme_node_get_padding (group2, ST_SIDE_RIGHT));
  assert_length ("group2", "padding-bottom", 3.,
		 st_theme_node_get_padding (group2, ST_SIDE_BOTTOM));
  assert_length ("group2", "padding-left", 4.,
		 st_theme_node_get_padding (group2, ST_SIDE_LEFT));
}

static void
test_margin (void)
{
  test = "margin";
  /* Test that a 4-sided margin property assigns the right margin to
   * all sides */
  assert_length ("group2", "margin-top", 1.,
		 st_theme_node_get_margin (group2, ST_SIDE_TOP));
  assert_length ("group2", "margin-right", 2.,
		 st_theme_node_get_margin (group2, ST_SIDE_RIGHT));
  assert_length ("group2", "margin-bottom", 3.,
		 st_theme_node_get_margin (group2, ST_SIDE_BOTTOM));
  assert_length ("group2", "margin-left", 4.,
		 st_theme_node_get_margin (group2, ST_SIDE_LEFT));

  /* Test that a 3-sided margin property assigns the right margin to
   * all sides */
  assert_length ("group4", "margin-top", 1.,
     st_theme_node_get_margin (group4, ST_SIDE_TOP));
  assert_length ("group4", "margin-right", 2.,
     st_theme_node_get_margin (group4, ST_SIDE_RIGHT));
  assert_length ("group4", "margin-bottom", 3.,
     st_theme_node_get_margin (group4, ST_SIDE_BOTTOM));
  assert_length ("group4", "margin-left", 2.,
     st_theme_node_get_margin (group4, ST_SIDE_LEFT));

  /* Test that a 2-sided margin property assigns the right margin to
   * all sides */
  assert_length ("group5", "margin-top", 1.,
     st_theme_node_get_margin (group5, ST_SIDE_TOP));
  assert_length ("group5", "margin-right", 2.,
     st_theme_node_get_margin (group5, ST_SIDE_RIGHT));
  assert_length ("group5", "margin-bottom", 1.,
     st_theme_node_get_margin (group5, ST_SIDE_BOTTOM));
  assert_length ("group5", "margin-left", 2.,
     st_theme_node_get_margin (group5, ST_SIDE_LEFT));

  /* Test that all sides have a margin of 0 when not specified */
  assert_length ("group6", "margin-top", 0.,
     st_theme_node_get_margin (group6, ST_SIDE_TOP));
  assert_length ("group6", "margin-right", 0.,
     st_theme_node_get_margin (group6, ST_SIDE_RIGHT));
  assert_length ("group6", "margin-bottom", 0.,
     st_theme_node_get_margin (group6, ST_SIDE_BOTTOM));
  assert_length ("group6", "margin-left", 0.,
     st_theme_node_get_margin (group6, ST_SIDE_LEFT));
}

static void
test_border (void)
{
  test = "border";

  /* group2 is defined as having a thin black border along the top three
   * sides with rounded joins, then a square-joined green border at the
   * bottom
   */

  assert_length ("group2", "border-top-width", 2.,
		 st_theme_node_get_border_width (group2, ST_SIDE_TOP));
  assert_length ("group2", "border-right-width", 2.,
		 st_theme_node_get_border_width (group2, ST_SIDE_RIGHT));
  assert_length ("group2", "border-bottom-width", 5.,
		 st_theme_node_get_border_width (group2, ST_SIDE_BOTTOM));
  assert_length ("group2", "border-left-width", 2.,
		 st_theme_node_get_border_width (group2, ST_SIDE_LEFT));

  assert_border_color (group2, "group2", ST_SIDE_TOP,    0x000000ff);
  assert_border_color (group2, "group2", ST_SIDE_RIGHT,  0x000000ff);
  assert_border_color (group2, "group2", ST_SIDE_BOTTOM, 0x0000ffff);
  assert_border_color (group2, "group2", ST_SIDE_LEFT,   0x000000ff);

  assert_length ("group2", "border-radius-topleft", 10.,
		 st_theme_node_get_border_radius (group2, ST_CORNER_TOPLEFT));
  assert_length ("group2", "border-radius-topright", 10.,
		 st_theme_node_get_border_radius (group2, ST_CORNER_TOPRIGHT));
  assert_length ("group2", "border-radius-bottomright", 0.,
		 st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMRIGHT));
  assert_length ("group2", "border-radius-bottomleft", 0.,
		 st_theme_node_get_border_radius (group2, ST_CORNER_BOTTOMLEFT));
}

static void
test_background (void)
{
  test = "background";
  /* group1 has a background: shortcut property setting color and image */
  assert_background_color (group1, "group1", 0xff0000ff);
  assert_background_image (group1, "group1", "some-background.png");
  /* text1 inherits the background image but not the color */
  assert_background_color (text1,  "text1",  0x00000000);
  assert_background_image (text1,  "text1",  "some-background.png");
  /* text2 inherits both, but then background: none overrides both */
  assert_background_color (text2,  "text2",  0x00000000);
  assert_background_image (text2,  "text2",  NULL);
  /* background-image property */
  assert_background_image (group2, "group2", "other-background.png");
}

static void
test_font (void)
{
  test = "font";
  /* font specified with font: */
  assert_font (group2, "group2", "serif Italic 12px");
  /* text3 inherits and overrides individually properties */
  assert_font (text3,  "text3",  "serif Bold Oblique Small-Caps 24px");
}

static void
test_font_features (void)
{
  test = "font_features";
  /* group1 has font-feature-settings: "tnum" */
  assert_font_features (group1, "group1", "\"tnum\"");
  /* text2 should inherit from group1 */
  assert_font_features (text2,  "text2", "\"tnum\"");
  /* group2 has font-feature-settings: "tnum", "zero" */
  assert_font_features (group2, "group2", "\"tnum\", \"zero\"");
  /* text3 should inherit from group2 using the inherit keyword */
  assert_font_features (text3,  "text3",  "\"tnum\", \"zero\"");
  /* text4 has font-feature-settings: normal */
  assert_font_features (text4,  "text4",  NULL);
}

static void
test_pseudo_class (void)
{
  StWidget *label;
  StThemeNode *labelNode;

  test = "pseudo_class";
  /* text4 has :visited and :hover pseudo-classes, so should pick up both of these */
  assert_foreground_color (text4,   "text4",  0x888888ff);
  assert_text_decoration  (text4,   "text4",  ST_TEXT_DECORATION_UNDERLINE);
  /* :hover pseudo-class matches, but class doesn't match */
  assert_text_decoration  (group3,  "group3", 0);

  /* Test the StWidget add/remove pseudo_class interfaces */
  label = st_label_new ("foo");
  clutter_actor_add_child (stage, CLUTTER_ACTOR (label));

  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x000000ff);
  assert_text_decoration  (labelNode, "label", 0);
  assert_length ("label", "border-width", 0.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_add_style_pseudo_class (label, "visited");
  g_assert (st_widget_has_style_pseudo_class (label, "visited"));
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x888888ff);
  assert_text_decoration  (labelNode, "label", 0);
  assert_length ("label", "border-width", 0.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_add_style_pseudo_class (label, "hover");
  g_assert (st_widget_has_style_pseudo_class (label, "hover"));
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x888888ff);
  assert_text_decoration  (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
  assert_length ("label", "border-width", 0.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_remove_style_pseudo_class (label, "visited");
  g_assert (!st_widget_has_style_pseudo_class (label, "visited"));
  g_assert (st_widget_has_style_pseudo_class (label, "hover"));
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x000000ff);
  assert_text_decoration  (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
  assert_length ("label", "border-width", 0.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_add_style_pseudo_class (label, "boxed");
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x000000ff);
  assert_text_decoration  (labelNode, "label", ST_TEXT_DECORATION_UNDERLINE);
  assert_length ("label", "border-width", 1.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_remove_style_pseudo_class (label, "hover");
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x000000ff);
  assert_text_decoration  (labelNode, "label", 0);
  assert_length ("label", "border-width", 1.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));

  st_widget_remove_style_pseudo_class (label, "boxed");
  g_assert (st_widget_get_style_pseudo_class (label) == NULL);
  labelNode = st_widget_get_theme_node (label);
  assert_foreground_color (labelNode, "label", 0x000000ff);
  assert_text_decoration  (labelNode, "label", 0);
  assert_length ("label", "border-width", 0.,
                 st_theme_node_get_border_width (labelNode, ST_SIDE_TOP));
}

static void
test_inline_style (void)
{
  test = "inline_style";
  /* These properties come from the inline-style specified when creating the node */
  assert_foreground_color (text3,   "text3",  0x00000ffff);
  assert_length ("text3", "padding-bottom", 12.,
                 st_theme_node_get_padding (text3, ST_SIDE_BOTTOM));
}

int
main (int argc, char **argv)
{
  MetaBackend *backend;
  StTheme *theme;
  StThemeContext *context;
  PangoFontDescription *font_desc;
  GFile *file;
  g_autofree char *cwd = NULL;

  gtk_init (&argc, &argv);

  /* meta_init() cds to $HOME */
  cwd = g_get_current_dir ();

  meta_test_init ();

  if (chdir (cwd) < 0)
    g_error ("chdir('%s') failed: %s", cwd, g_strerror (errno));

  /* Make sure our assumptions about resolution are correct */
  g_object_set (clutter_settings_get_default (), "font-dpi", -1, NULL);

  file = g_file_new_for_path ("test-theme.css");
  theme = st_theme_new (file, NULL, NULL);
  g_object_unref (file);

  backend = meta_get_backend ();
  stage = meta_backend_get_stage (backend);
  context = st_theme_context_get_for_stage (CLUTTER_STAGE (stage));
  st_theme_context_set_theme (context, theme);

  font_desc = pango_font_description_from_string ("sans-serif 12");
  st_theme_context_set_font (context, font_desc);
  pango_font_description_free (font_desc);

  root = st_theme_context_get_root_node (context);
  group1 = st_theme_node_new (context, root, NULL,
                              CLUTTER_TYPE_ACTOR, "group1", NULL, NULL, NULL);
  text1 = st_theme_node_new  (context, group1, NULL,
                              CLUTTER_TYPE_TEXT, "text1", "special-text", NULL, NULL);
  text2 = st_theme_node_new  (context, group1, NULL,
                              CLUTTER_TYPE_TEXT, "text2", NULL, NULL, NULL);
  group2 = st_theme_node_new (context, root, NULL,
                              CLUTTER_TYPE_ACTOR, "group2", NULL, NULL, NULL);
  group4 = st_theme_node_new (context, root, NULL,
                              CLUTTER_TYPE_ACTOR, "group4", NULL, NULL, NULL);
  group5 = st_theme_node_new (context, root, NULL,
                              CLUTTER_TYPE_ACTOR, "group5", NULL, NULL, NULL);
  group6 = st_theme_node_new (context, root, NULL,
                              CLUTTER_TYPE_ACTOR, "group6", NULL, NULL, NULL);
  text3 = st_theme_node_new  (context, group2, NULL,
                              CLUTTER_TYPE_TEXT, "text3", NULL, NULL,
                              "color: #0000ff; padding-bottom: 12px;");
  text4 = st_theme_node_new  (context, group2, NULL,
                              CLUTTER_TYPE_TEXT, "text4", NULL, "visited hover", NULL);
  group3 = st_theme_node_new (context, group2, NULL,
                              CLUTTER_TYPE_ACTOR, "group3", NULL, "hover", NULL);
  button = st_theme_node_new (context, root, NULL,
                              ST_TYPE_BUTTON, "button", NULL, NULL, NULL);

  test_defaults ();
  test_lengths ();
  test_classes ();
  test_type_inheritance ();
  test_adjacent_selector ();
  test_padding ();
  test_margin ();
  test_border ();
  test_background ();
  test_font ();
  test_font_features ();
  test_pseudo_class ();
  test_inline_style ();

  g_object_unref (button);
  g_object_unref (group1);
  g_object_unref (group2);
  g_object_unref (group3);
  g_object_unref (group4);
  g_object_unref (group5);
  g_object_unref (group6);
  g_object_unref (text1);
  g_object_unref (text2);
  g_object_unref (text3);
  g_object_unref (text4);
  g_object_unref (theme);

  clutter_actor_destroy (stage);

  return fail ? 1 : 0;
}
