/*
 * GStreamer MotionCells detect areas of motion
 * Copyright (C) 2011 Robert Jobbagy <jobbagy.robert@gmail.com>
 * Copyright (C) 2011 -2018 Nicola Murino <nicola.murino@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Alternatively, the contents of this file may be used under the
 * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
 * which case the following provisions apply instead of the ones
 * mentioned above:
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:element-motioncells
 *
 * Performs motion detection on videos.
 *
 * ## Example launch line
 *
 * |[
 * gst-launch-1.0 videotestsrc pattern=18 ! videorate ! videoscale ! video/x-raw,width=320,height=240,framerate=5/1 ! videoconvert ! motioncells ! videoconvert ! xvimagesink
 * ]|
 */

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

#include "gstmotioncells.h"

GST_DEBUG_CATEGORY_STATIC (gst_motion_cells_debug);
#define GST_CAT_DEFAULT gst_motion_cells_debug

#define GRID_DEF 10
#define GRID_MIN 8
#define GRID_MAX 32
#define SENSITIVITY_DEFAULT 0.5
#define SENSITIVITY_MIN 0
#define SENSITIVITY_MAX 1
#define THRESHOLD_MIN 0
#define THRESHOLD_DEFAULT 0.01
#define THRESHOLD_MAX 1.0
#define GAP_MIN 1
#define GAP_DEF 5
#define GAP_MAX 60
#define POST_NO_MOTION_MIN 0
#define POST_NO_MOTION_DEF 0
#define POST_NO_MOTION_MAX 180
#define MINIMUM_MOTION_FRAMES_MIN 1
#define MINIMUM_MOTION_FRAMES_DEF 1
#define MINIMUM_MOTION_FRAMES_MAX 60
#define THICKNESS_MIN -1
#define THICKNESS_DEF 1
#define THICKNESS_MAX 5
#define DATE_MIN 0
#define DATE_DEF 1
#define DATE_MAX LONG_MAX
#define DEF_DATAFILEEXT "vamc"
#define MSGLEN 6
#define BUSMSGLEN 20

#define GFREE(POINTER)\
		{\
			g_free(POINTER);\
			POINTER = NULL;\
		}

/* Filter signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_GRID_X,
  PROP_GRID_Y,
  PROP_SENSITIVITY,
  PROP_THRESHOLD,
  PROP_DISPLAY,
  PROP_DATE,
  PROP_DATAFILE,
  PROP_DATAFILE_EXT,
  PROP_MOTIONMASKCOORD,
  PROP_MOTIONMASKCELLSPOS,
  PROP_CELLSCOLOR,
  PROP_MOTIONCELLSIDX,
  PROP_GAP,
  PROP_POSTNOMOTION,
  PROP_MINIMUNMOTIONFRAMES,
  PROP_CALCULATEMOTION,
  PROP_POSTALLMOTION,
  PROP_USEALPHA,
  PROP_MOTIONCELLTHICKNESS
};

/* the capabilities of the inputs and outputs.
 */
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB")));

static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB")));

G_DEFINE_TYPE (GstMotioncells, gst_motion_cells, GST_TYPE_OPENCV_VIDEO_FILTER);

static void gst_motion_cells_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_motion_cells_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

static gboolean gst_motion_cells_handle_sink_event (GstPad * pad,
    GstObject * parent, GstEvent * event);
static GstFlowReturn gst_motion_cells_transform_ip (GstOpencvVideoFilter *
    filter, GstBuffer * buf, cv::Mat img);

static void gst_motioncells_update_motion_cells (GstMotioncells * filter);
static void gst_motioncells_update_motion_masks (GstMotioncells * filter);

/* Clean up */
static void
gst_motion_cells_finalize (GObject * obj)
{
  GstMotioncells *filter = gst_motion_cells (obj);

  motion_cells_free (filter->id);

  //freeing previously allocated dynamic array
  if (filter->motionmaskcoord_count > 0) {
    GFREE (filter->motionmaskcoords);
  }

  if (filter->motionmaskcells_count > 0) {
    GFREE (filter->motionmaskcellsidx);
  }
  if (filter->motioncells_count > 0) {
    GFREE (filter->motioncellsidx);
  }

  GFREE (filter->motioncellscolor);
  GFREE (filter->prev_datafile);
  GFREE (filter->cur_datafile);
  GFREE (filter->basename_datafile);
  GFREE (filter->datafile_extension);

  G_OBJECT_CLASS (gst_motion_cells_parent_class)->finalize (obj);
}

/* initialize the motioncells's class */
static void
gst_motion_cells_class_init (GstMotioncellsClass * klass)
{
  GObjectClass *gobject_class;
  GstOpencvVideoFilterClass *gstopencvbasefilter_class;

  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  gobject_class = (GObjectClass *) klass;
  gstopencvbasefilter_class = (GstOpencvVideoFilterClass *) klass;

  gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_motion_cells_finalize);
  gobject_class->set_property = gst_motion_cells_set_property;
  gobject_class->get_property = gst_motion_cells_get_property;

  gstopencvbasefilter_class->cv_trans_ip_func = gst_motion_cells_transform_ip;

  g_object_class_install_property (gobject_class, PROP_GRID_X,
      g_param_spec_int ("gridx", "Number of Horizontal Grids",
          "Number of horizontal grid cells.", GRID_MIN, GRID_MAX,
          GRID_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_GRID_Y,
      g_param_spec_int ("gridy", "Number of Vertical Grids",
          "Number of vertical grid cells.", GRID_MIN, GRID_MAX,
          GRID_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_SENSITIVITY,
      g_param_spec_double ("sensitivity", "Motion Sensitivity",
          "Motion detection sensitivity.", SENSITIVITY_MIN,
          SENSITIVITY_MAX, SENSITIVITY_DEFAULT,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_THRESHOLD,
      g_param_spec_double ("threshold", "Lower bound of motion cells number",
          "Threshold value for motion. Filter detects motion when at least "
          "this fraction of the cells have moved",
          THRESHOLD_MIN, THRESHOLD_MAX, THRESHOLD_DEFAULT,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_GAP,
      g_param_spec_int ("gap", "Motion-finished Threshold",
          "Interval in seconds after which motion is considered finished "
          "and a motion finished bus message is posted.",
          GAP_MIN, GAP_MAX, GAP_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_POSTNOMOTION,
      g_param_spec_int ("postnomotion", "No-motion Threshold",
          "If non 0, post a no_motion event on the bus if no motion is "
          "detected for the given number of seconds",
          POST_NO_MOTION_MIN, POST_NO_MOTION_MAX, POST_NO_MOTION_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_MINIMUNMOTIONFRAMES,
      g_param_spec_int ("minimummotionframes", "Minimum Motion Frames",
          "Minimum number of motion frames triggering a motion event",
          MINIMUM_MOTION_FRAMES_MIN, MINIMUM_MOTION_FRAMES_MAX,
          MINIMUM_MOTION_FRAMES_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_DISPLAY,
      g_param_spec_boolean ("display", "Display",
          "Toggle display of motion cells on current frame", FALSE,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_POSTALLMOTION,
      g_param_spec_boolean ("postallmotion", "Post All Motion",
          "Post bus messages for every motion frame or just motion start and "
          "motion stop",
          FALSE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_USEALPHA,
      g_param_spec_boolean ("usealpha", "Use alpha",
          "Toggle usage of alpha blending on frames with motion cells", TRUE,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
#if 0
  /* FIXME: should not be a long property, make it either gint or gint64
   * (is this property actually used or useful for anything?) */
  g_object_class_install_property (gobject_class, PROP_DATE,
      g_param_spec_long ("date", "Motion Cell Date",
          "Current Date in milliseconds", DATE_MIN, DATE_MAX, DATE_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
#endif
  g_object_class_install_property (gobject_class, PROP_DATAFILE,
      g_param_spec_string ("datafile", "DataFile",
          "Location of motioncells data file (empty string means no saving)",
          NULL, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_DATAFILE_EXT,
      g_param_spec_string ("datafileextension", "DataFile Extension",
          "Extension of datafile", DEF_DATAFILEEXT,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_MOTIONMASKCOORD,
      g_param_spec_string ("motionmaskcoords", "Motion Mask with Coordinates",
          "Describe a region with its upper left and lower right x, y "
          "coordinates separated with \":\". Pass multiple regions as a "
          "comma-separated list", NULL,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_MOTIONMASKCELLSPOS,
      g_param_spec_string ("motionmaskcellspos",
          "Motion Mask with Cells Position",
          "Describe a cell with its line and column idx separated with \":\". "
          "Pass multiple cells as a comma-separated list", NULL,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_CELLSCOLOR,
      g_param_spec_string ("cellscolor", "Color of Motion Cells",
          "Color for motion cells in R,G,B format. Max per channel is 255",
          "255,255,0",
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_MOTIONCELLSIDX,
      g_param_spec_string ("motioncellsidx", "Motion Cells Of Interest(MOCI)",
          "Describe a cell with its line and column idx separated with \":\". "
          "Pass multiple cells as a comma-separated list", NULL,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_CALCULATEMOTION,
      g_param_spec_boolean ("calculatemotion", "Calculate Motion",
          "Toggles motion calculation. If FALSE, this filter does nothing",
          TRUE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
  g_object_class_install_property (gobject_class, PROP_MOTIONCELLTHICKNESS,
      g_param_spec_int ("motioncellthickness", "Motion Cell Thickness",
          "Motion Cell Border Thickness. Set to -1 to fill motion cell",
          THICKNESS_MIN, THICKNESS_MAX, THICKNESS_DEF,
          (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));

  gst_element_class_set_static_metadata (element_class,
      "motioncells",
      "Filter/Effect/Video",
      "Performs motion detection on videos and images, providing detected motion cells index via bus messages",
      "Robert Jobbagy <jobbagy dot robert at gmail dot com>, Nicola Murino <nicola dot murino at gmail.com>");

  gst_element_class_add_static_pad_template (element_class, &src_factory);
  gst_element_class_add_static_pad_template (element_class, &sink_factory);
}

/* initialize the new element
 * instantiate pads and add them to element
 * set pad callback functions
 * initialize instance structure
 */
static void
gst_motion_cells_init (GstMotioncells * filter)
{
  gst_pad_set_event_function (GST_BASE_TRANSFORM_SINK_PAD (filter),
      GST_DEBUG_FUNCPTR (gst_motion_cells_handle_sink_event));

  filter->display = TRUE;
  filter->calculate_motion = TRUE;

  filter->prevgridx = 0;
  filter->prevgridy = 0;
  filter->gridx = GRID_DEF;
  filter->gridy = GRID_DEF;
  filter->gap = GAP_DEF;
  filter->postnomotion = POST_NO_MOTION_DEF;
  filter->minimum_motion_frames = MINIMUM_MOTION_FRAMES_DEF;

  filter->prev_datafile = NULL;
  filter->cur_datafile = NULL;
  filter->basename_datafile = NULL;
  filter->has_delayed_mask = 0;
  filter->datafile_extension = g_strdup (DEF_DATAFILEEXT);
  filter->sensitivity = SENSITIVITY_DEFAULT;
  filter->threshold = THRESHOLD_DEFAULT;

  filter->motionmaskcoord_count = 0;
  filter->motionmaskcoords = NULL;
  filter->motionmaskcells_count = 0;
  filter->motionmaskcellsidx = NULL;
  filter->motioncellscolor = g_new0 (cellscolor, 1);
  filter->motioncellscolor->R_channel_value = 255;
  filter->motioncellscolor->G_channel_value = 255;
  filter->motioncellscolor->B_channel_value = 0;
  filter->motioncellsidx = NULL;
  filter->motioncells_count = 0;
  filter->motion_begin_timestamp = 0;
  filter->last_motion_timestamp = 0;
  filter->last_nomotion_notified = 0;
  filter->consecutive_motion = 0;
  filter->motion_timestamp = 0;
  filter->prev_buff_timestamp = 0;
  filter->cur_buff_timestamp = 0;
  filter->diff_timestamp = -1;
  filter->starttime = 1000 * g_get_real_time();
  filter->previous_motion = FALSE;
  filter->changed_datafile = FALSE;
  filter->postallmotion = FALSE;
  filter->usealpha = TRUE;
  filter->firstdatafile = FALSE;
  filter->firstgridx = TRUE;
  filter->firstgridy = TRUE;
  filter->changed_gridx = FALSE;
  filter->changed_gridy = FALSE;
  filter->firstframe = TRUE;
  filter->changed_startime = FALSE;
  filter->sent_init_error_msg = FALSE;
  filter->sent_save_error_msg = FALSE;
  filter->thickness = THICKNESS_DEF;

  filter->datafileidx = 0;
  filter->id = motion_cells_init ();

  gst_opencv_video_filter_set_in_place (GST_OPENCV_VIDEO_FILTER_CAST (filter),
      TRUE);
}

static void
fix_coords (motionmaskcoordrect & coords, int width, int height)
{
  --width;
  --height;

  if (width < coords.upper_left_x)
    coords.upper_left_x = width;
  if (width < coords.lower_right_x)
    coords.lower_right_x = width;

  if (height < coords.upper_left_y)
    coords.upper_left_y = height;
  if (height < coords.lower_right_y)
    coords.lower_right_y = height;
}

static void
gst_motion_cells_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstMotioncells *filter = gst_motion_cells (object);
  //variables for overlay regions setup
  gchar **strs, **colorstr, **motioncellsstr, **motionmaskcellsstr;
  int i, ux, uy, lx, ly;
  int r, g, b;
  int cellscolorscnt = 0;
  int linidx, colidx, masklinidx, maskcolidx;
  int tmpux = -1;
  int tmpuy = -1;
  int tmplx = -1;
  int tmply = -1;

  GST_OBJECT_LOCK (filter);
  switch (prop_id) {
    case PROP_GRID_X:
      filter->gridx = g_value_get_int (value);
      if (filter->prevgridx != filter->gridx && !filter->firstframe) {
        filter->changed_gridx = TRUE;
      }
      filter->prevgridx = filter->gridx;
      break;
    case PROP_GRID_Y:
      filter->gridy = g_value_get_int (value);
      if (filter->prevgridy != filter->gridy && !filter->firstframe) {
        filter->changed_gridy = TRUE;
      }
      filter->prevgridy = filter->gridy;
      break;
    case PROP_GAP:
      filter->gap = g_value_get_int (value);
      break;
    case PROP_POSTNOMOTION:
      filter->postnomotion = g_value_get_int (value);
      break;
    case PROP_MINIMUNMOTIONFRAMES:
      filter->minimum_motion_frames = g_value_get_int (value);
      break;
    case PROP_SENSITIVITY:
      filter->sensitivity = g_value_get_double (value);
      break;
    case PROP_THRESHOLD:
      filter->threshold = g_value_get_double (value);
      break;
    case PROP_DISPLAY:
      filter->display = g_value_get_boolean (value);
      break;
    case PROP_POSTALLMOTION:
      filter->postallmotion = g_value_get_boolean (value);
      break;
    case PROP_USEALPHA:
      filter->usealpha = g_value_get_boolean (value);
      break;
    case PROP_CALCULATEMOTION:
      filter->calculate_motion = g_value_get_boolean (value);
      break;
    case PROP_DATE:
      if (!filter->firstframe) {
        filter->changed_startime = TRUE;
      }
      filter->starttime = g_value_get_long (value);
      break;
    case PROP_DATAFILE:
      GFREE (filter->cur_datafile);
      GFREE (filter->basename_datafile);
      filter->basename_datafile = g_value_dup_string (value);

      if (strlen (filter->basename_datafile) == 0) {
        filter->cur_datafile = NULL;
        break;
      }
      filter->cur_datafile =
          g_strdup_printf ("%s-0.%s", filter->basename_datafile,
          filter->datafile_extension);
      if (g_strcmp0 (filter->prev_datafile, filter->basename_datafile) != 0) {
        filter->changed_datafile = TRUE;
        filter->sent_init_error_msg = FALSE;
        filter->sent_save_error_msg = FALSE;
        filter->datafileidx = 0;
        motion_cells_free_resources (filter->id);
      } else {
        filter->changed_datafile = FALSE;
      }

      GFREE (filter->prev_datafile);
      filter->prev_datafile = g_strdup (filter->basename_datafile);
      break;
    case PROP_DATAFILE_EXT:
      GFREE (filter->datafile_extension);
      filter->datafile_extension = g_value_dup_string (value);
      break;
    case PROP_MOTIONMASKCOORD:
      filter->has_delayed_mask = (0 < filter->width && 0 < filter->height);

      strs = g_strsplit (g_value_get_string (value), ",", 255);
      GFREE (filter->motionmaskcoords);
      //setting number of regions
      for (filter->motionmaskcoord_count = 0;
          strs[filter->motionmaskcoord_count] != NULL;
          ++filter->motionmaskcoord_count);
      if (filter->motionmaskcoord_count > 0) {
        sscanf (strs[0], "%d:%d:%d:%d", &tmpux, &tmpuy, &tmplx, &tmply);
        if (tmpux > -1 && tmpuy > -1 && tmplx > -1 && tmply > -1) {
          filter->motionmaskcoords =
              g_new0 (motionmaskcoordrect, filter->motionmaskcoord_count);

          for (i = 0; i < filter->motionmaskcoord_count; ++i) {
            sscanf (strs[i], "%d:%d:%d:%d", &ux, &uy, &lx, &ly);

            filter->motionmaskcoords[i].upper_left_x = ux < 0 ? 0 : ux;
            filter->motionmaskcoords[i].upper_left_y = uy < 0 ? 0 : uy;
            filter->motionmaskcoords[i].lower_right_x = lx < 0 ? 0 : lx;
            filter->motionmaskcoords[i].lower_right_y = ly < 0 ? 0 : ly;

            if (0 < filter->width && 0 < filter->height) {
              fix_coords (filter->motionmaskcoords[i], filter->width,
                  filter->height);
            }
          }
        } else {
          filter->motionmaskcoord_count = 0;
        }
      }
      g_strfreev (strs);
      tmpux = -1;
      tmpuy = -1;
      tmplx = -1;
      tmply = -1;
      break;
    case PROP_MOTIONMASKCELLSPOS:
      motionmaskcellsstr = g_strsplit (g_value_get_string (value), ",", 255);
      GFREE (filter->motionmaskcellsidx);
      //setting number of regions
      for (filter->motionmaskcells_count = 0;
          motionmaskcellsstr[filter->motionmaskcells_count] != NULL;
          ++filter->motionmaskcells_count);
      if (filter->motionmaskcells_count > 0) {
        sscanf (motionmaskcellsstr[0], "%d:%d", &tmpux, &tmpuy);
        if (tmpux > -1 && tmpuy > -1) {
          filter->motionmaskcellsidx =
              g_new0 (motioncellidx, filter->motionmaskcells_count);
          for (i = 0; i < filter->motionmaskcells_count; ++i) {
            sscanf (motionmaskcellsstr[i], "%d:%d", &masklinidx, &maskcolidx);
            filter->motionmaskcellsidx[i].lineidx = masklinidx;
            filter->motionmaskcellsidx[i].columnidx = maskcolidx;
          }
        } else {
          filter->motionmaskcells_count = 0;
        }
      }
      g_strfreev (motionmaskcellsstr);
      tmpux = -1;
      tmpuy = -1;
      tmplx = -1;
      tmply = -1;
      break;
    case PROP_CELLSCOLOR:
      colorstr = g_strsplit (g_value_get_string (value), ",", 4);
      for (cellscolorscnt = 0; colorstr[cellscolorscnt] != NULL;
          ++cellscolorscnt);
      if (cellscolorscnt != 3) {
        GST_WARNING_OBJECT (filter, "Ignoring badly-formatted cellscolor RGB "
            "string");
      } else {
        sscanf (colorstr[0], "%d", &r);
        sscanf (colorstr[1], "%d", &g);
        sscanf (colorstr[2], "%d", &b);
        //check right RGB color format
        r = CLAMP (r, 1, 255);
        g = CLAMP (g, 1, 255);
        b = CLAMP (b, 1, 255);
        filter->motioncellscolor->R_channel_value = r;
        filter->motioncellscolor->G_channel_value = g;
        filter->motioncellscolor->B_channel_value = b;
      }
      g_strfreev (colorstr);
      break;
    case PROP_MOTIONCELLSIDX:
      motioncellsstr = g_strsplit (g_value_get_string (value), ",", 255);

      //setting number of regions
      for (filter->motioncells_count = 0;
          motioncellsstr[filter->motioncells_count] != NULL;
          ++filter->motioncells_count);
      if (filter->motioncells_count > 0) {
        sscanf (motioncellsstr[0], "%d:%d", &tmpux, &tmpuy);
        if (tmpux > -1 && tmpuy > -1) {
          GFREE (filter->motioncellsidx);

          filter->motioncellsidx =
              g_new0 (motioncellidx, filter->motioncells_count);

          for (i = 0; i < filter->motioncells_count; ++i) {
            sscanf (motioncellsstr[i], "%d:%d", &linidx, &colidx);
            filter->motioncellsidx[i].lineidx = linidx;
            filter->motioncellsidx[i].columnidx = colidx;
          }
        } else {
          filter->motioncells_count = 0;
        }
      }
      g_strfreev (motioncellsstr);
      tmpux = -1;
      tmpuy = -1;
      tmplx = -1;
      tmply = -1;
      break;
    case PROP_MOTIONCELLTHICKNESS:
      filter->thickness = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
  GST_OBJECT_UNLOCK (filter);
}

static void
gst_motion_cells_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstMotioncells *filter = gst_motion_cells (object);
  GString *str;
  int i;

  GST_OBJECT_LOCK (filter);
  switch (prop_id) {
    case PROP_GRID_X:
      g_value_set_int (value, filter->gridx);
      break;
    case PROP_GRID_Y:
      g_value_set_int (value, filter->gridy);
      break;
    case PROP_GAP:
      g_value_set_int (value, filter->gap);
      break;
    case PROP_POSTNOMOTION:
      g_value_set_int (value, filter->postnomotion);
      break;
    case PROP_MINIMUNMOTIONFRAMES:
      g_value_set_int (value, filter->minimum_motion_frames);
      break;
    case PROP_SENSITIVITY:
      g_value_set_double (value, filter->sensitivity);
      break;
    case PROP_THRESHOLD:
      g_value_set_double (value, filter->threshold);
      break;
    case PROP_DISPLAY:
      g_value_set_boolean (value, filter->display);
      break;
    case PROP_POSTALLMOTION:
      g_value_set_boolean (value, filter->postallmotion);
      break;
    case PROP_USEALPHA:
      g_value_set_boolean (value, filter->usealpha);
      break;
    case PROP_CALCULATEMOTION:
      g_value_set_boolean (value, filter->calculate_motion);
      break;
    case PROP_DATE:
      g_value_set_long (value, filter->starttime);
      break;
    case PROP_DATAFILE:
      g_value_set_string (value, filter->basename_datafile);
      break;
    case PROP_DATAFILE_EXT:
      g_value_set_string (value, filter->datafile_extension);
      break;
    case PROP_MOTIONMASKCOORD:
      str = g_string_new ("");
      for (i = 0; i < filter->motionmaskcoord_count; ++i) {
        if (i < filter->motionmaskcoord_count - 1)
          g_string_append_printf (str, "%d:%d:%d:%d,",
              filter->motionmaskcoords[i].upper_left_x,
              filter->motionmaskcoords[i].upper_left_y,
              filter->motionmaskcoords[i].lower_right_x,
              filter->motionmaskcoords[i].lower_right_y);
        else
          g_string_append_printf (str, "%d:%d:%d:%d",
              filter->motionmaskcoords[i].upper_left_x,
              filter->motionmaskcoords[i].upper_left_y,
              filter->motionmaskcoords[i].lower_right_x,
              filter->motionmaskcoords[i].lower_right_y);

      }
      g_value_set_string (value, str->str);
      g_string_free (str, TRUE);
      break;
    case PROP_MOTIONMASKCELLSPOS:
      str = g_string_new ("");
      for (i = 0; i < filter->motionmaskcells_count; ++i) {
        if (i < filter->motionmaskcells_count - 1)
          g_string_append_printf (str, "%d:%d,",
              filter->motionmaskcellsidx[i].lineidx,
              filter->motionmaskcellsidx[i].columnidx);
        else
          g_string_append_printf (str, "%d:%d",
              filter->motionmaskcellsidx[i].lineidx,
              filter->motionmaskcellsidx[i].columnidx);
      }
      g_value_set_string (value, str->str);
      g_string_free (str, TRUE);
      break;
    case PROP_CELLSCOLOR:
      str = g_string_new ("");

      g_string_printf (str, "%d,%d,%d",
          filter->motioncellscolor->R_channel_value,
          filter->motioncellscolor->G_channel_value,
          filter->motioncellscolor->B_channel_value);

      g_value_set_string (value, str->str);
      g_string_free (str, TRUE);
      break;
    case PROP_MOTIONCELLSIDX:
      str = g_string_new ("");
      for (i = 0; i < filter->motioncells_count; ++i) {
        if (i < filter->motioncells_count - 1)
          g_string_append_printf (str, "%d:%d,",
              filter->motioncellsidx[i].lineidx,
              filter->motioncellsidx[i].columnidx);
        else
          g_string_append_printf (str, "%d:%d",
              filter->motioncellsidx[i].lineidx,
              filter->motioncellsidx[i].columnidx);
      }
      g_value_set_string (value, str->str);
      g_string_free (str, TRUE);
      break;
    case PROP_MOTIONCELLTHICKNESS:
      g_value_set_int (value, filter->thickness);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
  GST_OBJECT_UNLOCK (filter);
}

static void
gst_motioncells_update_motion_cells (GstMotioncells * filter)
{
  int i = 0;
  int cellscnt = 0;
  int j = 0;
  int newcellscnt;
  motioncellidx *motioncellsidx;
  for (i = 0; i < filter->motioncells_count; i++) {
    if ((filter->gridx <= filter->motioncellsidx[i].columnidx) ||
        (filter->gridy <= filter->motioncellsidx[i].lineidx)) {
      cellscnt++;
    }
  }
  newcellscnt = filter->motioncells_count - cellscnt;
  motioncellsidx = g_new0 (motioncellidx, newcellscnt);
  for (i = 0; i < filter->motioncells_count; i++) {
    if ((filter->motioncellsidx[i].lineidx < filter->gridy) &&
        (filter->motioncellsidx[i].columnidx < filter->gridx)) {
      motioncellsidx[j].lineidx = filter->motioncellsidx[i].lineidx;
      motioncellsidx[j].columnidx = filter->motioncellsidx[i].columnidx;
      j++;
    }
  }
  GFREE (filter->motioncellsidx);
  filter->motioncells_count = newcellscnt;
  filter->motioncellsidx = g_new0 (motioncellidx, filter->motioncells_count);
  j = 0;
  for (i = 0; i < filter->motioncells_count; i++) {
    filter->motioncellsidx[i].lineidx = motioncellsidx[j].lineidx;
    filter->motioncellsidx[i].columnidx = motioncellsidx[j].columnidx;
    j++;
  }
  GFREE (motioncellsidx);
}

static void
gst_motioncells_update_motion_masks (GstMotioncells * filter)
{
  int i = 0;
  int maskcnt = 0;
  int j = 0;
  int newmaskcnt;
  motioncellidx *motionmaskcellsidx;
  for (i = 0; i < filter->motionmaskcells_count; i++) {
    if ((filter->gridx <= filter->motionmaskcellsidx[i].columnidx) ||
        (filter->gridy <= filter->motionmaskcellsidx[i].lineidx)) {
      maskcnt++;
    }
  }
  newmaskcnt = filter->motionmaskcells_count - maskcnt;
  motionmaskcellsidx = g_new0 (motioncellidx, newmaskcnt);
  for (i = 0; i < filter->motionmaskcells_count; i++) {
    if ((filter->motionmaskcellsidx[i].lineidx < filter->gridy) &&
        (filter->motionmaskcellsidx[i].columnidx < filter->gridx)) {
      motionmaskcellsidx[j].lineidx = filter->motionmaskcellsidx[i].lineidx;
      motionmaskcellsidx[j].columnidx = filter->motionmaskcellsidx[i].columnidx;
      j++;
    }
  }
  GFREE (filter->motionmaskcellsidx);
  filter->motionmaskcells_count = newmaskcnt;
  filter->motionmaskcellsidx =
      g_new0 (motioncellidx, filter->motionmaskcells_count);
  j = 0;
  for (i = 0; i < filter->motionmaskcells_count; i++) {
    filter->motionmaskcellsidx[i].lineidx = motionmaskcellsidx[j].lineidx;
    filter->motionmaskcellsidx[i].columnidx = motionmaskcellsidx[j].columnidx;
    j++;
  }
  GFREE (motionmaskcellsidx);
}

/* GstElement vmethod implementations */

/* this function handles the link with other elements */
static gboolean
gst_motion_cells_handle_sink_event (GstPad * pad, GstObject * parent,
    GstEvent * event)
{
  GstMotioncells *filter;
  GstVideoInfo info;
  gboolean res = TRUE;
  int i;

  filter = gst_motion_cells (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;
      gst_event_parse_caps (event, &caps);
      gst_video_info_from_caps (&info, caps);

      filter->width = info.width;
      filter->height = info.height;

      if (0 != filter->has_delayed_mask
          && 0 < filter->motionmaskcoord_count
          && NULL != filter->motionmaskcoords && 0 < filter->width
          && 0 < filter->height) {
        filter->has_delayed_mask = 0;
        for (i = 0; i < filter->motionmaskcoord_count; ++i) {
          fix_coords (filter->motionmaskcoords[i], filter->width,
              filter->height);
        }
      }

      filter->framerate = (double) info.fps_n / (double) info.fps_d;
      break;
    }
    default:
      break;
  }

  res = gst_pad_event_default (pad, parent, event);

  return res;
}

/* chain function
 * this function does the actual processing
 */
static GstFlowReturn
gst_motion_cells_transform_ip (GstOpencvVideoFilter * base, GstBuffer * buf,
    cv::Mat img)
{
  GstMotioncells *filter = gst_motion_cells (base);

  GST_OBJECT_LOCK (filter);
  if (filter->calculate_motion) {
    double sensitivity;
    int framerate, gridx, gridy, motionmaskcells_count, motionmaskcoord_count,
        motioncells_count, i;
    int thickness, success, motioncellsidxcnt, numberOfCells,
        motioncellsnumber, cellsOfInterestNumber;
    int mincellsOfInterestNumber, motiondetect;
    guint minimum_motion_frames, postnomotion;
    char *datafile;
    bool display, changed_datafile, useAlpha;
    gint64 starttime;
    motionmaskcoordrect *motionmaskcoords;
    motioncellidx *motionmaskcellsidx;
    cellscolor motioncellscolor;
    motioncellidx *motioncellsidx;

    if (filter->firstframe) {
      setPrevFrame (img, filter->id);
      filter->firstframe = FALSE;
    }

    minimum_motion_frames = filter->minimum_motion_frames;
    postnomotion = filter->postnomotion;
    sensitivity = filter->sensitivity;
    framerate = filter->framerate;
    gridx = filter->gridx;
    gridy = filter->gridy;
    display = filter->display;
    motionmaskcoord_count = filter->motionmaskcoord_count;
    motionmaskcoords =
        g_new0 (motionmaskcoordrect, filter->motionmaskcoord_count);
    for (i = 0; i < filter->motionmaskcoord_count; i++) {       //we need divide 2 because we use gauss pyramid in C++ side
      motionmaskcoords[i].upper_left_x =
          filter->motionmaskcoords[i].upper_left_x / 2;
      motionmaskcoords[i].upper_left_y =
          filter->motionmaskcoords[i].upper_left_y / 2;
      motionmaskcoords[i].lower_right_x =
          filter->motionmaskcoords[i].lower_right_x / 2;
      motionmaskcoords[i].lower_right_y =
          filter->motionmaskcoords[i].lower_right_y / 2;
    }

    motioncellscolor.R_channel_value =
        filter->motioncellscolor->R_channel_value;
    motioncellscolor.G_channel_value =
        filter->motioncellscolor->G_channel_value;
    motioncellscolor.B_channel_value =
        filter->motioncellscolor->B_channel_value;

    if ((filter->changed_gridx || filter->changed_gridy
            || filter->changed_startime)) {
      if ((g_strcmp0 (filter->cur_datafile, NULL) != 0)) {
        GFREE (filter->cur_datafile);
        filter->datafileidx++;
        filter->cur_datafile =
            g_strdup_printf ("%s-%d.%s", filter->basename_datafile,
            filter->datafileidx, filter->datafile_extension);
        filter->changed_datafile = TRUE;
        motion_cells_free_resources (filter->id);
      }
      if (filter->motioncells_count > 0)
        gst_motioncells_update_motion_cells (filter);
      if (filter->motionmaskcells_count > 0)
        gst_motioncells_update_motion_masks (filter);
      filter->changed_gridx = FALSE;
      filter->changed_gridy = FALSE;
      filter->changed_startime = FALSE;
    }

    datafile = g_strdup (filter->cur_datafile);
    filter->cur_buff_timestamp = (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND);
    filter->starttime +=
        (filter->cur_buff_timestamp - filter->prev_buff_timestamp);
    starttime = filter->starttime;
    if (filter->changed_datafile || filter->diff_timestamp < 0)
      filter->diff_timestamp =
          (gint64) (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND);
    changed_datafile = filter->changed_datafile;

    motionmaskcells_count = filter->motionmaskcells_count;
    motionmaskcellsidx = g_new0 (motioncellidx, filter->motionmaskcells_count);
    for (i = 0; i < filter->motionmaskcells_count; i++) {
      motionmaskcellsidx[i].lineidx = filter->motionmaskcellsidx[i].lineidx;
      motionmaskcellsidx[i].columnidx = filter->motionmaskcellsidx[i].columnidx;
    }
    motioncells_count = filter->motioncells_count;
    motioncellsidx = g_new0 (motioncellidx, filter->motioncells_count);
    for (i = 0; i < filter->motioncells_count; i++) {
      motioncellsidx[i].lineidx = filter->motioncellsidx[i].lineidx;
      motioncellsidx[i].columnidx = filter->motioncellsidx[i].columnidx;
    }

    useAlpha = filter->usealpha;
    thickness = filter->thickness;
    success =
        perform_detection_motion_cells (img, sensitivity,
        framerate, gridx, gridy,
        (gint64) (GST_BUFFER_TIMESTAMP (buf) / GST_MSECOND) -
        filter->diff_timestamp, display, useAlpha, motionmaskcoord_count,
        motionmaskcoords, motionmaskcells_count, motionmaskcellsidx,
        motioncellscolor, motioncells_count, motioncellsidx, starttime,
        datafile, changed_datafile, thickness, filter->id);

    if ((success == 1) && (filter->sent_init_error_msg == FALSE)) {
      char *initfailedreason;
      int initerrorcode;
      GstStructure *s;
      GstMessage *m;

      initfailedreason = getInitDataFileFailed (filter->id);
      initerrorcode = getInitErrorCode (filter->id);
      s = gst_structure_new ("motion", "init_error_code", G_TYPE_INT,
          initerrorcode, "details", G_TYPE_STRING, initfailedreason, NULL);
      m = gst_message_new_element (GST_OBJECT (filter), s);
      gst_element_post_message (GST_ELEMENT (filter), m);
      filter->sent_init_error_msg = TRUE;
    }
    if ((success == -1) && (filter->sent_save_error_msg == FALSE)) {
      char *savefailedreason;
      int saveerrorcode;
      GstStructure *s;
      GstMessage *m;

      savefailedreason = getSaveDataFileFailed (filter->id);
      saveerrorcode = getSaveErrorCode (filter->id);
      s = gst_structure_new ("motion", "save_error_code", G_TYPE_INT,
          saveerrorcode, "details", G_TYPE_STRING, savefailedreason, NULL);
      m = gst_message_new_element (GST_OBJECT (filter), s);
      gst_element_post_message (GST_ELEMENT (filter), m);
      filter->sent_save_error_msg = TRUE;
    }
    if (success == -2) {
      GST_LOG_OBJECT (filter, "frame dropped");
      filter->prev_buff_timestamp = filter->cur_buff_timestamp;
      //free
      GFREE (datafile);
      GFREE (motionmaskcoords);
      GFREE (motionmaskcellsidx);
      GFREE (motioncellsidx);
      GST_OBJECT_UNLOCK (filter);
      return GST_FLOW_OK;
    }

    filter->changed_datafile = getChangedDataFile (filter->id);
    motioncellsidxcnt = getMotionCellsIdxCnt (filter->id);
    numberOfCells = filter->gridx * filter->gridy;
    motioncellsnumber = motioncellsidxcnt / MSGLEN;
    cellsOfInterestNumber = (filter->motioncells_count > 0) ?   //how many cells interest for us
        (filter->motioncells_count) : (numberOfCells);
    mincellsOfInterestNumber =
        floor ((double) cellsOfInterestNumber * filter->threshold);
    GST_OBJECT_UNLOCK (filter);
    motiondetect = (motioncellsnumber >= mincellsOfInterestNumber) ? 1 : 0;
    if ((motioncellsidxcnt > 0) && (motiondetect == 1)) {
      char *detectedmotioncells;

      filter->last_motion_timestamp = GST_BUFFER_TIMESTAMP (buf);
      detectedmotioncells = getMotionCellsIdx (filter->id);
      if (detectedmotioncells) {
        filter->consecutive_motion++;
        if ((filter->previous_motion == FALSE)
            && (filter->consecutive_motion >= minimum_motion_frames)) {
          GstStructure *s;
          GstMessage *m;

          GST_DEBUG_OBJECT (filter, "motion started, post msg on the bus");
          filter->previous_motion = TRUE;
          filter->motion_begin_timestamp = GST_BUFFER_TIMESTAMP (buf);
          s = gst_structure_new ("motion", "motion_cells_indices",
              G_TYPE_STRING, detectedmotioncells, "motion_begin",
              G_TYPE_UINT64, filter->motion_begin_timestamp, NULL);
          m = gst_message_new_element (GST_OBJECT (filter), s);
          gst_element_post_message (GST_ELEMENT (filter), m);
        } else if (filter->postallmotion) {
          GstStructure *s;
          GstMessage *m;

          GST_DEBUG_OBJECT (filter, "motion, post msg on the bus");
          filter->motion_timestamp = GST_BUFFER_TIMESTAMP (buf);
          s = gst_structure_new ("motion", "motion_cells_indices",
              G_TYPE_STRING, detectedmotioncells, "motion", G_TYPE_UINT64,
              filter->motion_timestamp, NULL);
          m = gst_message_new_element (GST_OBJECT (filter), s);
          gst_element_post_message (GST_ELEMENT (filter), m);
        }
      } else {
        GstStructure *s;
        GstMessage *m;

        s = gst_structure_new ("motion", "motion_cells_indices",
            G_TYPE_STRING, "error", NULL);
        m = gst_message_new_element (GST_OBJECT (filter), s);
        gst_element_post_message (GST_ELEMENT (filter), m);
      }
    } else {
      filter->consecutive_motion = 0;
      if ((((GST_BUFFER_TIMESTAMP (buf) -
                      filter->last_motion_timestamp) / 1000000000l) >=
              filter->gap)
          && (filter->last_motion_timestamp > 0)) {
        if (filter->previous_motion) {
          GstStructure *s;
          GstMessage *m;

          GST_DEBUG_OBJECT (filter, "motion finished, post msg on the bus");
          filter->previous_motion = FALSE;
          s = gst_structure_new ("motion", "motion_finished", G_TYPE_UINT64,
              filter->last_motion_timestamp, NULL);
          m = gst_message_new_element (GST_OBJECT (filter), s);
          gst_element_post_message (GST_ELEMENT (filter), m);
        }
      }
    }
    if (postnomotion > 0) {
      guint64 last_buf_timestamp = GST_BUFFER_TIMESTAMP (buf) / 1000000000l;
      if ((last_buf_timestamp -
              (filter->last_motion_timestamp / 1000000000l)) >=
          filter->postnomotion) {
        GST_DEBUG_OBJECT (filter, "post no motion msg on the bus");
        if ((last_buf_timestamp -
                (filter->last_nomotion_notified / 1000000000l)) >=
            filter->postnomotion) {
          GstStructure *s;
          GstMessage *m;

          filter->last_nomotion_notified = GST_BUFFER_TIMESTAMP (buf);
          s = gst_structure_new ("motion", "no_motion", G_TYPE_UINT64,
              filter->last_motion_timestamp, NULL);
          m = gst_message_new_element (GST_OBJECT (filter), s);
          gst_element_post_message (GST_ELEMENT (filter), m);
        }
      }
    }
    filter->prev_buff_timestamp = filter->cur_buff_timestamp;

    //free
    GFREE (datafile);
    GFREE (motionmaskcoords);
    GFREE (motionmaskcellsidx);
    GFREE (motioncellsidx);
  } else {
    GST_WARNING_OBJECT (filter, "Motion detection disabled");
    GST_OBJECT_UNLOCK (filter);
  }
  return GST_FLOW_OK;
}

/* entry point to initialize the plug-in
 * initialize the plug-in itself
 * register the element factories and other features
 */
gboolean
gst_motion_cells_plugin_init (GstPlugin * plugin)
{
  /* debug category for fltering log messages */
  GST_DEBUG_CATEGORY_INIT (gst_motion_cells_debug,
      "motioncells",
      0,
      "Performs motion detection on videos, providing detected positions via bus messages");

  return gst_element_register (plugin, "motioncells", GST_RANK_NONE,
      GST_TYPE_MOTIONCELLS);
}
