/*
This is based on ``kd-match'', a suite of programs for matching
stellar catalogues with coordinate systems that differ through affine
transformations (rotations, translations, shearing and scaling).

https://github.com/UBC-Astrophysics/kd-match

Copyright (C) 2013 Jeremy Heyl <heyl@phas.ubc.ca>

Adopted for the use in the ESO MUSE pipeline.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
*/

/*----------------------------------------------------------------------------*
 *                             Includes                                       *
 *----------------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <cpl.h>

#include "muse_kdmatch.h"
#include "kdtree.h"

/*----------------------------------------------------------------------------*/
/**
 * @defgroup muse_wcs          World coordinate system (WCS) related functions
 */
/*----------------------------------------------------------------------------*/


/**
 * @brief Internal storage for transformation calculations
 */
typedef struct calctransform_s {
  double x0, y0;
  double sx1, sx2, sx3, sy1, sy2, sy3;
  double s11, s12, s13, s22, s23, s33;
  int x0_set;
} calctransform_t;


/**
 * @brief Initialize a transformation calculation structure
 */
static calctransform_t *
calctransform_init(void) {
  return calloc(1, sizeof(calctransform_t));
}


/**
 * @brief Add one coordinate pair to the transformation calculation structure
 */
static int
calctransform_add(calctransform_t *s, double pt[], double res[]) {
  if (! s->x0_set) {
    s->x0 = pt[0];
    s->y0 = pt[1];
    s->x0_set = 1;
  }
  double x = pt[0] - s->x0;
  double y = pt[1] - s->y0;
  s->sx1 += x * res[0];
  s->sx2 += y * res[0];
  s->sx3 += res[0];
  s->sy1 += x * res[1];
  s->sy2 += y * res[1];
  s->sy3 += res[1];
  s->s11 += x * x;
  s->s12 += x * y;
  s->s13 += x;
  s->s22 += y * y;
  s->s23 += y;
  s->s33++;
  return s->s33;

} /* calctransform_add() */


/**
 * @brief Get the estimated result of the calculation transformation.
 *
 * The result fills the @em param array, according to the following
 * equation:
 *
 *   pt[0] * param[0] + pt[1] * param[1] + param[2] = res[0]
 *   pt[0] * param[3] + pt[1] * param[4] + param[5] = res[1]
 */
static int
calctransform_get(calctransform_t *s, double param[]) {

  long double d = 
      (s->s13 * s->s22 - s->s12 * s->s23)* s->s13
    + (s->s11 * s->s23 - s->s12 * s->s13)* s->s23
    + (s->s12 * s->s12 - s->s11 * s->s22)* s->s33;
  param[0] = (  s->sx1 *(s->s23 * s->s23 - s->s22 * s->s33)
  	      + s->sx2 *(s->s12 * s->s33 - s->s13 * s->s23)
	      + s->sx3 *(s->s13 * s->s22 - s->s12 * s->s23))/d;
  param[1] = (  s->sx1 *(s->s12 * s->s33 - s->s13 * s->s23)
	      + s->sx2 *(s->s13 * s->s13 - s->s11 * s->s33)
 	      + s->sx3 *(s->s11 * s->s23 - s->s12 * s->s13))/d;
  param[2] = (  s->sx1 *(s->s13 * s->s22 - s->s12 * s->s23)
	      + s->sx2 *(s->s11 * s->s23 - s->s12 * s->s13)
	      + s->sx3 *(s->s12 * s->s12 - s->s11 * s->s22))/d;
  param[2] -= param[0] * s->x0 + param[1] * s->y0;
  param[3] = (  s->sy1 *(s->s23 * s->s23 - s->s22 * s->s33)
  	      + s->sy2 *(s->s12 * s->s33 - s->s13 * s->s23)
	      + s->sy3 *(s->s13 * s->s22 - s->s12 * s->s23))/d;
  param[4] = (  s->sy1 *(s->s12 * s->s33 - s->s13 * s->s23)
	      + s->sy2 *(s->s13 * s->s13 - s->s11 * s->s33)
 	      + s->sy3 *(s->s11 * s->s23 - s->s12 * s->s13))/d;
  param[5] = (  s->sy1 *(s->s13 * s->s22 - s->s12 * s->s23)
	      + s->sy2 *(s->s11 * s->s23 - s->s12 * s->s13)
	      + s->sy3 *(s->s12 * s->s12 - s->s11 * s->s22))/d;
  param[5] -= param[3] * s->x0 + param[4] * s->y0;
  return s->s33;

} /* calctransform_get() */


/**
 * @brief structure that takes a double value and a short int as index
 */
typedef struct {
  double val;
  short idx;
} indexed_double;

/**
 * @brief Qsort comparison function for indexed doubles
 */
static int
tcomp(const void *a, const void *b) {
  if (((indexed_double *) a)->val < ((indexed_double *)b)->val) return 1;
  if (((indexed_double *) a)->val > ((indexed_double *)b)->val) return -1;
  return 0;
}


/**
 * @brief Check that the transformation has the required accuracy
 *
 * @param p1, p2   Coordinates of original and transformed points
 * @param coeff    Transformation coefficients
 * @param accuracy Required accuracy of the transformed point
 */
static int
accuracy_filter(double p1[2], double coeff[], double p2[2],
		double accuracy) {
  double x = p1[0] * coeff[0] + p1[1] * coeff[1] + coeff[2];
  double dx = x - p2[0];
  double y = p1[0] * coeff[3] + p1[1] * coeff[4] + coeff[5];
  double dy = y - p2[1];
  return (dx*dx + dy*dy <= accuracy * accuracy);
}


/**
 * @brief Process a pair of quadruples
 *
 * @param kd_trans Transformation 3d-tree
 * @param patt     Pattern (catalog) matrix: 2 rows, one column per point
 * @param idx_cat  Four point (column) indices in the pattern matrix
 * @param data     Data (observation) point patrix: 2 rows, one col per point
 * @param idx_obs  Four point (column) indices in the data matrix
 * @param coeff    Transformation coefficients (filled on return)
 * @param pos_cut  Required positional accuracy in the catalog
 *
 * @return Number of found matches
 *
 * First, the transformation coefficients are calculated for the four
 * data and pattern points. The coefficients hold a11, a12, c1, a21,
 * a22, c2 in that order in the @em coeff C array. Then, other
 * quadruple pairs that have similar a11, c1, c2 are searched in the
 * tree. For each found pair of quadruples, the transformation is
 * checked against the full transformation (since a12, a21, a22 were
 * ignored there). Then, each point is (after duplicate removal) used
 * for re-calculating the final coefficients, and counted.  Finally,
 * the given transformation is added to the tree, with the pair of
 * point index quadruples as data.
 *
 * For speed improvements, quad pairs with transformations outside of
 * the considered parameter space are not considered. The conditions
 * are
 *
 *      0.9 <= a11,a22 <= 1.1
 *     -0.1 <= a12,a21 <= 0.1
 *
 * This is possible since pattern and data are already in the same
 * range, transformed by the pointing of the telescope, and we only do
 * a fine tuning here.
 */
static int
quadoutput(struct kdtree *kd_trans,
	   cpl_matrix *patt, short idx_cat[], 
	   cpl_matrix *data, short idx_obs[],
	   double coeff[], double pos_cut) {
  int i;
  double trans_cut = 3e-3; /* cut on transformation coefficient accuracy */

  cpl_size n_cat = cpl_matrix_get_ncol(patt);
  const double *x_cat = cpl_matrix_get_data_const(patt);
  const double *y_cat = x_cat + n_cat;
  cpl_size n_obs = cpl_matrix_get_ncol(data);
  const double *x_obs = cpl_matrix_get_data_const(data);
  const double *y_obs = x_obs + n_obs;

  short *quaddata = malloc(sizeof(short)*8);
  calctransform_t *tr = calctransform_init();
  double pt_cat[4][2], pt_obs[4][2];
  for (i=0; i<4; i++) {
    quaddata[i]   = idx_cat[i]; 
    quaddata[i+4] = idx_obs[i];
    pt_cat[i][0] = x_cat[idx_cat[i]];
    pt_cat[i][1] = y_cat[idx_cat[i]];
    pt_obs[i][0] = x_obs[idx_obs[i]];
    pt_obs[i][1] = y_obs[idx_obs[i]];
    calctransform_add(tr, pt_obs[i], pt_cat[i]);
  }

  /* calculate transformation for this quad and check it */
  double param[6]; /* Transformation defined by the quad */
  calctransform_get(tr, param);
  for (i=0; i<4; i++) {
    if (!accuracy_filter(pt_obs[i], param, pt_cat[i], pos_cut)) {
      free(tr);
      free(quaddata);
      return 0;
    }
  }

  /* MUSE specific filter for speed up: we search only in a subset of
     the coefficient space */
  if ((fabs(param[0] - 1) > 0.05) || (fabs(param[1]) > 0.05) ||
      (fabs(param[3]) > 0.05) || (fabs(param[4] - 1) > 0.05)) {
    free(tr);
    free(quaddata);
    return 0;
  }

  cpl_array *matches = cpl_array_new(n_cat, CPL_TYPE_INT);
  /* Get all quads with a similar transformations */
  struct kdres *res;
  double tr_coeff[4] = { param[0], param[2]*trans_cut/pos_cut,
			 param[5]*trans_cut/pos_cut };
  for (res = kd_nearest_range(kd_trans, tr_coeff, trans_cut);
       !kd_res_end(res); kd_res_next(res)) {

    /* get the data and position of the current result item */
    short *cur_data = kd_res_item(res, NULL);
    for (i=0; i<4; i++) {
      pt_cat[i][0] = x_cat[cur_data[i]];
      pt_cat[i][1] = y_cat[cur_data[i]];
      pt_obs[i][0] = x_obs[cur_data[i+4]];
      pt_obs[i][1] = y_obs[cur_data[i+4]];
      if (!accuracy_filter(pt_obs[i], param, pt_cat[i], pos_cut)) {
	break; /* Check that the points match to the original transformation */
      }
      if (!cpl_array_is_valid(matches, cur_data[i])) { // ignore duplicates
	cpl_array_set(matches, cur_data[i], cur_data[i+4]);
	calctransform_add(tr, pt_obs[i], pt_cat[i]);
      }
    }
  }
  int n_matches = n_cat - cpl_array_count_invalid(matches);
  cpl_array_delete(matches);
  kd_res_free(res);
  calctransform_get(tr, coeff);
  free(tr);
  kd_insert(kd_trans, tr_coeff, quaddata);
  return n_matches;

} /* quadoutput() */


/**
 * @brief Refine the transformation and the match data and catalog
 *
 * @param kd_cat  2d-tree with catalog (pattern) positions
 * @param data    Observed positions: 2 rows, one column per position
 * @param coeff   Transformation coefficients. coeff[2] and coeff[5] are the 
 *                shifts. Values are refined from the all matches
 * @param pos_cut Required positional accuracy
 *
 * @return Array with matching indices (catalogue->data)
 *
 * For each data point, the closest matching reference point (within
 * the given accuracy) is searched and returned in the array.
 */
static cpl_array*
get_crossmatches(struct kdtree *kd_cat, cpl_matrix *data,
		 double coeff[], double pos_cut) {
  struct kdres *res;

  cpl_size n_obs = cpl_matrix_get_ncol(data);
  const double *x_obs = cpl_matrix_get_data_const(data);
  const double *y_obs = x_obs + n_obs;

  int n_matches = 0;
  cpl_array *matches = cpl_array_new(1, CPL_TYPE_INT);

  calctransform_t *tr = calctransform_init();

  cpl_msg_debug(__func__, "Starting crossmatch with %fx%+fy%+f",
		coeff[0], coeff[1], coeff[2]);
  cpl_msg_debug(__func__, "                         %fx%+fy%+f",
		coeff[3], coeff[4], coeff[5]);

  int idx_obs;
  for (idx_obs = 0; idx_obs < n_obs; idx_obs++) {
    double pos[2] = {
      x_obs[idx_obs] * coeff[0] + y_obs[idx_obs] * coeff[1] + coeff[2],
      x_obs[idx_obs] * coeff[3] + y_obs[idx_obs] * coeff[4] + coeff[5]
    };
    double best_dist = pos_cut * pos_cut;
    short idx_cat = -1;
    double refpos[2];
    /* get the nearest point from all that are close */
    for (res = kd_nearest_range(kd_cat, pos, pos_cut);
	 !kd_res_end(res); kd_res_next(res)) {
      short *kdata = kd_res_item(res, refpos);
      double dx = pos[0] - refpos[0];
      double dy = pos[1] - refpos[1];
      if (dx*dx + dy*dy < best_dist) {
	best_dist = dx*dx+dy*dy;
	idx_cat = *kdata;
      }
    }
    if (idx_cat != -1) {
      double pt_obs[2] = { x_obs[idx_obs], y_obs[idx_obs] };
      calctransform_add(tr, pt_obs, refpos);
      cpl_msg_debug(__func__, "Match: cat#%i --> obs#%i (distance %g)",
		    idx_cat, idx_obs, sqrt(best_dist));
      if (cpl_array_get_size(matches) <= idx_cat) {
	cpl_array_set_size(matches, idx_cat + 1); // extend array if needed
      }
      cpl_array_set_int(matches, idx_cat, idx_obs);
      n_matches++;
    }
    kd_res_free(res);
  }
  calctransform_get(tr, coeff);
  free(tr);
  cpl_msg_debug(__func__, "Full crossmatch returned %i matches (of %i observed objects)", n_matches, (int)n_obs);
  return matches;

} /* get_crossmatches() */


/**
 * @brief Return the (signed) Triangle area between three points
 *
 * @param p1, p2, p2 Triangle point positions
 * @return triangle area
 */
static double
triangle_area(double p1[], double p2[], double p3[]) {
  return (p1[0] - p2[0]) * (p2[1] - p3[1])
    -(p2[0] - p3[0]) * (p1[1] - p2[1]);
}


/**
 * @brief Return the square of the distance between two points
 *
 * @param p1, p2 Point positions
 * @return Square of the distance
 */
static double
sqr_distance(double p1[], double p2[]) {
  return (p1[0] - p2[0]) * (p1[0] - p2[0])
    + (p1[1] - p2[1]) * (p1[1] - p2[1]);
}


/**
 * @brief Ensure some shape of a pattern
 *
 * @param n       Number of points
 * @param aPoint  Point coordinates
 * @param aRatio  Maximal distance ratio
 *
 * This function ensures that the ratio between the smallest and the
 * largest distance between any two points is smaller than the given
 * ratio.
 */
static int
ensure_shape(int n, double aPoint[][2], double aRatio) {
  int i, j;
  double d2 = sqr_distance(aPoint[0], aPoint[1]);
  double mind2 = d2; 
  double maxd2 = d2;
  for (i = 2; i < n; i++) {
    for (j = 0; j < i; j++) {
      d2 = sqr_distance(aPoint[i], aPoint[j]);
      if (mind2 > d2) mind2 = d2;
      if (maxd2 < d2) maxd2 = d2;
    }
  }
  return (mind2 * aRatio * aRatio >= maxd2);
}


/**
 * @brief Calculate the area ratio for a quadruple
 *
 * @param aPoint Array with four point coordinated (double[4][2])
 * @param aIdx   Array with four point indices (will be sorted)
 * @param aRatio Array that gets filled with the ratios of the
 *               2nd and 3rd aread to the largest one 
 *
 * Calculate the area ratios and store them (2 values) in ratio.
 * Also sort the indices by the area, starting with the largest.
 */
static void
calc_arearatio(double (*aPoint)[2], short aIdx[], double aRatio[]) {
  indexed_double la[4] = {
    { triangle_area(aPoint[0], aPoint[1], aPoint[2]), aIdx[3] },
    { triangle_area(aPoint[1], aPoint[2], aPoint[3]), aIdx[0] },
    { triangle_area(aPoint[2], aPoint[3], aPoint[0]), aIdx[1] },
    { triangle_area(aPoint[3], aPoint[0], aPoint[1]), aIdx[2] }
  };

  int i;
  int sign = 1;

  /* Check area sign and replace area by its absolute value */
  for (i = 0; i < 4; i++) {
    if (la[i].val < 0) {
      sign *= -1;
      la[i].val = -la[i].val;
    }
  }
  /* Sign is now positive if the quadruple is convex and negative if
     it is concave */

  /* sort them */
  qsort(la, 4, sizeof(indexed_double), tcomp);

  /* calculate the area ratio for the 2nd biggest to biggest */
  /* and 3rd biggest to biggest, the final ratio is determined */
  /* by the other two */
  aRatio[0] = la[1].val / la[0].val * sign;
  aRatio[1] = la[2].val / la[0].val * sign;

  /* store the indices back */
  aIdx[0] = la[0].idx;
  aIdx[1] = la[1].idx;
  aIdx[2] = la[2].idx;
  aIdx[3] = la[3].idx;

  return;
  
} /* calc_arearatio() */


/**
 * @brief Create the 2d-tree with positions of all points.
 *
 * @param aMatrix    Point positions: 2 rows with one coordinate per column
 *
 * @return Two dimensional kd-tree with the a short of the point index.
 *
 * Loop over all points in the matrix and store the positions in a 2d-tree.
 * The data holds the point index.
 */
static struct kdtree *
create_position_tree(cpl_matrix *aMatrix) {

  struct kdtree *kd_cat = kd_create(2);
  kd_data_destructor(kd_cat, free);

  cpl_size icol;
  cpl_size ncol = cpl_matrix_get_ncol(aMatrix);
  for (icol = 0; icol < ncol; icol++) {
    short *kdata = malloc(sizeof(short));
    kdata[0] = icol;
    double pos[2] = {
      cpl_matrix_get(aMatrix, 0, icol),
      cpl_matrix_get(aMatrix, 1, icol),
    };
    kd_insert(kd_cat, pos, kdata);
  }

  return kd_cat;

} /* create_position_tree() */


/**
 * @brief Create the area 2d-tree (2nd and 3rd area ratios) for all quads
 *
 * @param aMatrix   Point positions: 2 rows with one coordinate per column
 *
 * @return Two dimensional kd-tree with the a short[4] array of point
 *         indices in the matrix (sorted by triangle area).
 *
 * Loop over all point quadruples, create all four triangle areas,
 * sort them by size, and store the ratio of the 2nd and 3rd are to
 * the largest one one in a 2d-tree. These two ratios are
 * transformation invariant and therefore describe the pattern built
 * by these for points.
 *
 */
static struct kdtree *
create_area_tree(cpl_matrix *aMatrix, cpl_size n_max) {

  struct kdtree *kd_area = kd_create(2);
  kd_data_destructor(kd_area, free);

  cpl_size i, j, k, l;
  double pt[4][2];
  const double *x = cpl_matrix_get_data_const(aMatrix);
  const double *y = x + cpl_matrix_get_ncol(aMatrix);
  /* build the tree */
  for (i = 3; i < n_max; i++) {
    pt[0][0] = x[i];
    pt[0][1] = y[i];
    for (j = 2; j < i; j++) {
      pt[1][0] = x[j];
      pt[1][1] = y[j];
      for (k = 1; k < j; k++) {
	pt[2][0] = x[k];
	pt[2][1] = y[k];
	if (!ensure_shape(3, pt, 10)) {
	  continue;
	}
	for (l = 0; l < k; l++) {
	  pt[3][0] = x[l];
	  pt[3][1] = y[l];
	  /* calculate longest and shortest distances -- they should
	     not exceed a certain ratio */
	  if (!ensure_shape(4, pt, 10)) {
	    continue;
	  }

	  double ratioarray[2];
	  /* allocate an array to hold the point indices */
	  short *idx = malloc(sizeof(short) * 4);
	  idx[0]=i; idx[1]=j; idx[2]=k; idx[3]=l;

	  /* calculate triangle area ratios */
	  calc_arearatio(pt, idx, ratioarray);

	  /* add it to the tree */
	  kd_insert(kd_area, ratioarray, idx);
	}
      }
    }
  }
  return kd_area;

} /* create_area_tree() */


/**
 * @brief Match 2D distribution of points using kdtree
 *
 * @param data        List of data points (e.g., detected stars positions).
 * @param use_data    Number of data points used for primary pattern matching.
 * @param pattern     List of pattern points (e.g., expected stars positions).
 * @param use_pattern Number of pattern points used for primary pattern matching.
 * @param dist_cut    Required accuracy of quad area ratios
 * @param pos_cut     Required positional accuracy
 * @param n_matches   Required number of matches to break
 *
 * @return Indexes of identified data points (pattern-to-data).
 *
 * The API is slightly modelled after cpl_ppm_match_points().
 *
 * A point is described here by its coordinates on a cartesian plane.
 * The input matrices @em data and @em pattern must have 2 rows, as 
 * their column vectors are the points coordinates.
 *
 * This function attemps to associate points in @em data to points in
 * @em pattern, under the assumption that a linear transformation
 * would convert positions in @em pattern into positions in @em
 * data. Association between points is also indicated in the following
 * as "match", or "identification".
 *
 * The Point identification is performed in two steps. In the first
 * step, for all quadrupels of points in the observed data, their
 * transformation invariant properties (area ratios) are stored in a
 * 2d-tree. The cost of this step is O(n_data^4). Therefore it is
 * recommended to keep the number of data points below ~150.
 *
 * In a second step, the same transformation invariant properties are
 * calculated by iterating over the quadruples build from the pattern
 * points, and matching quadruples in the data point quadruples are
 * searched in the 2d-tree built in the first step. If such a
 * quadruple is found, the transformation matrix is calculated.
 *
 * In the list of found transformation matrizes (a 3d-tree), similar
 * transformation are searched then. If there are more than four of
 * them (and more than we found previously), then a transformation of
 * all data points with the given matrix is done, and tried to match
 * to pattern points using a 2d-tree. If more than @em n_matches are
 * found, the list of matched point is returned.
 *
 * Note that the potential cost (memory and time) of the second step
 * is O(n_pattern^4), so it may be useful to limit the length of the
 * pattern list as well. The function will however stop when enough
 * matching points were found. Since the quadruples are built starting
 * from the first points, it is useful to sort the pattern by
 * flux (highest flux first).
 *
 * Both @em data and @em pattern matrices are row-wise normalized by
 * the function.
 * 
 */
cpl_array *
muse_ppm_kdmatch_points(cpl_matrix *data, cpl_size use_data,
			cpl_matrix *pattern, cpl_size use_pattern,
			double dist_cut, double pos_cut, int n_matches) {

  int i, j, k, l;
  struct kdres *res;
  int nbest = 4;
  double coeff[6];

  cpl_msg_info(__func__, "matching %"CPL_SIZE_FORMAT
	       " observed position with a catalog of %"CPL_SIZE_FORMAT
	       " positions",
	       cpl_matrix_get_ncol(data), cpl_matrix_get_ncol(pattern));
  if (use_data > cpl_matrix_get_ncol(data)) {
    use_data = cpl_matrix_get_ncol(data);
  }
  if (use_pattern > cpl_matrix_get_ncol(pattern)) {
    use_pattern = cpl_matrix_get_ncol(pattern);
  }

  /* Create the catalogue tree (x and y of all catalogue points) */
  struct kdtree *kd_cat = create_position_tree(pattern);

  /* create the area kd-tree (2nd and 3rd area ratios) from the observations */
  struct kdtree *kd_area = create_area_tree(data, use_data);

  /* go through the quads from the catalogue */
  /* create the kd-tree for the matches (first three coefficients for
     transformation) */
  struct kdtree *kd_trans = kd_create(3);
  kd_data_destructor(kd_trans, free);
  double pt_cat[4][2];
  const double *x_cat = cpl_matrix_get_data_const(pattern);
  const double *y_cat = x_cat + cpl_matrix_get_ncol(pattern);
  for (i = 3; i < use_pattern; i++) {
    pt_cat[0][0] = x_cat[i];
    pt_cat[0][1] = y_cat[i];
    cpl_msg_debug(__func__, "processing catalog indices <= %i", i);
    for (j = 2; j < i; j++) {
      pt_cat[1][0] = x_cat[j];
      pt_cat[1][1] = y_cat[j];
      for (k = 1; k < j; k++) {
	pt_cat[2][0] = x_cat[k];
	pt_cat[2][1] = y_cat[k];
	if (!ensure_shape(3, pt_cat, 10)) {
	  continue;
	}
	for (l = 0; l < k; l++) {
	  pt_cat[3][0] = x_cat[l];
	  pt_cat[3][1] = y_cat[l];
	  if (!ensure_shape(4, pt_cat, 10)) {
	    continue;
	  }

	  double ratioarray[2];
	  short idx_cat[4] = {i, j, k, l};
	  calc_arearatio(pt_cat, idx_cat, ratioarray);
	  
	  /* find all the quads from the first list (observations) that are
	     within the dist_cut */
	  for (res = kd_nearest_range(kd_area, ratioarray, dist_cut);
	       !kd_res_end(res);
	       kd_res_next(res)) {
	    /* get the data and position of the current result item */
	    short *idx_obs = kd_res_item(res, NULL);
	    int cur_matches = quadoutput(kd_trans, 
					 pattern, idx_cat,
					 data, idx_obs,
					 coeff, pos_cut);
	    if (cur_matches >= nbest) {
	      nbest = cur_matches;
	      cpl_array *m = get_crossmatches(kd_cat, data, coeff, pos_cut);
	      cur_matches = cpl_array_get_size(m) - cpl_array_count_invalid(m);
	      cpl_array_delete(m);
	    }
	    if (cur_matches >= n_matches) {
	      cpl_msg_info(__func__, "Found enough matches: %i", cur_matches);
	      i=j=k=l=use_pattern; /* finish all loops */
	      break;
	    }
	  }
	  /* free the results structure */
	  kd_res_free(res);
	}
      }
    }
  }

  cpl_array *matches = get_crossmatches(kd_cat, data, coeff, pos_cut);
  kd_free(kd_trans);
  kd_free(kd_area);
  kd_free(kd_cat);
  return matches;

} /* muse_ppm_kdmatch_points() */
