/*
 * FpImageDevice - An image based fingerprint reader device
 * Copyright (C) 2019 Benjamin Berg <bberg@redhat.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
 */

#define FP_COMPONENT "image_device"
#include "fpi-log.h"

#include "fp-image-device-private.h"

#define BOZORTH3_DEFAULT_THRESHOLD 40

/**
 * SECTION: fp-image-device
 * @title: FpImageDevice
 * @short_description: Image device subclass
 *
 * This is a helper class for the commonly found image based devices.
 */

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpImageDevice, fp_image_device, FP_TYPE_DEVICE)

enum {
  PROP_0,
  PROP_FPI_STATE,
  N_PROPS
};

static GParamSpec *properties[N_PROPS];

enum {
  FPI_STATE_CHANGED,

  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

/*******************************************************/

/* TODO:
 * - sanitize_image seems a bit odd, in particular the sizing stuff.
 **/

/* Callbacks/vfuncs */
static void
fp_image_device_open (FpDevice *device)
{
  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (device);

  /* Nothing special about opening an image device, just
   * forward the request. */
  cls->img_open (FP_IMAGE_DEVICE (device));
}

static void
fp_image_device_close (FpDevice *device)
{
  FpImageDevice *self = FP_IMAGE_DEVICE (device);
  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);

  g_assert (priv->active == FALSE);
  cls->img_close (self);
}

static void
fp_image_device_cancel_action (FpDevice *device)
{
  FpImageDevice *self = FP_IMAGE_DEVICE (device);
  FpiDeviceAction action;

  action = fpi_device_get_current_action (device);

  /* We can only cancel capture operations, in that case, deactivate and return
   * an error immediately. */
  if (action == FPI_DEVICE_ACTION_ENROLL ||
      action == FPI_DEVICE_ACTION_VERIFY ||
      action == FPI_DEVICE_ACTION_IDENTIFY ||
      action == FPI_DEVICE_ACTION_CAPTURE)
    fpi_image_device_deactivate (self, TRUE);
}

static void
fp_image_device_start_capture_action (FpDevice *device)
{
  FpImageDevice *self = FP_IMAGE_DEVICE (device);
  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
  FpiDeviceAction action;
  FpiPrintType print_type;

  /* There is just one action that we cannot support out
   * of the box, which is a capture without first waiting
   * for a finger to be on the device.
   */
  action = fpi_device_get_current_action (device);
  if (action == FPI_DEVICE_ACTION_CAPTURE)
    {
      gboolean wait_for_finger;

      fpi_device_get_capture_data (device, &wait_for_finger);

      if (!wait_for_finger)
        {
          fpi_device_action_error (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED));
          return;
        }
    }
  else if (action == FPI_DEVICE_ACTION_ENROLL)
    {
      FpPrint *enroll_print = NULL;

      fpi_device_get_enroll_data (device, &enroll_print);
      g_object_get (enroll_print, "fpi-type", &print_type, NULL);
      if (print_type != FPI_PRINT_NBIS)
        fpi_print_set_type (enroll_print, FPI_PRINT_NBIS);
    }

  priv->enroll_stage = 0;
  /* The internal state machine guarantees both of these. */
  g_assert (!priv->finger_present);
  g_assert (!priv->minutiae_scan_active);

  /* And activate the device; we rely on fpi_image_device_activate_complete()
   * to be called when done (or immediately). */
  fpi_image_device_activate (self);
}


/*********************************************************/

static void
fp_image_device_finalize (GObject *object)
{
  FpImageDevice *self = (FpImageDevice *) object;
  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);

  g_assert (priv->active == FALSE);

  G_OBJECT_CLASS (fp_image_device_parent_class)->finalize (object);
}

static void
fp_image_device_default_activate (FpImageDevice *self)
{
  fpi_image_device_activate_complete (self, NULL);
}

static void
fp_image_device_default_deactivate (FpImageDevice *self)
{
  fpi_image_device_deactivate_complete (self, NULL);
}

static void
fp_image_device_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  FpImageDevice *self = FP_IMAGE_DEVICE (object);
  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);

  switch (prop_id)
    {
    case PROP_FPI_STATE:
      g_value_set_enum (value, priv->state);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
fp_image_device_constructed (GObject *obj)
{
  FpImageDevice *self = FP_IMAGE_DEVICE (obj);
  FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
  FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);

  /* Set default threshold. */
  priv->bz3_threshold = BOZORTH3_DEFAULT_THRESHOLD;
  if (cls->bz3_threshold > 0)
    priv->bz3_threshold = cls->bz3_threshold;

  G_OBJECT_CLASS (fp_image_device_parent_class)->constructed (obj);
}

static void
fp_image_device_class_init (FpImageDeviceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  FpDeviceClass *fp_device_class = FP_DEVICE_CLASS (klass);

  object_class->finalize = fp_image_device_finalize;
  object_class->get_property = fp_image_device_get_property;
  object_class->constructed = fp_image_device_constructed;

  /* Set default enroll stage count. */
  fp_device_class->nr_enroll_stages = IMG_ENROLL_STAGES;

  fp_device_class->open = fp_image_device_open;
  fp_device_class->close = fp_image_device_close;
  fp_device_class->enroll = fp_image_device_start_capture_action;
  fp_device_class->verify = fp_image_device_start_capture_action;
  fp_device_class->identify = fp_image_device_start_capture_action;
  fp_device_class->capture = fp_image_device_start_capture_action;

  fp_device_class->cancel = fp_image_device_cancel_action;

  fpi_device_class_auto_initialize_features (fp_device_class);
  fp_device_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT;

  /* Default implementations */
  klass->activate = fp_image_device_default_activate;
  klass->deactivate = fp_image_device_default_deactivate;

  /**
   * FpImageDevice::fpi-image-device-state: (skip)
   *
   * This property is only for internal purposes.
   *
   * Stability: private
   */
  properties[PROP_FPI_STATE] =
    g_param_spec_enum ("fpi-image-device-state",
                       "Image Device State",
                       "Private: The state of the image device",
                       FPI_TYPE_IMAGE_DEVICE_STATE,
                       FPI_IMAGE_DEVICE_STATE_INACTIVE,
                       G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);

  /**
   * FpImageDevice::fpi-image-device-state-changed: (skip)
   * @image_device: A #FpImageDevice
   * @new_state: The new state of the device
   *
   * This signal is only for internal purposes.
   *
   * Stability: private
   */
  signals[FPI_STATE_CHANGED] =
    g_signal_new ("fpi-image-device-state-changed",
                  G_TYPE_FROM_CLASS (object_class),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (FpImageDeviceClass, change_state),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1, FPI_TYPE_IMAGE_DEVICE_STATE);

  g_object_class_install_properties (object_class, N_PROPS, properties);
}

static void
fp_image_device_init (FpImageDevice *self)
{
}
