/*
 * Image management functions for libfprint
 * Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
 *
 * 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
 */

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <glib.h>

#include "fp_internal.h"
#include "nbis/include/bozorth.h"
#include "nbis/include/lfs.h"

/**
 * SECTION:img
 * @title: Image operations
 * @short_description: Image operation functions
 *
 * libfprint offers several ways of retrieving images from imaging devices,
 * one example being the fp_dev_img_capture() function. The functions
 * documented below allow you to work with such images.
 *
 * # Image format # {#img_fmt}
 * All images are represented as 8-bit greyscale data.
 *
 * # Image standardization # {#img_std}
 * In some contexts, images you are provided through libfprint are raw images
 * from the hardware. The orientation of these varies from device-to-device,
 * as does the color scheme (black-on-white or white-on-black?). libfprint
 * provides the fp_img_standardize() function to convert images into standard
 * form, which is defined to be: finger flesh as black on white surroundings,
 * natural upright orientation.
 */

/**
 * SECTION:fpi-img
 * @title: Driver Image operations
 * @short_description: Driver image operation functions
 *
 * Those are the driver-specific helpers for #fp_img manipulation. See #fp_img's
 * documentation for more information about data formats, and their uses in
 * front-end applications.
 */

/**
 * fpi_img_new:
 * @length: the length of data to allocate
 *
 * Creates a new #fp_img structure with @length bytes of data allocated
 * to hold the image.
 *
 * Returns: a new #fp_img to free with fp_img_free()
 */
struct fp_img *fpi_img_new(size_t length)
{
	struct fp_img *img = g_malloc0(sizeof(*img) + length);
	fp_dbg("length=%zd", length);
	img->length = length;
	return img;
}

/**
 * fpi_img_new_for_imgdev:
 * @imgdev: a #fp_img_dev imaging fingerprint device
 *
 * Creates a new #fp_img structure, like fpi_img_new(), but uses the
 * driver's advertised height and width to calculate the size of the
 * length of data to allocate.
 *
 * Returns: a new #fp_img to free with fp_img_free()
 */
struct fp_img *fpi_img_new_for_imgdev(struct fp_img_dev *imgdev)
{
	struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(FP_DEV(imgdev)->drv);
	int width = imgdrv->img_width;
	int height = imgdrv->img_height;
	struct fp_img *img = fpi_img_new(width * height);
	img->width = width;
	img->height = height;
	return img;
}

/**
 * fpi_img_is_sane:
 * @img: a #fp_img image
 *
 * Checks whether an #fp_img structure passes some basic checks, such
 * as length, width and height being non-zero, and the buffer being
 * big enough to hold the image of said size.
 *
 * Returns: %TRUE if the image is sane, %FALSE otherwise
 */
gboolean fpi_img_is_sane(struct fp_img *img)
{
	guint len;

	/* basic checks */
	if (!img->length || img->width <= 0 || img->height <= 0)
		return FALSE;

	/* Are width and height just too big? */
	if (!g_uint_checked_mul(&len, img->width, img->height) ||
	    len > G_MAXINT)
		return FALSE;

	/* buffer big enough? */
	if (len > img->length)
		return FALSE;

	return TRUE;
}

/**
 * fpi_img_realloc:
 * @img: an #fp_img image
 * @newsize: the new length of the image
 *
 * Changes the size of the data part of the #fp_img.
 *
 * Returns: the modified #fp_img, the same as the first argument to this function
 */
struct fp_img *fpi_img_realloc(struct fp_img *img, size_t newsize)
{
	return g_realloc(img, sizeof(*img) + newsize);
}

/**
 * fp_img_free:
 * @img: the image to destroy. If %NULL, function simply returns.
 *
 * Frees an image. Must be called when you are finished working with an image.
 */
API_EXPORTED void fp_img_free(struct fp_img *img)
{
	if (!img)
		return;

	if (img->minutiae)
		free_minutiae(img->minutiae);
	if (img->binarized)
		free(img->binarized);
	g_free(img);
}

/**
 * fp_img_get_height:
 * @img: an image
 *
 * Gets the pixel height of an image.
 *
 * Returns: the height of the image
 */
API_EXPORTED int fp_img_get_height(struct fp_img *img)
{
	return img->height;
}

/**
 * fp_img_get_width:
 * @img: an image
 *
 * Gets the pixel width of an image.
 *
 * Returns: the width of the image
 */
API_EXPORTED int fp_img_get_width(struct fp_img *img)
{
	return img->width;
}

/**
 * fp_img_get_data:
 * @img: an image
 *
 * Gets the greyscale data for an image. This data must not be modified or
 * freed, and must not be used after fp_img_free() has been called.
 *
 * Returns: a pointer to libfprint's internal data for the image
 */
API_EXPORTED unsigned char *fp_img_get_data(struct fp_img *img)
{
	return img->data;
}

/**
 * fp_img_save_to_file:
 * @img: the image to save
 * @path: the path to save the image. Existing files will be overwritten.
 *
 * A quick convenience function to save an image to a file in
 * [PGM format](http://netpbm.sourceforge.net/doc/pgm.html).
 *
 * Returns: 0 on success, non-zero on error.
 */
API_EXPORTED int fp_img_save_to_file(struct fp_img *img, char *path)
{
	FILE *fd = fopen(path, "w");
	size_t write_size = img->width * img->height;
	int r;

	if (!fd) {
		fp_dbg("could not open '%s' for writing: %d", path, errno);
		return -errno;
	}

	r = fprintf(fd, "P5 %d %d 255\n", img->width, img->height);
	if (r < 0) {
		fclose(fd);
		fp_err("pgm header write failed, error %d", r);
		return r;
	}

	r = fwrite(img->data, 1, write_size, fd);
	if (r < write_size) {
		fclose(fd);
		fp_err("short write (%d)", r);
		return -EIO;
	}

	fclose(fd);
	fp_dbg("written to '%s'", path);
	return 0;
}

static void vflip(struct fp_img *img)
{
	int width = img->width;
	int data_len = img->width * img->height;
	unsigned char rowbuf[width];
	int i;

	for (i = 0; i < img->height / 2; i++) {
		int offset = i * width;
		int swap_offset = data_len - (width * (i + 1));

		/* copy top row into buffer */
		memcpy(rowbuf, img->data + offset, width);

		/* copy lower row over upper row */
		memcpy(img->data + offset, img->data + swap_offset, width);

		/* copy buffer over lower row */
		memcpy(img->data + swap_offset, rowbuf, width);
	}
}

static void hflip(struct fp_img *img)
{
	int width = img->width;
	unsigned char rowbuf[width];
	int i, j;

	for (i = 0; i < img->height; i++) {
		int offset = i * width;

		memcpy(rowbuf, img->data + offset, width);
		for (j = 0; j < width; j++)
			img->data[offset + j] = rowbuf[width - j - 1];
	}
}

static void invert_colors(struct fp_img *img)
{
	int data_len = img->width * img->height;
	int i;
	for (i = 0; i < data_len; i++)
		img->data[i] = 0xff - img->data[i];
}

/**
 * fp_img_standardize:
 * @img: the image to standardize
 *
 * [Standardizes](libfprint-Image-operations.html#img_std) an image by normalizing its orientation, colors,
 * etc. It is safe to call this multiple times on an image, libfprint keeps
 * track of the work it needs to do to make an image standard and will not
 * perform these operations more than once for a given image.
 */
API_EXPORTED void fp_img_standardize(struct fp_img *img)
{
	if (img->flags & FP_IMG_V_FLIPPED) {
		vflip(img);
		img->flags &= ~FP_IMG_V_FLIPPED;
	}
	if (img->flags & FP_IMG_H_FLIPPED) {
		hflip(img);
		img->flags &= ~FP_IMG_H_FLIPPED;
	}
	if (img->flags & FP_IMG_COLORS_INVERTED) {
		invert_colors(img);
		img->flags &= ~FP_IMG_COLORS_INVERTED;
	}
}

/* Based on write_minutiae_XYTQ and bz_load */
static void minutiae_to_xyt(struct fp_minutiae *minutiae, int bwidth,
	int bheight, unsigned char *buf)
{
	int i;
	struct fp_minutia *minutia;
	struct minutiae_struct c[MAX_FILE_MINUTIAE];
	struct xyt_struct *xyt = (struct xyt_struct *) buf;

	/* struct xyt_struct uses arrays of MAX_BOZORTH_MINUTIAE (200) */
	int nmin = min(minutiae->num, MAX_BOZORTH_MINUTIAE);

	for (i = 0; i < nmin; i++){
		minutia = minutiae->list[i];

		lfs2nist_minutia_XYT(&c[i].col[0], &c[i].col[1], &c[i].col[2],
				minutia, bwidth, bheight);
		c[i].col[3] = sround(minutia->reliability * 100.0);

		if (c[i].col[2] > 180)
			c[i].col[2] -= 360;
	}

	qsort((void *) &c, (size_t) nmin, sizeof(struct minutiae_struct),
			sort_x_y);

	for (i = 0; i < nmin; i++) {
		xyt->xcol[i]     = c[i].col[0];
		xyt->ycol[i]     = c[i].col[1];
		xyt->thetacol[i] = c[i].col[2];
	}
	xyt->nrows = nmin;
}

#define FP_IMG_STANDARDIZATION_FLAGS (FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED \
	| FP_IMG_COLORS_INVERTED)

static int fpi_img_detect_minutiae(struct fp_img *img)
{
	struct fp_minutiae *minutiae;
	int r;
	int *direction_map, *low_contrast_map, *low_flow_map;
	int *high_curve_map, *quality_map;
	int map_w, map_h;
	unsigned char *bdata;
	int bw, bh, bd;
	GTimer *timer;

	if (img->flags & FP_IMG_STANDARDIZATION_FLAGS) {
		fp_err("Cannot detect minutiae for non-standardized images");
		return -EINVAL;
	}

	/* 25.4 mm per inch */
	timer = g_timer_new();
	r = get_minutiae(&minutiae, &quality_map, &direction_map,
                         &low_contrast_map, &low_flow_map, &high_curve_map,
                         &map_w, &map_h, &bdata, &bw, &bh, &bd,
                         img->data, img->width, img->height, 8,
						 DEFAULT_PPI / (double)25.4, &g_lfsparms_V2);
	g_timer_stop(timer);
	fp_dbg("minutiae scan completed in %f secs", g_timer_elapsed(timer, NULL));
	g_timer_destroy(timer);
	if (r) {
		fp_err("get minutiae failed, code %d", r);
		return r;
	}
	fp_dbg("detected %d minutiae", minutiae->num);
	img->minutiae = minutiae;
	img->binarized = bdata;

	free(quality_map);
	free(direction_map);
	free(low_contrast_map);
	free(low_flow_map);
	free(high_curve_map);
	return minutiae->num;
}

int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
	struct fp_print_data **ret)
{
	struct fp_print_data *print;
	struct fp_print_data_item *item;
	int r;

	if (!img->minutiae) {
		r = fpi_img_detect_minutiae(img);
		if (r < 0)
			return r;
		if (!img->minutiae) {
			fp_err("no minutiae after successful detection?");
			return -ENOENT;
		}
	}

	/* FIXME: space is wasted if we don't hit the max minutiae count. would
	 * be good to make this dynamic. */
	print = fpi_print_data_new(FP_DEV(imgdev));
	item = fpi_print_data_item_new(sizeof(struct xyt_struct));
	print->type = PRINT_DATA_NBIS_MINUTIAE;
	minutiae_to_xyt(img->minutiae, img->width, img->height, item->data);
	print->prints = g_slist_prepend(print->prints, item);

	/* FIXME: the print buffer at this point is endian-specific, and will
	 * only work when loaded onto machines with identical endianness. not good!
	 * data format should be platform-independent. */
	*ret = print;

	return 0;
}

int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
	struct fp_print_data *new_print)
{
	int score, max_score = 0, probe_len;
	struct xyt_struct *pstruct = NULL;
	struct xyt_struct *gstruct = NULL;
	struct fp_print_data_item *data_item;
	GSList *list_item;

	if (enrolled_print->type != PRINT_DATA_NBIS_MINUTIAE ||
	     new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
		fp_err("invalid print format");
		return -EINVAL;
	}

	if (g_slist_length(new_print->prints) != 1) {
		fp_err("new_print contains more than one sample, is it enrolled print?");
		return -EINVAL;
	}

	data_item = new_print->prints->data;
	pstruct = (struct xyt_struct *)data_item->data;

	probe_len = bozorth_probe_init(pstruct);
	list_item = enrolled_print->prints;
	do {
		data_item = list_item->data;
		gstruct = (struct xyt_struct *)data_item->data;
		score = bozorth_to_gallery(probe_len, pstruct, gstruct);
		fp_dbg("score %d", score);
		max_score = max(score, max_score);
		list_item = g_slist_next(list_item);
	} while (list_item);

	return max_score;
}

int fpi_img_compare_print_data_to_gallery(struct fp_print_data *print,
	struct fp_print_data **gallery, int match_threshold, size_t *match_offset)
{
	struct xyt_struct *pstruct;
	struct xyt_struct *gstruct;
	struct fp_print_data *gallery_print;
	struct fp_print_data_item *data_item;
	int probe_len;
	size_t i = 0;
	int r;
	GSList *list_item;

	if (g_slist_length(print->prints) != 1) {
		fp_err("new_print contains more than one sample, is it enrolled print?");
		return -EINVAL;
	}

	data_item = print->prints->data;
	pstruct = (struct xyt_struct *)data_item->data;

	probe_len = bozorth_probe_init(pstruct);
	while ((gallery_print = gallery[i++])) {
		list_item = gallery_print->prints;
		do {
			data_item = list_item->data;
			gstruct = (struct xyt_struct *)data_item->data;
			r = bozorth_to_gallery(probe_len, pstruct, gstruct);
			if (r >= match_threshold) {
				*match_offset = i - 1;
				return FP_VERIFY_MATCH;
			}
			list_item = g_slist_next(list_item);
		} while (list_item);
	}
	return FP_VERIFY_NO_MATCH;
}

/**
 * fp_img_binarize:
 * @img: a standardized image
 *
 * Get a binarized form of a standardized scanned image. This is where the
 * fingerprint image has been "enhanced" and is a set of pure black ridges
 * on a pure white background. Internally, image processing happens on top
 * of the binarized image.
 *
 * The image must have been [standardized](libfprint-Image-operations.html#img_std)
 * otherwise this function will fail.
 *
 * It is safe to binarize an image and free the original while continuing
 * to use the binarized version.
 *
 * You cannot binarize an image twice.
 *
 * Returns: a new image representing the binarized form of the original, or
 * %NULL on error. Must be freed with fp_img_free() after use.
 */
API_EXPORTED struct fp_img *fp_img_binarize(struct fp_img *img)
{
	struct fp_img *ret;
	int height = img->height;
	int width = img->width;
	int imgsize = height * width;

	if (img->flags & FP_IMG_BINARIZED_FORM) {
		fp_err("image already binarized");
		return NULL;
	}

	if (!img->binarized) {
		int r = fpi_img_detect_minutiae(img);
		if (r < 0)
			return NULL;
		if (!img->binarized) {
			fp_err("no minutiae after successful detection?");
			return NULL;
		}
	}

	ret = fpi_img_new(imgsize);
	ret->flags |= FP_IMG_BINARIZED_FORM;
	ret->width = width;
	ret->height = height;
	memcpy(ret->data, img->binarized, imgsize);
	return ret;
}

/**
 * fp_img_get_minutiae:
 * @img: a standardized image
 * @nr_minutiae: an output location to store minutiae list length
 *
 * Get a list of minutiae detected in an image. A minutia point is a feature
 * detected on a fingerprint, typically where ridges end or split.
 * libfprint's image processing code relies upon comparing sets of minutiae,
 * so accurate placement of minutia points is critical for good imaging
 * performance.
 *
 * The image must have been [standardized](libfprint-Image-operations.html#img_std)
 * otherwise this function will fail.
 *
 * You cannot pass a binarized image to this function. Instead, pass the
 * original image.
 *
 * Returns a list of pointers to minutiae, where the list is of length
 * indicated in the nr_minutiae output parameter. The returned list is only
 * valid while the parent image has not been freed, and the minutiae data
 * must not be modified or freed.
 *
 * Returns: a list of minutiae points. Must not be modified or freed.
 */
API_EXPORTED struct fp_minutia **fp_img_get_minutiae(struct fp_img *img,
	int *nr_minutiae)
{
	if (img->flags & FP_IMG_BINARIZED_FORM) {
		fp_err("image is binarized");
		return NULL;
	}

	if (!img->minutiae) {
		int r = fpi_img_detect_minutiae(img);
		if (r < 0)
			return NULL;
		if (!img->minutiae) {
			fp_err("no minutiae after successful detection?");
			return NULL;
		}
	}

	*nr_minutiae = img->minutiae->num;
	return img->minutiae->list;
}

/**
 * fp_minutia_get_coords:
 * @minutia: a struct #fp_minutia
 * @coord_x: the return variable for the X coordinate of the minutia
 * @coord_y: the return variable for the Y coordinate of the minutia
 *
 * Sets @coord_x and @coord_y to be the coordinates of the detected minutia, so it
 * can be presented in a more verbose user interface. This is usually only
 * used for debugging purposes.
 *
 * Returns: 0 on success, -1 on error.
 */
API_EXPORTED int fp_minutia_get_coords(struct fp_minutia *minutia, int *coord_x, int *coord_y)
{
	g_return_val_if_fail (minutia != NULL, -1);
	g_return_val_if_fail (coord_x != NULL, -1);
	g_return_val_if_fail (coord_y != NULL, -1);

	*coord_x = minutia->x;
	*coord_y = minutia->y;

	return 0;
}

/**
 * fpi_std_sq_dev:
 * @buf: buffer (usually bitmap, one byte per pixel)
 * @size: size of @buffer
 *
 * Calculates the squared standard deviation of the individual
 * pixels in the buffer, as per the following formula:
 * |[<!-- -->
 *    mean = sum (buf[0..size]) / size
 *    sq_dev = sum ((buf[0.size] - mean) ^ 2)
 * ]|
 * This function is usually used to determine whether image
 * is empty.
 *
 * Returns: the squared standard deviation for @buffer
 */
int fpi_std_sq_dev(const unsigned char *buf, int size)
{
	int res = 0, mean = 0, i;

	if (size > (INT_MAX / 65536)) {
		fp_err("%s: we might get an overflow!", __func__);
		return -EOVERFLOW;
	}

	for (i = 0; i < size; i++)
		mean += buf[i];

	mean /= size;

	for (i = 0; i < size; i++) {
		int dev = (int)buf[i] - mean;
		res += dev*dev;
	}

	return res / size;
}

/**
 * fpi_mean_sq_diff_norm:
 * @buf1: buffer (usually bitmap, one byte per pixel)
 * @buf2: buffer (usually bitmap, one byte per pixel)
 * @size: buffer size of smallest buffer
 *
 * This function calculates the normalized mean square difference of
 * two buffers, usually two lines, as per the following formula:
 * |[<!-- -->
 *    sq_diff = sum ((buf1[0..size] - buf2[0..size]) ^ 2) / size
 * ]|
 *
 * This functions is usually used to get numerical difference
 * between two images.
 *
 * Returns: the normalized mean squared difference between @buf1 and @buf2
 */
int fpi_mean_sq_diff_norm(unsigned char *buf1, unsigned char *buf2, int size)
{
	int res = 0, i;
	for (i = 0; i < size; i++) {
		int dev = (int)buf1[i] - (int)buf2[i];
		res += dev * dev;
	}

	return res / size;
}
