/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/* Mutter box operation testing program */

/*
 * Copyright (C) 2005 Elijah Newren
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "tests/boxes-tests.h"

#include <glib.h>
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xutil.h>
#include <time.h>
#include <math.h>

#include "core/boxes-private.h"

#define NUM_RANDOM_RUNS 10000

static void
init_random_ness (void)
{
  srand (time (NULL));
}

static void
get_random_rect (MetaRectangle *rect)
{
  rect->x = rand () % 1600;
  rect->y = rand () % 1200;
  rect->width  = rand () % 1600 + 1;
  rect->height = rand () % 1200 + 1;
}

static MetaRectangle*
new_meta_rect (int x, int y, int width, int height)
{
  MetaRectangle* temporary;
  temporary = g_new (MetaRectangle, 1);
  temporary->x = x;
  temporary->y = y;
  temporary->width  = width;
  temporary->height = height;

  return temporary;
}

static MetaStrut*
new_meta_strut (int x, int y, int width, int height, int side)
{
  MetaStrut* temporary;
  temporary = g_new (MetaStrut, 1);
  temporary->rect = meta_rect(x, y, width, height);
  temporary->side = side;

  return temporary;
}

static MetaEdge*
new_screen_edge (int x, int y, int width, int height, int side_type)
{
  MetaEdge* temporary;
  temporary = g_new (MetaEdge, 1);
  temporary->rect.x = x;
  temporary->rect.y = y;
  temporary->rect.width  = width;
  temporary->rect.height = height;
  temporary->side_type = side_type;
  temporary->edge_type = META_EDGE_SCREEN;

  return temporary;
}

static MetaEdge*
new_monitor_edge (int x, int y, int width, int height, int side_type)
{
  MetaEdge* temporary;
  temporary = g_new (MetaEdge, 1);
  temporary->rect.x = x;
  temporary->rect.y = y;
  temporary->rect.width  = width;
  temporary->rect.height = height;
  temporary->side_type = side_type;
  temporary->edge_type = META_EDGE_MONITOR;

  return temporary;
}

static void
test_area (void)
{
  MetaRectangle temp;
  int i;
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&temp);
      g_assert (meta_rectangle_area (&temp) == temp.width * temp.height);
    }

  temp = meta_rect (0, 0, 5, 7);
  g_assert (meta_rectangle_area (&temp) == 35);
}

static void
test_intersect (void)
{
  MetaRectangle a = {100, 200,  50,  40};
  MetaRectangle b = {  0,  50, 110, 152};
  MetaRectangle c = {  0,   0,  10,  10};
  MetaRectangle d = {100, 100,  50,  50};
  MetaRectangle b_intersect_d = {100, 100, 10, 50};
  MetaRectangle temp;
  MetaRectangle temp2;

  meta_rectangle_intersect (&a, &b, &temp);
  temp2 = meta_rect (100, 200, 10, 2);
  g_assert (meta_rectangle_equal (&temp, &temp2));
  g_assert (meta_rectangle_area (&temp) == 20);

  meta_rectangle_intersect (&a, &c, &temp);
  g_assert (meta_rectangle_area (&temp) == 0);

  meta_rectangle_intersect (&a, &d, &temp);
  g_assert (meta_rectangle_area (&temp) == 0);

  meta_rectangle_intersect (&b, &d, &b);
  g_assert (meta_rectangle_equal (&b, &b_intersect_d));
}

static void
test_equal (void)
{
  MetaRectangle a = {10, 12, 4, 18};
  MetaRectangle b = a;
  MetaRectangle c = {10, 12, 4, 19};
  MetaRectangle d = {10, 12, 7, 18};
  MetaRectangle e = {10, 62, 4, 18};
  MetaRectangle f = {27, 12, 4, 18};

  g_assert ( meta_rectangle_equal (&a, &b));
  g_assert (!meta_rectangle_equal (&a, &c));
  g_assert (!meta_rectangle_equal (&a, &d));
  g_assert (!meta_rectangle_equal (&a, &e));
  g_assert (!meta_rectangle_equal (&a, &f));
}

static void
test_overlap_funcs (void)
{
  MetaRectangle temp1, temp2;
  int i;
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&temp1);
      get_random_rect (&temp2);
      g_assert (meta_rectangle_overlap (&temp1, &temp2) ==
                (meta_rectangle_horiz_overlap (&temp1, &temp2) &&
                 meta_rectangle_vert_overlap (&temp1, &temp2)));
    }

  temp1 = meta_rect ( 0, 0, 10, 10);
  temp2 = meta_rect (20, 0, 10,  5);
  g_assert (!meta_rectangle_overlap (&temp1, &temp2));
  g_assert (!meta_rectangle_horiz_overlap (&temp1, &temp2));
  g_assert ( meta_rectangle_vert_overlap (&temp1, &temp2));
}

static void
test_basic_fitting (void)
{
  MetaRectangle temp1, temp2, temp3;
  int i;
  /* Four cases:
   *   case   temp1 fits temp2    temp1 could fit temp2
   *     1           Y                      Y
   *     2           N                      Y
   *     3           Y                      N
   *     4           N                      N
   * Of the four cases, case 3 is impossible.  An alternate way of looking
   * at this table is that either the middle column must be no, or the last
   * column must be yes.  So we test that.  Also, we can repeat the test
   * reversing temp1 and temp2.
   */
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&temp1);
      get_random_rect (&temp2);
      g_assert (meta_rectangle_contains_rect (&temp1, &temp2) == FALSE ||
                meta_rectangle_could_fit_rect (&temp1, &temp2) == TRUE);
      g_assert (meta_rectangle_contains_rect (&temp2, &temp1) == FALSE ||
                meta_rectangle_could_fit_rect (&temp2, &temp1) == TRUE);
    }

  temp1 = meta_rect ( 0, 0, 10, 10);
  temp2 = meta_rect ( 5, 5,  5,  5);
  temp3 = meta_rect ( 8, 2,  3,  7);
  g_assert ( meta_rectangle_contains_rect (&temp1, &temp2));
  g_assert (!meta_rectangle_contains_rect (&temp2, &temp1));
  g_assert (!meta_rectangle_contains_rect (&temp1, &temp3));
  g_assert ( meta_rectangle_could_fit_rect (&temp1, &temp3));
  g_assert (!meta_rectangle_could_fit_rect (&temp3, &temp2));
}

static void
free_strut_list (GSList *struts)
{
  g_slist_free_full (struts, g_free);
}

static GSList*
get_strut_list (int which)
{
  GSList *ans;
  MetaSide wc = 0; /* wc == who cares? ;-) */

  ans = NULL;

  g_assert (which >=0 && which <= 6);
  switch (which)
    {
    case 0:
      break;
    case 1:
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0, 1600,   20, META_SIDE_TOP));
      ans = g_slist_prepend (ans, new_meta_strut ( 400, 1160, 1600,   40, META_SIDE_BOTTOM));
      break;
    case 2:
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0, 1600,   20, META_SIDE_TOP));
      ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100,  400,  100, META_SIDE_BOTTOM));
      ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150,  150,   50, META_SIDE_BOTTOM));
      break;
    case 3:
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0, 1600,   20, META_SIDE_TOP));
      ans = g_slist_prepend (ans, new_meta_strut ( 800, 1100,  400,  100, META_SIDE_LEFT));
      ans = g_slist_prepend (ans, new_meta_strut ( 300, 1150,   80,   50, META_SIDE_BOTTOM));
      ans = g_slist_prepend (ans, new_meta_strut ( 700,  525,  200,  150, wc));
      break;
    case 4:
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0,  800, 1200, META_SIDE_LEFT));
      ans = g_slist_prepend (ans, new_meta_strut ( 800,    0, 1600,   20, META_SIDE_TOP));
      break;
    case 5:
      ans = g_slist_prepend (ans, new_meta_strut ( 800,    0, 1600,   20, META_SIDE_TOP));
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0,  800, 1200, META_SIDE_LEFT));
      ans = g_slist_prepend (ans, new_meta_strut ( 800,   10,  800, 1200, META_SIDE_RIGHT));
      break;
    case 6:
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0, 1600,   40, META_SIDE_TOP));
      ans = g_slist_prepend (ans, new_meta_strut (   0,    0, 1600,   20, META_SIDE_TOP));
      break;
    }

  return ans;
}

static GList*
get_screen_region (int which)
{
  GList *ret;
  GSList *struts;
  MetaRectangle basic_rect;

  basic_rect = meta_rect (0, 0, 1600, 1200);
  ret = NULL;

  struts = get_strut_list (which);
  ret = meta_rectangle_get_minimal_spanning_set_for_region (&basic_rect, struts);
  free_strut_list (struts);

  return ret;
}

static GList*
get_screen_edges (int which)
{
  GList *ret;
  GSList *struts;
  MetaRectangle basic_rect;

  basic_rect = meta_rect (0, 0, 1600, 1200);
  ret = NULL;

  struts = get_strut_list (which);
  ret = meta_rectangle_find_onscreen_edges (&basic_rect, struts);
  free_strut_list (struts);

  return ret;
}

static GList*
get_monitor_edges (int which_monitor_set, int which_strut_set)
{
  GList *ret;
  GSList *struts;
  GList *xins;

  xins = NULL;
  g_assert (which_monitor_set >=0 && which_monitor_set <= 3);
  switch (which_monitor_set)
    {
    case 0:
      xins = g_list_prepend (xins, new_meta_rect (  0,   0, 1600, 1200));
      break;
    case 1:
      xins = g_list_prepend (xins, new_meta_rect (  0,   0,  800, 1200));
      xins = g_list_prepend (xins, new_meta_rect (800,   0,  800, 1200));
      break;
    case 2:
      xins = g_list_prepend (xins, new_meta_rect (  0,   0, 1600,  600));
      xins = g_list_prepend (xins, new_meta_rect (  0, 600, 1600,  600));
      break;
    case 3:
      xins = g_list_prepend (xins, new_meta_rect (  0,   0, 1600,  600));
      xins = g_list_prepend (xins, new_meta_rect (  0, 600,  800,  600));
      xins = g_list_prepend (xins, new_meta_rect (800, 600,  800,  600));
      break;
    }

  ret = NULL;

  struts = get_strut_list (which_strut_set);
  ret = meta_rectangle_find_nonintersected_monitor_edges (xins, struts);

  free_strut_list (struts);
  meta_rectangle_free_list_and_elements (xins);

  return ret;
}

#if 0
static void
test_merge_regions (void)
{
  /* logarithmically distributed random number of struts (range?)
   * logarithmically distributed random size of struts (up to screen size???)
   * uniformly distributed location of center of struts (within screen)
   * merge all regions that are possible
   * print stats on problem setup
   *   number of (non-completely-occluded?) struts
   *   percentage of screen covered
   *   length of resulting non-minimal spanning set
   *   length of resulting minimal spanning set
   * print stats on merged regions:
   *   number boxes merged
   *   number of those merges that were of the form A contains B
   *   number of those merges that were of the form A partially contains B
   *   number of those merges that were of the form A is adjacent to B
   */

  GList* region;
  GList* compare;
  int num_contains, num_merged, num_part_contains, num_adjacent;

  num_contains = num_merged = num_part_contains = num_adjacent = 0;
  compare = region = get_screen_region (2);
  g_assert (region);

  printf ("Merging stats:\n");
  printf ("  Length of initial list: %d\n", g_list_length (region));
#ifdef PRINT_DEBUG
  char rect1[RECT_LENGTH], rect2[RECT_LENGTH];
  char region_list[(RECT_LENGTH + 2) * g_list_length (region)];
  meta_rectangle_region_to_string (region, ", ", region_list);
  printf ("  Initial rectangles: %s\n", region_list);
#endif

  while (compare && compare->next)
    {
      MetaRectangle *a = compare->data;
      GList *other = compare->next;

      g_assert (a->width > 0 && a->height > 0);

      while (other)
        {
          MetaRectangle *b = other->data;
          GList *delete_me = NULL;

          g_assert (b->width > 0 && b->height > 0);

#ifdef PRINT_DEBUG
          printf ("    -- Comparing %s to %s --\n",
                  meta_rectangle_to_string (a, rect1),
                  meta_rectangle_to_string (b, rect2));
#endif

          /* If a contains b, just remove b */
          if (meta_rectangle_contains_rect (a, b))
            {
              delete_me = other;
              num_contains++;
              num_merged++;
            }
          /* If b contains a, just remove a */
          else if (meta_rectangle_contains_rect (a, b))
            {
              delete_me = compare;
              num_contains++;
              num_merged++;
            }
          /* If a and b might be mergeable horizontally */
          else if (a->y == b->y && a->height == b->height)
            {
              /* If a and b overlap */
              if (meta_rectangle_overlap (a, b))
                {
                  int new_x = MIN (a->x, b->x);
                  a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
                  a->x = new_x;
                  delete_me = other;
                  num_part_contains++;
                  num_merged++;
                }
              /* If a and b are adjacent */
              else if (a->x + a->width == b->x || a->x == b->x + b->width)
                {
                  int new_x = MIN (a->x, b->x);
                  a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
                  a->x = new_x;
                  delete_me = other;
                  num_adjacent++;
                  num_merged++;
                }
            }
          /* If a and b might be mergeable vertically */
          else if (a->x == b->x && a->width == b->width)
            {
              /* If a and b overlap */
              if (meta_rectangle_overlap (a, b))
                {
                  int new_y = MIN (a->y, b->y);
                  a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
                  a->y = new_y;
                  delete_me = other;
                  num_part_contains++;
                  num_merged++;
                }
              /* If a and b are adjacent */
              else if (a->y + a->height == b->y || a->y == b->y + b->height)
                {
                  int new_y = MIN (a->y, b->y);
                  a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
                  a->y = new_y;
                  delete_me = other;
                  num_adjacent++;
                  num_merged++;
                }
            }

          other = other->next;

          /* Delete any rectangle in the list that is no longer wanted */
          if (delete_me != NULL)
            {
#ifdef PRINT_DEBUG
              MetaRectangle *bla = delete_me->data;
              printf ("    Deleting rect %s\n",
                      meta_rectangle_to_string (bla, rect1));
#endif

              /* Deleting the rect we're compare others to is a little tricker */
              if (compare == delete_me)
                {
                  compare = compare->next;
                  other = compare->next;
                  a = compare->data;
                }

              /* Okay, we can free it now */
              g_free (delete_me->data);
              region = g_list_delete_link (region, delete_me);
            }

#ifdef PRINT_DEBUG
          char region_list[(RECT_LENGTH + 2) * g_list_length (region)];
          meta_rectangle_region_to_string (region, ", ", region_list);
          printf ("      After comparison, new list is: %s\n", region_list);
#endif
        }

      compare = compare->next;
    }

  printf ("  Num rectangles contained in others          : %d\n",
          num_contains);
  printf ("  Num rectangles partially contained in others: %d\n",
          num_part_contains);
  printf ("  Num rectangles adjacent to others           : %d\n",
          num_adjacent);
  printf ("  Num rectangles merged with others           : %d\n",
          num_merged);
#ifdef PRINT_DEBUG
  char region_list2[(RECT_LENGTH + 2) * g_list_length (region)];
  meta_rectangle_region_to_string (region, ", ", region_list2);
  printf ("  Final rectangles: %s\n", region_list2);
#endif

  meta_rectangle_free_spanning_set (region);
  region = NULL;

  printf ("%s passed.\n", G_STRFUNC);
}
#endif

static void
verify_lists_are_equal (GList *code, GList *answer)
{
  int which = 0;

  while (code && answer)
    {
      MetaRectangle *a = code->data;
      MetaRectangle *b = answer->data;

      if (a->x      != b->x     ||
          a->y      != b->y     ||
          a->width  != b->width ||
          a->height != b->height)
        {
          g_error ("%dth item in code answer answer lists do not match; "
                   "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n",
                   which,
                   a->x, a->y, a->width, a->height,
                   b->x, b->y, b->width, b->height);
        }

      code = code->next;
      answer = answer->next;

      which++;
    }

  /* Ought to be at the end of both lists; check if we aren't */
  if (code)
    {
      MetaRectangle *tmp = code->data;
      g_error ("code list longer than answer list by %d items; "
               "first extra item: %d,%d +%d,%d\n",
               g_list_length (code),
               tmp->x, tmp->y, tmp->width, tmp->height);
    }

  if (answer)
    {
      MetaRectangle *tmp = answer->data;
      g_error ("answer list longer than code list by %d items; "
               "first extra item: %d,%d +%d,%d\n",
               g_list_length (answer),
               tmp->x, tmp->y, tmp->width, tmp->height);
    }
}

static void
test_regions_okay (void)
{
  GList* region;
  GList* tmp;

  /*************************************************************/
  /* Make sure test region 0 has the right spanning rectangles */
  /*************************************************************/
  region = get_screen_region (0);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_meta_rect (0, 0, 1600, 1200));
  verify_lists_are_equal (region, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (region);

  /*************************************************************/
  /* Make sure test region 1 has the right spanning rectangles */
  /*************************************************************/
  region = get_screen_region (1);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_meta_rect (0, 20,  400, 1180));
  tmp = g_list_prepend (tmp, new_meta_rect (0, 20, 1600, 1140));
  verify_lists_are_equal (region, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (region);

  /*************************************************************/
  /* Make sure test region 2 has the right spanning rectangles */
  /*************************************************************/
  region = get_screen_region (2);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_meta_rect (   0,   20,  300, 1180));
  tmp = g_list_prepend (tmp, new_meta_rect ( 450,   20,  350, 1180));
  tmp = g_list_prepend (tmp, new_meta_rect (1200,   20,  400, 1180));
  tmp = g_list_prepend (tmp, new_meta_rect (   0,   20,  800, 1130));
  tmp = g_list_prepend (tmp, new_meta_rect (   0,   20, 1600, 1080));
  verify_lists_are_equal (region, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (region);

  /*************************************************************/
  /* Make sure test region 3 has the right spanning rectangles */
  /*************************************************************/
  region = get_screen_region (3);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_meta_rect (   0,   20,  300, 1180)); /* 354000 */
  tmp = g_list_prepend (tmp, new_meta_rect ( 380,   20,  1220, 1180)); /* 377600 */
  tmp = g_list_prepend (tmp, new_meta_rect (   0,   20,  1600, 1130)); /* 791000 */
#if 0
  printf ("Got to here...\n");
  char region_list[(RECT_LENGTH+2) * g_list_length (region)];
  char tmp_list[   (RECT_LENGTH+2) * g_list_length (tmp)];
  meta_rectangle_region_to_string (region, ", ", region_list);
  meta_rectangle_region_to_string (region, ", ", tmp_list);
  printf ("%s vs. %s\n", region_list, tmp_list);
#endif
  verify_lists_are_equal (region, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (region);

  /*************************************************************/
  /* Make sure test region 4 has the right spanning rectangles */
  /*************************************************************/
  region = get_screen_region (4);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_meta_rect ( 800,   20,  800, 1180));
  verify_lists_are_equal (region, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (region);

  /*************************************************************/
  /* Make sure test region 5 has the right spanning rectangles */
  /*************************************************************/
  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                         "Region to merge was empty!*");
  region = get_screen_region (5);
  g_test_assert_expected_messages ();

  verify_lists_are_equal (region, NULL);

  /* FIXME: Still to do:
   *   - Create random struts and check the regions somehow
   */
}

static void
test_region_fitting (void)
{
  GList* region;
  MetaRectangle rect;

  /* See test_basic_fitting() for how/why these automated random tests work */
  int i;
  region = get_screen_region (3);
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&rect);
      g_assert (meta_rectangle_contained_in_region (region, &rect) == FALSE ||
                meta_rectangle_could_fit_in_region (region, &rect) == TRUE);
    }
  meta_rectangle_free_list_and_elements (region);

  /* Do some manual tests too */
  region = get_screen_region (1);

  rect = meta_rect (50, 50, 400, 400);
  g_assert (meta_rectangle_could_fit_in_region (region, &rect));
  g_assert (meta_rectangle_contained_in_region (region, &rect));

  rect = meta_rect (250, 0, 500, 1150);
  g_assert (!meta_rectangle_could_fit_in_region (region, &rect));
  g_assert (!meta_rectangle_contained_in_region (region, &rect));

  rect = meta_rect (250, 0, 400, 400);
  g_assert (meta_rectangle_could_fit_in_region (region, &rect));
  g_assert (!meta_rectangle_contained_in_region (region, &rect));

  meta_rectangle_free_list_and_elements (region);

  region = get_screen_region (2);
  rect = meta_rect (1000, 50, 600, 1100);
  g_assert (meta_rectangle_could_fit_in_region (region, &rect));
  g_assert (!meta_rectangle_contained_in_region (region, &rect));

  meta_rectangle_free_list_and_elements (region);
}

static void
test_clamping_to_region (void)
{
  GList* region;
  MetaRectangle rect;
  MetaRectangle min_size;
  FixedDirections fixed_directions;
  int i;

  min_size.height = min_size.width = 1;
  fixed_directions = 0;

  region = get_screen_region (3);
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      MetaRectangle temp;
      get_random_rect (&rect);
      temp = rect;
      meta_rectangle_clamp_to_fit_into_region (region,
                                               fixed_directions,
                                               &rect,
                                               &min_size);
      g_assert (meta_rectangle_could_fit_in_region (region, &rect) == TRUE);
      g_assert (rect.x == temp.x && rect.y == temp.y);
    }
  meta_rectangle_free_list_and_elements (region);

  /* Do some manual tests too */
  region = get_screen_region (1);

  rect = meta_rect (50, 50, 10000, 10000);
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_assert (rect.width == 1600 && rect.height == 1140);

  rect = meta_rect (275, -50, 410, 10000);
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_assert (rect.width == 400 && rect.height == 1180);

  rect = meta_rect (50, 50, 10000, 10000);
  min_size.height = 1170;
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_assert (rect.width == 400 && rect.height == 1180);

  rect = meta_rect (50, 50, 10000, 10000);
  min_size.width = 600;  min_size.height = 1170;

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                         "No rect whose size to clamp to found*");
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_test_assert_expected_messages ();

  g_assert (rect.width == 600 && rect.height == 1170);

  rect = meta_rect (350, 50, 100, 1100);
  min_size.width = 1;  min_size.height = 1;
  fixed_directions = FIXED_DIRECTION_X;
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_assert (rect.width == 100 && rect.height == 1100);

  rect = meta_rect (300, 70, 500, 1100);
  min_size.width = 1;  min_size.height = 1;
  fixed_directions = FIXED_DIRECTION_Y;
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_assert (rect.width == 400 && rect.height == 1100);

  rect = meta_rect (300, 70, 999999, 999999);
  min_size.width = 100;  min_size.height = 200;
  fixed_directions = FIXED_DIRECTION_Y;

  g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
                         "No rect whose size to clamp to found*");
  meta_rectangle_clamp_to_fit_into_region (region,
                                           fixed_directions,
                                           &rect,
                                           &min_size);
  g_test_assert_expected_messages ();

  g_assert (rect.width == 100 && rect.height == 999999);

  meta_rectangle_free_list_and_elements (region);
}

static gboolean
rect_overlaps_region (const GList         *spanning_rects,
                      const MetaRectangle *rect)
{
  /* FIXME: Should I move this to boxes.[ch]? */
  const GList *temp;
  gboolean     overlaps;

  temp = spanning_rects;
  overlaps = FALSE;
  while (!overlaps && temp != NULL)
    {
      overlaps = overlaps || meta_rectangle_overlap (temp->data, rect);
      temp = temp->next;
    }

  return overlaps;
}

gboolean time_to_print = FALSE;

static void
test_clipping_to_region (void)
{
  GList* region;
  MetaRectangle rect, temp;
  FixedDirections fixed_directions = 0;
  int i;

  region = get_screen_region (3);
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&rect);
      if (rect_overlaps_region (region, &rect))
        {
          meta_rectangle_clip_to_region (region, 0, &rect);
          g_assert (meta_rectangle_contained_in_region (region, &rect) == TRUE);
        }
    }
  meta_rectangle_free_list_and_elements (region);

  /* Do some manual tests too */
  region = get_screen_region (2);

  rect = meta_rect (-50, -10, 10000, 10000);
  meta_rectangle_clip_to_region (region,
                                 fixed_directions,
                                 &rect);
  g_assert (meta_rectangle_equal (region->data, &rect));

  rect = meta_rect (300, 1000, 400, 200);
  temp = meta_rect (300, 1000, 400, 150);
  meta_rectangle_clip_to_region (region,
                                 fixed_directions,
                                 &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (400, 1000, 300, 200);
  temp = meta_rect (450, 1000, 250, 200);
  meta_rectangle_clip_to_region (region,
                                 fixed_directions,
                                 &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (400, 1000, 300, 200);
  temp = meta_rect (400, 1000, 300, 150);
  meta_rectangle_clip_to_region (region,
                                 FIXED_DIRECTION_X,
                                 &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (400, 1000, 300, 200);
  temp = meta_rect (400, 1000, 300, 150);
  meta_rectangle_clip_to_region (region,
                                 FIXED_DIRECTION_X,
                                 &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  meta_rectangle_free_list_and_elements (region);
}

static void
test_shoving_into_region (void)
{
  GList* region;
  MetaRectangle rect, temp;
  FixedDirections fixed_directions = 0;
  int i;

  region = get_screen_region (3);
  for (i = 0; i < NUM_RANDOM_RUNS; i++)
    {
      get_random_rect (&rect);
      if (meta_rectangle_could_fit_in_region (region, &rect))
        {
          meta_rectangle_shove_into_region (region, 0, &rect);
          g_assert (meta_rectangle_contained_in_region (region, &rect));
        }
    }
  meta_rectangle_free_list_and_elements (region);

  /* Do some manual tests too */
  region = get_screen_region (2);

  rect = meta_rect (300, 1000, 400, 200);
  temp = meta_rect (300,  950, 400, 200);
  meta_rectangle_shove_into_region (region,
                                    fixed_directions,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (425, 1000, 300, 200);
  temp = meta_rect (450, 1000, 300, 200);
  meta_rectangle_shove_into_region (region,
                                    fixed_directions,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (425, 1000, 300, 200);
  temp = meta_rect (425,  950, 300, 200);
  meta_rectangle_shove_into_region (region,
                                    FIXED_DIRECTION_X,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 300, 1000, 400, 200);
  temp = meta_rect (1200, 1000, 400, 200);
  meta_rectangle_shove_into_region (region,
                                    FIXED_DIRECTION_Y,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 800, 1150, 400,  50);  /* Completely "offscreen" :) */
  temp = meta_rect ( 800, 1050, 400,  50);
  meta_rectangle_shove_into_region (region,
                                    0,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (-1000,  0, 400, 150);  /* Offscreen in 2 directions */
  temp = meta_rect (    0, 20, 400, 150);
  meta_rectangle_shove_into_region (region,
                                    0,
                                    &rect);
  g_assert (meta_rectangle_equal (&rect, &temp));

  meta_rectangle_free_list_and_elements (region);
}

static void
verify_edge_lists_are_equal (GList *code, GList *answer)
{
  int which = 0;

  while (code && answer)
    {
      MetaEdge *a = code->data;
      MetaEdge *b = answer->data;

      if (!meta_rectangle_equal (&a->rect, &b->rect) ||
          a->side_type != b->side_type ||
          a->edge_type != b->edge_type)
        {
          g_error ("%dth item in code answer answer lists do not match; "
                   "code rect: %d,%d + %d,%d; answer rect: %d,%d + %d,%d\n",
                   which,
                   a->rect.x, a->rect.y, a->rect.width, a->rect.height,
                   b->rect.x, b->rect.y, b->rect.width, b->rect.height);
        }

      code = code->next;
      answer = answer->next;

      which++;
    }

  /* Ought to be at the end of both lists; check if we aren't */
  if (code)
    {
      MetaEdge *tmp = code->data;
      g_error ("code list longer than answer list by %d items; "
               "first extra item rect: %d,%d +%d,%d\n",
               g_list_length (code),
               tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height);
    }

  if (answer)
    {
      MetaEdge *tmp = answer->data;
      g_error ("answer list longer than code list by %d items; "
               "first extra item rect: %d,%d +%d,%d\n",
               g_list_length (answer),
               tmp->rect.x, tmp->rect.y, tmp->rect.width, tmp->rect.height);
    }
}

static void
test_find_onscreen_edges (void)
{
  GList* edges;
  GList* tmp;

  int left   = META_DIRECTION_LEFT;
  int right  = META_DIRECTION_RIGHT;
  int top    = META_DIRECTION_TOP;
  int bottom = META_DIRECTION_BOTTOM;

  /*************************************************/
  /* Make sure test region 0 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (0);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge (   0, 1200, 1600, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,    0, 1600, 0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,    0, 0, 1200, right));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,    0, 0, 1200, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 1 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (1);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge (   0, 1200,  400, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 1200, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 1600, 0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,   20, 0, 1140, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 400, 1160, 0,   40, right));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 0, 1180, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 2 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (2);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200,  400, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1200,  350, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0, 1200,  300, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150,  150, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100,  400, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 1600, 0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,   20, 0, 1180, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0,  100, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0,   50, right));
  tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0,  100, left));
  tmp = g_list_prepend (tmp, new_screen_edge ( 450, 1150, 0,   50, left));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 0, 1180, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 3 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (3);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge (1200, 1200,  400, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1200,  420, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0, 1200,  300, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150,   80, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100,  400, 0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 700,  525, 200,  0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 700,  675, 200,  0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 1600, 0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,   20, 0, 1180, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1100, 0,  100, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 700,  525, 0,  150, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 300, 1150, 0,   50, right));
  tmp = g_list_prepend (tmp, new_screen_edge (1200, 1100, 0,  100, left));
  tmp = g_list_prepend (tmp, new_screen_edge ( 900,  525, 0,  150, left));
  tmp = g_list_prepend (tmp, new_screen_edge ( 380, 1150, 0,   50, left));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   20, 0, 1180, left));

#if 0
  #define FUDGE 50 /* number of edges */
  char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE];
  meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1);
  meta_rectangle_edge_list_to_string (tmp,   "\n ", big_buffer2);
  printf("Generated edge list:\n %s\nComparison edges list:\n %s\n",
         big_buffer1, big_buffer2);
#endif

  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 4 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (4);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge ( 800, 1200, 800,  0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800,   20, 800,  0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,   20, 0, 1180, right));
  tmp = g_list_prepend (tmp, new_screen_edge ( 800,   20, 0, 1180, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 5 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (5);
  tmp = NULL;
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************/
  /* Make sure test region 6 has the correct edges */
  /*************************************************/
  edges = get_screen_edges (6);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_screen_edge (   0, 1200, 1600,  0, bottom));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   40, 1600,  0, top));
  tmp = g_list_prepend (tmp, new_screen_edge (1600,   40, 0,  1160, right));
  tmp = g_list_prepend (tmp, new_screen_edge (   0,   40, 0,  1160, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);
}

static void
test_find_nonintersected_monitor_edges (void)
{
  GList* edges;
  GList* tmp;

  int left   = META_DIRECTION_LEFT;
  int right  = META_DIRECTION_RIGHT;
  int top    = META_DIRECTION_TOP;
  int bottom = META_DIRECTION_BOTTOM;

  /*************************************************************************/
  /* Make sure test monitor set 0 for with region 0 has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (0, 0);
  tmp = NULL;
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************************************/
  /* Make sure test monitor set 2 for with region 1 has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (2, 1);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_monitor_edge (   0,  600, 1600, 0, bottom));
  tmp = g_list_prepend (tmp, new_monitor_edge (   0,  600, 1600, 0, top));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************************************/
  /* Make sure test monitor set 1 for with region 2 has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (1, 2);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,   20, 0, 1080, right));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,   20, 0, 1180, left));
#if 0
  #define FUDGE 50
  char big_buffer1[(EDGE_LENGTH+2)*FUDGE], big_buffer2[(EDGE_LENGTH+2)*FUDGE];
  meta_rectangle_edge_list_to_string (edges, "\n ", big_buffer1);
  meta_rectangle_edge_list_to_string (tmp,   "\n ", big_buffer2);
  printf("Generated edge list:\n %s\nComparison edges list:\n %s\n",
         big_buffer1, big_buffer2);
#endif
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************************************/
  /* Make sure test monitor set 3 for with region 3 has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (3, 3);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_monitor_edge ( 900,  600,  700, 0, bottom));
  tmp = g_list_prepend (tmp, new_monitor_edge (   0,  600,  700, 0, bottom));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 900,  600,  700, 0, top));
  tmp = g_list_prepend (tmp, new_monitor_edge (   0,  600,  700, 0, top));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,  675, 0,  425, right));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,  675, 0,  525, left));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************************************/
  /* Make sure test monitor set 3 for with region 4 has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (3, 4);
  tmp = NULL;
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,  600,  800, 0, bottom));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,  600,  800, 0, top));
  tmp = g_list_prepend (tmp, new_monitor_edge ( 800,  600,  0, 600, right));
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);

  /*************************************************************************/
  /* Make sure test monitor set 3 for with region 5has the correct edges */
  /*************************************************************************/
  edges = get_monitor_edges (3, 5);
  tmp = NULL;
  verify_edge_lists_are_equal (edges, tmp);
  meta_rectangle_free_list_and_elements (tmp);
  meta_rectangle_free_list_and_elements (edges);
}

static void
test_gravity_resize (void)
{
  MetaRectangle oldrect, rect, temp;

  rect.x = -500;  /* Some random amount not equal to oldrect.x to ensure that
                   * the resize is done with respect to oldrect instead of rect
                   */
  oldrect = meta_rect ( 50,  300, 250, 400);
  temp    = meta_rect ( 50,  300,  20,   5);
  meta_rectangle_resize_with_gravity (&oldrect,
                                      &rect,
                                      META_GRAVITY_NORTH_WEST,
                                      20,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 50,  300, 250, 400);
  temp = meta_rect (165,  300,  20,   5);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_NORTH,
                                      20,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 50,  300, 250, 400);
  temp = meta_rect (280,  300,  20,   5);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_NORTH_EAST,
                                      20,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 50,  300, 250, 400);
  temp = meta_rect ( 50,  695,  50,   5);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_SOUTH_WEST,
                                      50,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 50,  300, 250, 400);
  temp = meta_rect (150,  695,  50,   5);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_SOUTH,
                                      50,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 50,  300, 250, 400);
  temp = meta_rect (250,  695,  50,   5);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_SOUTH_EAST,
                                      50,
                                      5);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (167,  738, 237, 843);
  temp = meta_rect (167, 1113, 832,  93);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_WEST,
                                      832,
                                      93);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect ( 167,  738, 237, 843);
  temp = meta_rect (-131, 1113, 833,  93);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_CENTER,
                                      832,
                                      93);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (300, 1000, 400, 200);
  temp = meta_rect (270,  994, 430, 212);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_EAST,
                                      430,
                                      211);
  g_assert (meta_rectangle_equal (&rect, &temp));

  rect = meta_rect (300, 1000, 400, 200);
  temp = meta_rect (300, 1000, 430, 211);
  meta_rectangle_resize_with_gravity (&rect,
                                      &rect,
                                      META_GRAVITY_STATIC,
                                      430,
                                      211);
  g_assert (meta_rectangle_equal (&rect, &temp));
}

#define EPSILON 0.000000001
static void
test_find_closest_point_to_line (void)
{
  double x1, y1, x2, y2, px, py, rx, ry;
  double answer_x, answer_y;

  x1 =  3.0;  y1 =  49.0;
  x2 =  2.0;  y2 = - 1.0;
  px = -2.6;  py =  19.1;
  answer_x = 2.4; answer_y = 19;
  meta_rectangle_find_linepoint_closest_to_point (x1,  y1,
                                                  x2,  y2,
                                                  px,  py,
                                                  &rx, &ry);
  g_assert (fabs (rx - answer_x) < EPSILON && fabs (ry - answer_y) < EPSILON);

  /* Special test for x1 == x2, so that slop of line is infinite */
  x1 =  3.0;  y1 =  49.0;
  x2 =  3.0;  y2 = - 1.0;
  px = -2.6;  py =  19.1;
  answer_x = 3.0; answer_y = 19.1;
  meta_rectangle_find_linepoint_closest_to_point (x1,  y1,
                                                  x2,  y2,
                                                  px,  py,
                                                  &rx, &ry);
  g_assert (fabs (rx - answer_x) < EPSILON && fabs (ry - answer_y) < EPSILON);

  /* Special test for y1 == y2, so perp line has slope of infinity */
  x1 =  3.14;  y1 =   7.0;
  x2 =  2.718; y2 =   7.0;
  px = -2.6;   py =  19.1;
  answer_x = -2.6; answer_y = 7;
  meta_rectangle_find_linepoint_closest_to_point (x1,  y1,
                                                  x2,  y2,
                                                  px,  py,
                                                  &rx, &ry);
  g_assert (fabs (rx - answer_x) < EPSILON && fabs (ry - answer_y) < EPSILON);

  /* Test when we the point we want to be closest to is actually on the line */
  x1 =  3.0;  y1 =  49.0;
  x2 =  2.0;  y2 = - 1.0;
  px =  2.4;  py =  19.0;
  answer_x = 2.4; answer_y = 19;
  meta_rectangle_find_linepoint_closest_to_point (x1,  y1,
                                                  x2,  y2,
                                                  px,  py,
                                                  &rx, &ry);
  g_assert (fabs (rx - answer_x) < EPSILON && fabs (ry - answer_y) < EPSILON);
}

void
init_boxes_tests (void)
{
  init_random_ness ();

  g_test_add_func ("/util/boxes/area", test_area);
  g_test_add_func ("/util/boxes/intersect", test_intersect);
  g_test_add_func ("/util/boxes/equal", test_equal);
  g_test_add_func ("/util/boxes/overlap", test_overlap_funcs);
  g_test_add_func ("/util/boxes/basic-fitting", test_basic_fitting);

  g_test_add_func ("/util/boxes/regions-ok", test_regions_okay);
  g_test_add_func ("/util/boxes/regions-fitting", test_region_fitting);

  g_test_add_func ("/util/boxes/clamp-to-region", test_clamping_to_region);
  g_test_add_func ("/util/boxes/clip-to-region", test_clipping_to_region);
  g_test_add_func ("/util/boxes/shove-into-region", test_shoving_into_region);

  /* And now the functions dealing with edges more than boxes */
  g_test_add_func ("/util/boxes/onscreen-edges", test_find_onscreen_edges);
  g_test_add_func ("/util/boxes/nonintersected-monitor-edges",
                   test_find_nonintersected_monitor_edges);

  /* And now the misfit functions that don't quite fit in anywhere else... */
  g_test_add_func ("/util/boxes/gravity-resize", test_gravity_resize);
  g_test_add_func ("/util/boxes/closest-point-to-line",
                   test_find_closest_point_to_line);
}
