/*
 *  vaapipostproc.c - GStreamer unit test for the vaapipostproc element
 *
 *  Copyright (C) 2019 Intel Corporation
 *    Author: U. Artie Eoff <ullysses.a.eoff@intel.com>
 *
 *  This library 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 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <gst/check/gstcheck.h>
#include <gst/video/video.h>
#include <gst/video/navigation.h>

typedef struct
{
  GstElement *pipeline;
  GstElement *source;
  GstElement *filter;
  GstElement *vpp;
  GstElement *sink;
} VppTestContext;

typedef struct
{
  gdouble x;
  gdouble y;
} VppTestCoordinate;

typedef struct
{
  VppTestCoordinate send;
  VppTestCoordinate expect;
} VppTestCoordinateParams;

GST_START_TEST (test_make)
{
  GstElement *vaapipostproc;

  vaapipostproc = gst_element_factory_make ("vaapipostproc", "vaapipostproc");
  fail_unless (vaapipostproc != NULL, "Failed to create vaapipostproc element");

  gst_object_unref (vaapipostproc);
}

GST_END_TEST;

static void
vpp_test_init_context (VppTestContext * ctx)
{
  GST_INFO ("initing context");

  ctx->pipeline = gst_pipeline_new ("pipeline");
  fail_unless (ctx->pipeline != NULL);

  ctx->source = gst_element_factory_make ("videotestsrc", "src");
  fail_unless (ctx->source != NULL, "Failed to create videotestsrc element");

  ctx->filter = gst_element_factory_make ("capsfilter", "filter");
  fail_unless (ctx->filter != NULL, "Failed to create caps filter element");

  ctx->vpp = gst_element_factory_make ("vaapipostproc", "vpp");
  fail_unless (ctx->vpp != NULL, "Failed to create vaapipostproc element");

  ctx->sink = gst_element_factory_make ("fakesink", "sink");
  fail_unless (ctx->sink != NULL, "Failed to create fakesink element");

  gst_bin_add_many (GST_BIN (ctx->pipeline), ctx->source, ctx->filter, ctx->vpp,
      ctx->sink, NULL);
  gst_element_link_many (ctx->source, ctx->filter, ctx->vpp, ctx->sink, NULL);
}

static void
vpp_test_deinit_context (VppTestContext * ctx)
{
  GST_INFO ("deiniting context");

  gst_element_set_state (ctx->pipeline, GST_STATE_NULL);
  gst_object_unref (ctx->pipeline);
  memset (ctx, 0x00, sizeof (VppTestContext));
}

static void
vpp_test_set_crop (VppTestContext * ctx, gint l, gint r, gint t, gint b)
{
  GST_LOG ("%d %d %d %0d", l, r, t, b);
  g_object_set (ctx->vpp, "crop-left", l, "crop-right", r,
      "crop-top", t, "crop-bottom", b, NULL);
}

static void
vpp_test_set_orientation (VppTestContext * ctx, GstVideoOrientationMethod m)
{
  GST_LOG ("%u", m);
  g_object_set (ctx->vpp, "video-direction", m, NULL);
}

static void
vpp_test_set_dimensions (VppTestContext * ctx, gint w, gint h)
{
  GstCaps *caps = gst_caps_new_simple ("video/x-raw",
      "width", G_TYPE_INT, w, "height", G_TYPE_INT, h, NULL);
  GST_LOG ("%dx%d", w, h);
  g_object_set (ctx->filter, "caps", caps, NULL);
  gst_caps_unref (caps);
}

static GstPadProbeReturn
cb_mouse_event (GstPad * pad, GstPadProbeInfo * info, gpointer data)
{
  VppTestCoordinate *coord = data;
  gdouble x = 0, y = 0;
  GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);

  if (GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION) {
    switch (gst_navigation_event_get_type (event)) {
      case GST_NAVIGATION_EVENT_MOUSE_MOVE:
        if (gst_navigation_event_parse_mouse_move_event (event, &x, &y)) {
          coord->x = x;
          coord->y = y;
        }
        break;
      case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
      case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE:
        if (gst_navigation_event_parse_mouse_button_event (event, NULL, &x, &y)) {
          coord->x = x;
          coord->y = y;
        }
        break;
      default:
        break;
    }
  }

  return GST_PAD_PROBE_OK;
}

static void
vpp_test_mouse_events (VppTestContext * ctx,
    const VppTestCoordinateParams * const params, const size_t nparams)
{
  GstEvent *event = NULL;
  VppTestCoordinate probed = { 0, };
  guint i, j;

  /* probe mouse events propagated up from vaapipostproc */
  GstPad *pad = gst_element_get_static_pad (ctx->source, "src");
  gulong id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
      (GstPadProbeCallback) cb_mouse_event, &probed, NULL);

  const char *mouse_events[] = {
    "mouse-move",
    "mouse-button-press",
    "mouse-button-release",
  };

  fail_unless (gst_element_set_state (ctx->pipeline, GST_STATE_PAUSED)
      != GST_STATE_CHANGE_FAILURE);
  fail_unless (gst_element_get_state (ctx->pipeline, NULL, NULL, -1)
      == GST_STATE_CHANGE_SUCCESS);

  for (i = 0; i < nparams; ++i) {
    for (j = 0; j < G_N_ELEMENTS (mouse_events); ++j) {
      probed.x = probed.y = -1;

      switch (j) {
        case 0:
          event = gst_navigation_event_new_mouse_move (params[i].send.x,
              params[i].send.y, GST_NAVIGATION_MODIFIER_NONE);
          break;
        case 1:
          event = gst_navigation_event_new_mouse_button_press (0,
              params[i].send.x, params[i].send.y, GST_NAVIGATION_MODIFIER_NONE);
          break;
        case 2:
          event = gst_navigation_event_new_mouse_button_release (0,
              params[i].send.x, params[i].send.y, GST_NAVIGATION_MODIFIER_NONE);
          break;
      }

      GST_LOG ("sending %s event %fx%f", mouse_events[j], params[i].send.x,
          params[i].send.y);
      gst_element_send_event (ctx->pipeline, event);

      GST_LOG ("probed %s event %fx%f", mouse_events[j], probed.x, probed.y);
      GST_LOG ("expect %s event %fx%f", mouse_events[j], params[i].expect.x,
          params[i].expect.y);

      fail_unless (params[i].expect.x == probed.x);
      fail_unless (params[i].expect.y == probed.y);
    }
  }

  gst_element_set_state (ctx->pipeline, GST_STATE_NULL);

  gst_pad_remove_probe (pad, id);
  gst_object_unref (pad);
}

static void
vpp_test_crop_mouse_events (VppTestContext * ctx, gint w, gint h, gint l,
    gint r, gint t, gint b)
{
  const gdouble xmin = 0.0;
  const gdouble ymin = 0.0;
  const gdouble xmax = w - (l + r) - 1;
  const gdouble ymax = h - (t + b) - 1;
  const gdouble xctr = xmax / 2;
  const gdouble yctr = ymax / 2;
  const gdouble xrand = g_random_double_range (xmin, xmax);
  const gdouble yrand = g_random_double_range (ymin, ymax);

  const gdouble e_xmin = xmin + l;
  const gdouble e_ymin = ymin + t;
  const gdouble e_xmax = xmax + l;
  const gdouble e_ymax = ymax + t;
  const gdouble e_xctr = xctr + l;
  const gdouble e_yctr = yctr + t;
  const gdouble e_xrand = xrand + l;
  const gdouble e_yrand = yrand + t;

  const VppTestCoordinateParams params[] = {
    {{xmin, ymin}, {e_xmin, e_ymin}},   /* left-top */
    {{xmin, yctr}, {e_xmin, e_yctr}},   /* left-center */
    {{xmin, ymax}, {e_xmin, e_ymax}},   /* left-bottom */

    {{xmax, ymin}, {e_xmax, e_ymin}},   /* right-top */
    {{xmax, yctr}, {e_xmax, e_yctr}},   /* right-center */
    {{xmax, ymax}, {e_xmax, e_ymax}},   /* right-bottom */

    {{xctr, ymin}, {e_xctr, e_ymin}},   /* center-top */
    {{xctr, yctr}, {e_xctr, e_yctr}},   /* center */
    {{xctr, ymax}, {e_xctr, e_ymax}},   /* center-bottom */

    {{xrand, yrand}, {e_xrand, e_yrand}},       /* random */
  };

  vpp_test_set_dimensions (ctx, w, h);
  vpp_test_set_crop (ctx, l, r, t, b);
  vpp_test_mouse_events (ctx, params, G_N_ELEMENTS (params));
}

GST_START_TEST (test_crop_mouse_events)
{
  VppTestContext ctx;

  vpp_test_init_context (&ctx);

  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 1, 0, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 1, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 1, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 0, 1);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 63, 0, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 63, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 63, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 0, 63);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 63, 0, 0, 1);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 63, 1, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 1, 63, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 1, 0, 0, 63);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 0, 0, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 32, 0, 0, 128);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 32, 128, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 0, 128, 32, 0);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 128, 0, 0, 32);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 1, 1, 1, 1);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 63, 63, 63, 63);
  vpp_test_crop_mouse_events (&ctx, 160, 160, 64, 64, 64, 64);

  vpp_test_deinit_context (&ctx);
}

GST_END_TEST;

static void
vpp_test_orientation_mouse_events (VppTestContext * ctx, gint w, gint h)
{
  size_t i;
  const gdouble xmin = 0.0;
  const gdouble ymin = 0.0;
  const gdouble xmax = w - 1;
  const gdouble ymax = h - 1;
  const VppTestCoordinateParams params[8][4] = {
    /* (0) identity */
    {
          {{xmin, ymin}, {xmin, ymin}},
          {{xmax, ymin}, {xmax, ymin}},
          {{xmin, ymax}, {xmin, ymax}},
          {{xmax, ymax}, {xmax, ymax}},
        },
    /* (1) 90 Rotation */
    {
          {{ymin, xmin}, {xmin, ymax}},
          {{ymax, xmin}, {xmin, ymin}},
          {{ymin, xmax}, {xmax, ymax}},
          {{ymax, xmax}, {xmax, ymin}},
        },
    /* (2) 180 Rotation */
    {
          {{xmin, ymin}, {xmax, ymax}},
          {{xmax, ymin}, {xmin, ymax}},
          {{xmin, ymax}, {xmax, ymin}},
          {{xmax, ymax}, {xmin, ymin}},
        },
    /* (3) 270 Rotation */
    {
          {{ymin, xmin}, {xmax, ymin}},
          {{ymax, xmin}, {xmax, ymax}},
          {{ymin, xmax}, {xmin, ymin}},
          {{ymax, xmax}, {xmin, ymax}},
        },
    /* (4) Horizontal Flip */
    {
          {{xmin, ymin}, {xmax, ymin}},
          {{xmax, ymin}, {xmin, ymin}},
          {{xmin, ymax}, {xmax, ymax}},
          {{xmax, ymax}, {xmin, ymax}},
        },
    /* (5) Vertical Flip */
    {
          {{xmin, ymin}, {xmin, ymax}},
          {{xmax, ymin}, {xmax, ymax}},
          {{xmin, ymax}, {xmin, ymin}},
          {{xmax, ymax}, {xmax, ymin}},
        },
    /* (6) Vertical Flip + 90 Rotation */
    {
          {{ymin, xmin}, {xmin, ymin}},
          {{ymax, xmin}, {xmin, ymax}},
          {{ymin, xmax}, {xmax, ymin}},
          {{ymax, xmax}, {xmax, ymax}},
        },
    /* (7) Horizontal Flip + 90 Rotation */
    {
          {{ymin, xmin}, {xmax, ymax}},
          {{ymax, xmin}, {xmax, ymin}},
          {{ymin, xmax}, {xmin, ymax}},
          {{ymax, xmax}, {xmin, ymin}},
        },
  };

  vpp_test_set_dimensions (ctx, w, h);

  for (i = 0; i < 8; ++i) {
    vpp_test_set_orientation (ctx, i);
    vpp_test_mouse_events (ctx, params[i], 4);
  }
}

GST_START_TEST (test_orientation_mouse_events)
{
  VppTestContext ctx;

  vpp_test_init_context (&ctx);

  vpp_test_orientation_mouse_events (&ctx, 160, 320);
  vpp_test_orientation_mouse_events (&ctx, 161, 320);
  vpp_test_orientation_mouse_events (&ctx, 160, 321);
  vpp_test_orientation_mouse_events (&ctx, 161, 321);

  vpp_test_orientation_mouse_events (&ctx, 320, 160);
  vpp_test_orientation_mouse_events (&ctx, 320, 161);
  vpp_test_orientation_mouse_events (&ctx, 321, 160);
  vpp_test_orientation_mouse_events (&ctx, 321, 161);

  vpp_test_deinit_context (&ctx);
}

GST_END_TEST;

static Suite *
vaapipostproc_suite (void)
{
  Suite *s = suite_create ("vaapipostproc");
  TCase *tc_chain = tcase_create ("general");

  suite_add_tcase (s, tc_chain);
  tcase_add_test (tc_chain, test_make);
  tcase_add_test (tc_chain, test_crop_mouse_events);
  tcase_add_test (tc_chain, test_orientation_mouse_events);

  return s;
}

GST_CHECK_MAIN (vaapipostproc);