/* $Id: fors_identify.c,v 1.58 2013-09-10 15:14:44 cgarcia Exp $
 *
 * This file is part of the FORS Library
 * Copyright (C) 2002-2010 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013-09-10 15:14:44 $
 * $Revision: 1.58 $
 * $Name: not supported by cvs2svn $
 */

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

#include <fors_identify.h>

#include <fors_image.h>
#include <fors_pattern.h>
#include <fors_point.h>
#include <fors_dfs.h>
#include <fors_utils.h>
#include <fors_double.h>

#include <cpl.h>

#include <math.h>
#include <assert.h>

/**
 * @defgroup fors_identify  Source identification
 */

/**@{*/

struct _identify_method
{
    int ncat;
    double nsource;
    double kappa;
    double search;
    double max_search;
    double max_offset;
};

static bool
inside_region(const fors_std_star *std,
              void *reg);

static void
match_patterns(const fors_star_list *stars, 
               const fors_std_star_list *std,
               double kappa,
               double *sx_00,
               double *sy_00,
               double *med_scale,
               double *med_angle,
               int    *status);

/**
 * @brief    Define recipe parameters
 * @param    parameters     parameter list to fill
 * @param    context        parameters context
 */
void 
fors_identify_define_parameters(cpl_parameterlist *parameters, 
                                const char *context)
{
    cpl_parameter *p;
    const char *full_name = NULL;
    const char *name;

/* Restore if pattern matching needs to be restored

    name = "ncat";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_INT,
                                "Number of catalog stars to use in "
                                "pattern matching",
                                context,
                                10);
                                         
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);


    name = "nsource";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_DOUBLE,
                                "Number of sources to use in "
                                "pattern matching, pr. catalog star",
                                context,
                                15.0);
                                         
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);

    name = "kappa";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_DOUBLE,
                                "Rejection parameter (scale, angle) used "
                                "in pattern matching ",
                                context,
                                5.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);

End of restoring if pattern matching has to be restored */
    
/*

    name = "search";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_DOUBLE,
                                "Search radius (pixels)",
                                context,
                                5.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);
    
    name = "maxsearch";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_DOUBLE,
                                "Maximum search radius (pixels)",
                                context,
                                20.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);

*/

    name = "maxoffset";
    full_name = cpl_sprintf("%s.%s", context, name);
    p = cpl_parameter_new_value(full_name,
                                CPL_TYPE_DOUBLE,
    "Maximum acceptable offset between the image and catalogue WCS (pixels)",
                                context,
                                150.0);
    cpl_parameter_set_alias(p, CPL_PARAMETER_MODE_CLI, name);
    cpl_parameter_disable(p, CPL_PARAMETER_MODE_ENV);
    cpl_parameterlist_append(parameters, p);
    cpl_free((void *)full_name);
    
    return;
}

#undef cleanup
#define cleanup \
do { \
    cpl_free((void *)name); \
} while (0)
/**
 * @brief    Get id method from parameter list
 * @param    parameters     recipe parameter list
 * @param    context        read id method from this context
 * @return   newly allocated id method
 *
 * The parameter list should have been previously created using
 * fors_identify_define_parameters()
 */
identify_method *
fors_identify_method_new(const cpl_parameterlist *parameters, const char *context)
{
    identify_method *im = cpl_malloc(sizeof(*im));
    const char *name = NULL;

    cpl_msg_info(cpl_func, "Identification parameters:");
       
/* Restore if pattern matching to be restored

    cpl_msg_indent_more();
    name = cpl_sprintf("%s.%s", context, "ncat");
    im->ncat = dfs_get_parameter_int_const(parameters, name);
    cpl_free((void *)name); name = NULL;
    cpl_msg_indent_less();

       
    cpl_msg_indent_more();
    name = cpl_sprintf("%s.%s", context, "nsource");
    im->nsource = dfs_get_parameter_double_const(parameters, name);
    cpl_free((void *)name); name = NULL;
    cpl_msg_indent_less();

       
    cpl_msg_indent_more();
    name = cpl_sprintf("%s.%s", context, "kappa");
    im->kappa = dfs_get_parameter_double_const(parameters, name);
    cpl_free((void *)name); name = NULL;
    cpl_msg_indent_less();

End of restore if pattern matching to be restored */
    

    // cpl_msg_indent_more();
    // name = cpl_sprintf("%s.%s", context, "search");
    im->search = 5.0; // dfs_get_parameter_double_const(parameters, name);
    // cpl_free((void *)name); name = NULL;
    // cpl_msg_indent_less();
    
   
    // cpl_msg_indent_more();
    // name = cpl_sprintf("%s.%s", context, "maxsearch");
    // name = cpl_sprintf("%s.%s", context, "search"); // Same value for max search
    im->max_search = 5.0; // dfs_get_parameter_double_const(parameters, name);
    // cpl_free((void *)name); name = NULL;
    // cpl_msg_indent_less();


    cpl_msg_indent_more();
    name = cpl_sprintf("%s.%s", context, "maxoffset");
    im->max_offset = dfs_get_parameter_double_const(parameters, name);
    cpl_free((void *)name); name = NULL;
    cpl_msg_indent_less();
    

    assure( !cpl_error_get_code(), return NULL, NULL );
    
    return im;
}

/**
 * @brief    Deallocate identifyion method and set the pointer to NULL
 */
void
fors_identify_method_delete(identify_method **em)
{
    if (em && *em) {
        cpl_free(*em); *em = NULL;
    }
    return;
}

/**
 * @brief   Compare brightness
 * @param   s1     1st star
 * @param   s2     2nd star
 * @return true if s1 is brighter than s2
 * 
 * This function is the same as fors_std_star_brighter_than() but with the
 * interface expected by list_extract()
 */
static bool
std_brighter_than(const fors_std_star *s1,
                  void *s2)
{
    return fors_std_star_brighter_than(s1, s2, NULL);
}

/**
 * @brief   Compare brightness
 * @param   s1     1st star
 * @param   s2     2nd star
 * @return true if s1 is brighter than s2
 * 
 * This function is the same as fors_star_brighter_than() but with the
 * interface expected by list_extract()
 */
static bool
star_brighter_than(const fors_star *s1,
                   void *s2)
{
    return fors_star_brighter_than(s1, s2, NULL);
}

/**
 * @brief   Distance between source and shifted catalog star
 * @param   s        source
 * @param   std      catalog star
 * @param   shiftx   shift applied to std
 * @param   shifty   shift applied to std
 * @return squared distance between source and shifted catalog star
 */
static double
distsq_shift(const fors_star *s,
             const fors_std_star *std,
             double shiftx,
             double shifty)
{
    fors_point *shifted_pos = fors_point_new(std->pixel->x + shiftx,
                                             std->pixel->y + shifty);
    
    double result = fors_point_distsq(s->pixel, shifted_pos);
    
    fors_point_delete(&shifted_pos);
    
    return result;
}

/**
 * @brief   Tell if a source is closest to a catalog star
 * @param   s1     1st source
 * @param   s2     2nd source
 * @param   d      radius, shift, catalog star
 * @return true s1 is closer than s2 to reference, after shifting the
 *         reference star
 */
static bool
star_nearer(const fors_star *s1,
            const fors_star *s2,
            void *data) 
{
    struct {
        double shift_x, shift_y;
        const fors_std_star *ref;
    } *d = data;
    /* Cast is safe, see caller */
    
    return 
        distsq_shift(s1, d->ref, d->shift_x, d->shift_y) <
        distsq_shift(s2, d->ref, d->shift_x, d->shift_y);
}


#undef cleanup
#define cleanup \
do { \
    fors_std_star_list_delete(&std_ccd       , fors_std_star_delete); \
    fors_std_star_list_delete(&std_ccd_bright, fors_std_star_delete); \
    fors_star_list_delete(&source_bright, fors_star_delete); \
} while (0)
/**
 * @brief   Identify sources
 * @param   stars           list of stars to identify
 * @param   cat             list of catalog stars
 * @param   im              parameters
 *
 * pseudocode:
 *    Select catalog stars, 10 brightest stars inside CCD
 *    Select source stars, brightest 2 x catalog stars
 *    Compute reference patterns
 *    Compute source patterns
 *    For each reference pattern
 *        Identify with nearest source, compute scale, orientation
 *    Reject outliers, get average shift
 *
 *    For each catalog star
 *        Apply shift
 *        Identify with nearest source inside search radius
 */
void
fors_identify(fors_star_list *stars, 
              fors_std_star_list *cat,
              const identify_method *im,
              cpl_image **histogram)
{
    fors_std_star_list *std_ccd = NULL;  /* Subset of catalog stars
                                            which are inside the CCD */

    fors_std_star_list *std_ccd_bright = NULL; /* Brightest std stars */

    fors_star_list *source_bright  = NULL;     /* Subset of brightest stars */

    double offset = 0.0;
    double sx_00, sy_00;
    int status;

    if (histogram)
        *histogram = NULL;

    assure( stars != NULL, return, NULL );

    cpl_msg_info(cpl_func, "Identifying sources");
    cpl_msg_indent_more();


    /*
      fors_star_print_list(CPL_MSG_ERROR, stars);
      fors_std_star_print_list(CPL_MSG_ERROR, cat); 
     */

    cpl_msg_info(cpl_func, "Pattern matching");
    cpl_msg_indent_more();

    /* Select standards inside CCD */
    {
        double tolerance = 100; /* pixels */
        struct {
            int xlo, ylo;
            int xhi, yhi;
        } region;
        if (fors_star_list_size(stars) > 0) {
            region.xlo = fors_star_get_x(fors_star_list_min_val(stars,
                    fors_star_get_x,
                    NULL), NULL) - tolerance;
            region.ylo = fors_star_get_y(fors_star_list_min_val(stars,
                    fors_star_get_y,
                    NULL), NULL) - tolerance;
            region.xhi = fors_star_get_x(fors_star_list_max_val(stars,
                    fors_star_get_x,
                    NULL), NULL) + tolerance;
            region.yhi = fors_star_get_y(fors_star_list_max_val(stars,
                    fors_star_get_y,
                    NULL), NULL) + tolerance;

        } else {
            region.xlo = 1;
            region.ylo = 1;
            region.xhi = 1000; /* Anything can go here, not used */
            region.yhi = 1000;
        }
        cpl_msg_debug(cpl_func, "Search region = (%d, %d) - (%d, %d)",
                      region.xlo, region.ylo,
                      region.xhi, region.yhi);

        std_ccd = fors_std_star_list_extract(
                cat, fors_std_star_duplicate, inside_region, &region);

        int multiple_entries = 0;

        if (fors_std_star_list_size(std_ccd) > 1) {
            bool found_double;
            do {
                found_double = false;

                /* Searching for the nearest neighbour will permute the list,
                   do not do that while iterating the same list */
                fors_std_star_list *tmp = 
                        fors_std_star_list_duplicate(std_ccd,
                                fors_std_star_duplicate);

                cpl_msg_debug(cpl_func, "%d stars left", fors_std_star_list_size(tmp));

                fors_std_star *std;

                for (std = fors_std_star_list_first(tmp);
                        std != NULL && !found_double;
                        std = fors_std_star_list_next(tmp)) {

                    fors_std_star *self = fors_std_star_list_kth_val(
                            std_ccd, 1,
                            (fors_std_star_list_func_eval)
                            fors_std_star_dist_arcsec,
                            std);

                    fors_std_star *nn = fors_std_star_list_kth_val(
                            std_ccd, 2,
                            (fors_std_star_list_func_eval)
                            fors_std_star_dist_arcsec,
                            std);

                    double min_dist = fors_std_star_dist_arcsec(std, nn);

                    cpl_msg_debug(cpl_func, "dist = %f arcseconds", min_dist);

                    /* If very close, remove the one with the largest magnitude
                       error. 

                       Do not try to combine the two magnitudes because
                       those estimates may or may not be correlated 
                     */
                    if (min_dist < 5) {
                        multiple_entries += 1;

                        if (std->dmagnitude > nn->dmagnitude) {
                            fors_std_star_list_remove(std_ccd, self);
                            fors_std_star_delete(&self);
                        } else {
                            fors_std_star_list_remove(std_ccd, nn);
                            fors_std_star_delete(&nn);
                        }
                        found_double = true;
                    }
                }

                fors_std_star_list_delete(&tmp,
                                          fors_std_star_delete);

            } while (found_double);
        }

        cpl_msg_info(cpl_func, 
                     "%d catalog star%s are expected inside detector, "
                     "ignored %d repeated source%s",
                     fors_std_star_list_size(std_ccd),
                     fors_std_star_list_size(std_ccd) == 1 ? "" : "s",
                     multiple_entries,
                     multiple_entries == 1 ? "" : "s");
    }


    if (0) {
        /* Select brightest std */
        if (fors_std_star_list_size(std_ccd) <= im->ncat) {
            std_ccd_bright = fors_std_star_list_duplicate(std_ccd,
                    fors_std_star_duplicate);
        }
        else {
            fors_std_star *kth =
                    fors_std_star_list_kth(std_ccd,
                            im->ncat + 1,
                            fors_std_star_brighter_than, NULL);

            //fors_std_star_print_list(std_ccd);

            std_ccd_bright = fors_std_star_list_extract(
                    std_ccd, fors_std_star_duplicate, 
                    std_brighter_than, kth);
        }

        if (fors_std_star_list_size(std_ccd_bright) < 3) {

            cpl_msg_warning(cpl_func, 
                    "Too few catalog stars (%d) available for pattern "
                    "matching, assuming FITS header WCS solution",
                    fors_std_star_list_size(std_ccd_bright));

            sx_00 = 0;
            sy_00 = 0;
        }
        else {

            double med_scale, med_angle;

            cpl_msg_info(cpl_func, "Using %d brightest standards",
                         fors_std_star_list_size(std_ccd_bright));

            fors_std_star_print_list(CPL_MSG_DEBUG, std_ccd_bright);

            /* Select sources */
            int n_sources = 
                    (int) (fors_std_star_list_size(std_ccd_bright)*im->nsource + 0.5);

            if (fors_star_list_size(stars) <= n_sources) {
                source_bright = fors_star_list_duplicate(stars,
                        fors_star_duplicate);
            }
            else {
                fors_star *kth =
                        fors_star_list_kth(stars,
                                n_sources + 1,
                                fors_star_brighter_than, NULL);

                source_bright = fors_star_list_extract(
                        stars, fors_star_duplicate,
                        star_brighter_than, kth);
            }

            cpl_msg_info(cpl_func, "Using %d brightest sources", 
                         fors_star_list_size(source_bright));
            fors_star_print_list(CPL_MSG_DEBUG, source_bright);


            status = 0;
            match_patterns(source_bright, std_ccd_bright,
                           im->kappa,
                           &sx_00, &sy_00, &med_scale, &med_angle, &status);

            assure( !cpl_error_get_code(), return, "Pattern matching failed" );


            if (status) {
                cpl_msg_warning(cpl_func, 
                        "BAD pattern matching solution rejected.");

                if (med_scale > 1.1 || med_scale < 0.9) {
                    cpl_msg_warning(cpl_func, "Unexpected scale from pattern "
                            "matching (expected 1.0): assuming FITS header WCS solution");
                }

                offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);

                if (offset > im->max_offset) {
                    cpl_msg_warning(cpl_func, "Pattern matching identifications "
                            "are more than %.2f pixel off their expected positions (max "
                            "allowed was: %.2f). Pattern matching solution is rejected, "
                            "FITS header WCS solution is used instead!", offset, 
                            im->max_offset);
                }

                sx_00 = 0;
                sy_00 = 0;
            }
        }
        cpl_msg_indent_less();

        cpl_msg_info(cpl_func, 
                     "Average shift from pattern matching = (%.2f, %.2f) pixels", 
                     sx_00, sy_00);
    }

    /*
     * Begin here alternative algorithm to pattern matching
     */

    double               search_radius = im->max_offset;
    const fors_std_star *catalog_star;
    const fors_star     *ccd_star;
    float                dx, dy;
    int                  npix = (2 * search_radius + 1);
    cpl_image           *histo = cpl_image_new(npix, npix, CPL_TYPE_INT);
    int                 *dhisto = cpl_image_get_data(histo);
    cpl_size             xpos, ypos;
    int                  i, rebin, max;
    int                  found = 0;

    for (catalog_star = fors_std_star_list_first_const(std_ccd);
            catalog_star != NULL;
            catalog_star = fors_std_star_list_next_const(std_ccd)) {

        for (ccd_star = fors_star_list_first_const(stars);
                ccd_star != NULL;
                ccd_star = fors_star_list_next_const(stars)) {

            dx = catalog_star->pixel->x - ccd_star->pixel->x;
            dy = catalog_star->pixel->y - ccd_star->pixel->y;

            if (fabs(dx) < search_radius) {
                if (fabs(dy) < search_radius) {
                    xpos = search_radius + floor(dx + 0.5);
                    ypos = search_radius + floor(dy + 0.5);
                    ++dhisto[xpos + ypos * npix];
                }
            }
        }
    }

    max = 0;
    for (i = 0; i < npix * npix; i++) {
        if (max < dhisto[i]) {
            max = dhisto[i];
        }
    }

    if (max == 0) {
        cpl_msg_warning(cpl_func, 
                "WCS offset determination failed.");
        sx_00 = 0.0;
        sy_00 = 0.0;
    }
    else {
        rebin = 0;
        for (i = 0; i < npix * npix; i++) {
            if (max == dhisto[i]) {
                if (found) {
                    cpl_image *rebinned;
                    found = 2;
                    rebin = 1;
                    cpl_msg_warning(cpl_func,
                                    "More than one WCS offset found, try rebinning...");
                    rebinned = cpl_image_rebin(histo, 1, 1, 3, 3);
                    cpl_image_delete(histo);
                    histo = rebinned;
                    dhisto = cpl_image_get_data(histo);
                    npix = cpl_image_get_size_x(histo);
                    search_radius /= 3;
                    break;
                }
                else {
                    found = 1;
                }
            }
        }

        if (found > 1) {
            found = max = 0;
            for (i = 0; i < npix * npix; i++) {
                if (max < dhisto[i]) {
                    max = dhisto[i];
                }
            }

            for (i = 0; i < npix * npix; i++) {
                if (max == dhisto[i]) {
                    if (found) {

                        /*
                         * Check if connected to previously found maxima
                         */

                        int connected = 0;

                        if (i > 0) {
                            if (max == dhisto[i-1]) {
                                connected = 1;
                            }
                        }
                        if (i > npix) {
                            if (max == dhisto[i-npix-1] || 
                                    max == dhisto[i-npix]   ||
                                    max == dhisto[i-npix+1]) {
                                connected = 1;
                            }
                        }

                        if (!connected) {
                            found = 2;
                            cpl_msg_warning(cpl_func,
                                            "WCS offset determination failed.");
                            sx_00 = 0.0;
                            sy_00 = 0.0;
                            break;
                        }
                    }
                    else {
                        found = 1;
                    }
                }
            }
        }
    }

    if (found == 1) {
        cpl_image_get_maxpos(histo, &xpos, &ypos);
        xpos--;
        ypos--;

        /*
         * Refine peak position
         */

        float xmean = 0.0;
        float ymean = 0.0;
        int   i, j, count = 0;

        for (i = xpos - 1; i <= xpos; i++) {
            for (j = ypos - 1; j <= ypos; j++) {
                if (i >= 0 && i < npix) {
                    if (j >= 0 && j < npix) {
                        xmean += i * dhisto[i + j*npix];
                        ymean += j * dhisto[i + j*npix];
                        count += dhisto[i + j*npix];
                    }
                }
            }
        }

        sx_00 = search_radius - xmean / count - 0.5;
        sy_00 = search_radius - ymean / count - 0.5;

        if (rebin) {
            sx_00 *= 3;
            sy_00 *= 3;
        }

        /*
        cpl_msg_info(cpl_func, 
                 "Average shift from histogram method = (%.2f, %.2f) pixels", 
                 sx_00, sy_00);
         */

        //        cpl_image_save(histo, "histogram.fits", CPL_BPP_32_SIGNED, NULL,
        //                       CPL_IO_DEFAULT);
        //
        //        cpl_image_delete(histo);
    }

    if (histogram) 
        *histogram = histo;
    else
        cpl_image_delete(histo);

    offset = sqrt(sx_00 * sx_00 + sy_00 * sy_00);

    if (offset > im->max_offset) {
        cpl_msg_warning(cpl_func, "Offset with respect to WCS is %.2f pixel "
                "(max allowed was: %.2f). This offset is rejected.",
                offset, im->max_offset);

        sx_00 = 0;
        sy_00 = 0;
    }

    /*
     * End here alternative algorithm to pattern matching
     */

    /* Finally, make the identification if a unique source is found 
       within the corrected position search radius.

       Use all catalog stars (inside CCD).
     */

    if (sx_00 == 0.0 && sy_00 == 0.0) {
        cpl_msg_warning(cpl_func, "No standard star could be identified.");
        cpl_msg_indent_less();
        cleanup;
        return;
    }

    int number_of_ids = 0;
    bool require_unique = true;
    search_radius = im->search;

    while (number_of_ids == 0 && search_radius <= im->max_search) {

        cpl_msg_info(cpl_func, "Identification");

        cpl_msg_indent_more();
        cpl_msg_info(cpl_func, "Average shift with WCS = (%.2f, %.2f) pixels",
                     sx_00,
                     sy_00);

        if (fabs(sx_00) > 10.0) {
            cpl_msg_warning(cpl_func, "Large x-shift");
        }
        if (fabs(sy_00) > 10.0) {
            cpl_msg_warning(cpl_func, "Large y-shift");
        }

        cpl_msg_info(cpl_func, "search_radius = %.2f pixels", search_radius);

        struct {
            double shift_x, shift_y;
            const fors_std_star *ref;
        } data;

        data.shift_x = sx_00;
        data.shift_y = sy_00;

        if (fors_star_list_size(stars) > 0) {
            for (data.ref = fors_std_star_list_first_const(std_ccd);
                    data.ref != NULL;
                    data.ref = fors_std_star_list_next_const(std_ccd)) {

                fors_star *nearest_1 = fors_star_list_kth(stars,
                        1,
                        star_nearer,
                        &data);

                if (fors_star_list_size(stars) > 1) {

                    fors_star *nearest_2 = fors_star_list_kth(stars,
                            2,
                            star_nearer,
                            &data);

                    cpl_msg_debug(cpl_func, "Nearest candidates at "
                                  "distance %f and %f pixels",
                                  sqrt(distsq_shift(nearest_1, data.ref, 
                                          data.shift_x, 
                                          data.shift_y)),
                                          sqrt(distsq_shift(nearest_2, data.ref, 
                                                  data.shift_x, 
                                                  data.shift_y)));

                    if (distsq_shift(nearest_1, data.ref, 
                            data.shift_x, data.shift_y) <= 
                            search_radius * search_radius) {

                        if (!require_unique || 
                                distsq_shift(nearest_2, data.ref, 
                                        data.shift_x, data.shift_y) > 
                        search_radius * search_radius) {

                            cpl_msg_debug(cpl_func, 
                                    "unique source inside %f pixels",
                                    search_radius);

                            nearest_1->id = fors_std_star_duplicate(data.ref);
                            number_of_ids += 1;
                        }
                    }
                }
                else {
                    cpl_msg_debug(cpl_func, "Nearest candidate at "
                            "distance %f pixels",
                            sqrt(distsq_shift(nearest_1, data.ref, 
                                    data.shift_x, 
                                    data.shift_y)));
                    if (distsq_shift(nearest_1, data.ref, 
                            data.shift_x, data.shift_y) <=
                            search_radius * search_radius) {

                        cpl_msg_debug(cpl_func, 
                                "unique source inside %f pixels",
                                search_radius);

                        nearest_1->id = fors_std_star_duplicate(data.ref);
                        number_of_ids += 1;
                    }
                }
            }
        }

        cpl_msg_info(cpl_func, "Identified %d star%s",
                     number_of_ids, (number_of_ids == 1) ? "" : "s");

        if (number_of_ids == 0) {

            if (fabs(sx_00) > 0.1 && 
                    fabs(sy_00) > 0.1) {

                cpl_msg_debug(cpl_func,
                        "No identifications made, "
                        "set shift to zero and try again");
                search_radius = 20;
                require_unique = false;

                sx_00 = 0;
                sy_00 = 0;
            }
            else {
                require_unique = false;

                search_radius *= 2;

                cpl_msg_debug(cpl_func,
                              "No identifications made, "
                              "double search radius and try again");
            }
        }

        cpl_msg_indent_less();

    } /* while no identifications made */

    if (number_of_ids == 0) {
        cpl_msg_warning(cpl_func,
                "No identifications made, "
                "within search radius %f pixels",
                im->max_search);
        /* When maxsearchradius was a parameter

        cpl_msg_warning(cpl_func,
                        "No identifications made, "
                        "max search radius reached: %f pixels",
                        im->max_search);
         */
    }
    else {
        /* Sketch to recompute shift:
         *    Get median shifts (in x and y) of identified stars.
         *    Then do the equivalent of a single tour through the while() loop above
         *    
         *    This could perhaps be unified with the code above to avoid duplication
         */
    }


    cpl_msg_indent_less();

    cleanup;
    return;
}

/**
 * @brief   Determine if star is inside region
 * @param   std     star
 * @param   reg     rectangular region, see code for the
 *                  required type
 * @return true iff the star's pixel coordinates are inside
 *         the given region
 */
static bool
inside_region(const fors_std_star *std,
              void *reg)
{
    const struct {
        int xlo, ylo;
        int xhi, yhi;
    } *region = reg;

    return
        region->xlo <= std->pixel->x && std->pixel->x <= region->xhi &&
        region->ylo <= std->pixel->y && std->pixel->y <= region->yhi;
}



#undef cleanup
#define cleanup \
do { \
    fors_point_list_delete(&std_points, fors_point_delete); \
    fors_point_list_delete(&source_points, fors_point_delete); \
    fors_pattern_list_delete(&std_patterns, fors_pattern_delete); \
    fors_pattern_list_delete(&source_patterns, fors_pattern_delete); \
    double_list_delete(&scales, double_delete); \
    double_list_delete(&angles, double_delete); \
    double_list_delete(&angle_cos, double_delete); \
    double_list_delete(&angle_sin, double_delete); \
    double_list_delete(&match_dist, double_delete); \
    double_list_delete(&shiftx, double_delete); \
    double_list_delete(&shifty, double_delete); \
} while (0)

/**
 * @brief   Match patterns
 * @param   stars     sources
 * @param   std       reference stars
 * @param   sx_00     (output) shift in x
 * @param   sy_00     (output) shift in y 
 * @param   med_scale (output) median scale
 * @param   med_angle (output) median angle
 *
 * For now, the shifts are approximated with constant 2d polynomials
 * as function of detector (x, y) position.
 *
 * This function could be extended to fit 2d polynomials of degree > 0,
 * if necessary. 
 */
static void
match_patterns(const fors_star_list *stars, 
               const fors_std_star_list *std,
               double kappa,
               double *sx_00,
               double *sy_00,
               double *med_scale,
               double *med_angle,
               int    *status)
{
    fors_point_list *std_points = NULL;
    fors_point_list *source_points = NULL;

    fors_pattern_list *std_patterns = NULL;
    fors_pattern_list *source_patterns = NULL;

    double_list *scales = NULL;
    double_list *angles = NULL;
    double_list *angle_cos = NULL;
    double_list *angle_sin = NULL;
    double_list *match_dist = NULL;
    double_list *shiftx = NULL;
    double_list *shifty = NULL;

    *status = 0;

    assure( sx_00 != NULL, return, NULL );
    assure( sy_00 != NULL, return, NULL );
  
    /* Build patterns */
    std_points = fors_point_list_new();
    {
        const fors_std_star *s;
        
        for (s = fors_std_star_list_first_const(std);
             s != NULL;
             s = fors_std_star_list_next_const(std)) {
            
            fors_point_list_insert(std_points,
                                   fors_point_new(s->pixel->x,
                                                  s->pixel->y));
        }
    }
    
    source_points = fors_point_list_new();
    {
        const fors_star *s;
        
        for (s = fors_star_list_first_const(stars);
             s != NULL;
             s = fors_star_list_next_const(stars)) {
            
            fors_point_list_insert(source_points,
                                   fors_point_new(s->pixel->x,
                                                  s->pixel->y));
        }
    }

    const double min_dist = 1.0; /* minimum distance between two 
                                    points in a pattern */
    double sigma_std = 0.0; /* No uncertainty of catalog patterns */
    std_patterns = 
        fors_pattern_new_from_points(std_points, min_dist, sigma_std);
    cpl_msg_info(cpl_func, "Created %d catalog patterns",
                 fors_pattern_list_size(std_patterns));

    double sigma_source;
    if (fors_star_list_size(stars) > 0) {
	sigma_source = fors_star_list_median(stars,
					     fors_star_extension, NULL);
	cpl_msg_info(cpl_func, "Average source extension = %.2f pixels", sigma_source);
    } else {
	sigma_source = -1; /* not used in this case */
    }
    source_patterns = 
	fors_pattern_new_from_points(source_points, min_dist, sigma_source);

    cpl_msg_info(cpl_func, "Created %d source patterns",
                 fors_pattern_list_size(source_patterns));
    
    scales = double_list_new();
    angles = double_list_new();
    angle_cos = double_list_new();
    angle_sin = double_list_new();
    match_dist = double_list_new();

    if ( fors_pattern_list_size(source_patterns) > 0) {
	/* Identify, 
	   get average scale, orientation */
	fors_pattern *p;
	    
	for (p = fors_pattern_list_first(std_patterns);
	     p != NULL;
	     p = fors_pattern_list_next(std_patterns)) {
		
	    fors_pattern *nearest_source = 
		fors_pattern_list_min_val(source_patterns,
					  (fors_pattern_list_func_eval) fors_pattern_distsq,
					  p);
		
	    double scale = fors_pattern_get_scale(p, nearest_source);
	    double angle = fors_pattern_get_angle(p, nearest_source);
	    double angle_c = cos(angle);
	    double angle_s = sin(angle);
	    double dist = sqrt(fors_pattern_distsq(p, nearest_source));
	    double dist_per_error = fors_pattern_dist_per_error(p, nearest_source);
		
	    cpl_msg_debug(cpl_func, "dist, ndist, scale, orientation = %f, %f, %f, %f",
			  dist, dist_per_error, scale, angle * 360/(2*M_PI));
		
	    /* Make identification if patterns match within error bars
	       (defined above as sigma_source) 
	    */
	    if (dist_per_error < 1.0) {
		double_list_insert(scales   , double_duplicate(&scale));
		double_list_insert(angles   , double_duplicate(&angle));
		double_list_insert(angle_cos, double_duplicate(&angle_c));
		double_list_insert(angle_sin, double_duplicate(&angle_s));
		double_list_insert(match_dist, double_duplicate(&dist));
	    }
	}
    }
    else {
	/* no source patterns to match */
    }
    
    if ( double_list_size(scales) >= 2 ) {
        double scale_avg   = double_list_median(scales, double_eval, NULL);
        double scale_stdev = double_list_mad(scales, double_eval, NULL) * STDEV_PR_MAD;
        
        cpl_msg_info(cpl_func, "Median scale = %.4f +- %.4f",
                     scale_avg, scale_stdev);

        *med_scale = scale_avg;
        
        if (scale_stdev > 0.2) {
            cpl_msg_warning(cpl_func, "Uncertain scale determination");
            *status = 1;
        }
        
        /* 
         * Represent each angle as a unit vector, compute average 
         * unit vector orientation.
         * Compute median absolute deviation with respect to this average 
         */
        double angle_avg = atan2(double_list_mean(angle_sin, double_eval, NULL),
                                 double_list_mean(angle_cos, double_eval, NULL));
        double angle_stdev = STDEV_PR_MAD *
            double_list_median(angles, (double_list_func_eval) fors_angle_diff, &angle_avg);
        
        cpl_msg_info(cpl_func, "Average orientation = %.1f +- %.1f degrees",
                     angle_avg   * 360 / (2*M_PI),
                     angle_stdev * 360 / (2*M_PI));

        *med_angle = angle_avg;
        
        if (angle_avg > M_PI/4 || angle_avg < -M_PI/4) {
            cpl_msg_warning(cpl_func, "Expected orientation = 0 degrees");
            *status = 1;
            /* To model any orientation, we should use higher order (than zero) polynomials
               for the shifts */
        }
        
        double avg_dist   = double_list_mean(match_dist, double_eval, NULL);
        double false_dist = 1.0/sqrt(M_PI * fors_pattern_list_size(source_patterns));
        /* Average distance to nearest false match */
        
        cpl_msg_info(cpl_func, "Average match distance = %f pixel", avg_dist);
        cpl_msg_info(cpl_func, "False match distance = %f pixel", false_dist);
        cpl_msg_info(cpl_func, "Safety index = %.3f (should be >~ 5)", 
                     false_dist / avg_dist);
        if (false_dist / avg_dist < 1.5) {
            cpl_msg_warning(cpl_func, "Uncertain pattern matching");
            *status = 1;
        }
        
        /* Get shift from matches */
        shiftx = double_list_new();
        shifty = double_list_new();
        {
            fors_pattern *p;
            
            for (p = fors_pattern_list_first(std_patterns);
                 p != NULL;
                 p = fors_pattern_list_next(std_patterns)) {
                
                fors_pattern *nearest_source = 
                    fors_pattern_list_min_val(
                        source_patterns,
                        (fors_pattern_list_func_eval) fors_pattern_distsq,
                        p);
                
                double dist = sqrt(fors_pattern_distsq(p, nearest_source));
                double dist_per_error = fors_pattern_dist_per_error(p, nearest_source);
                double scale = fors_pattern_get_scale(p, nearest_source);
                double angle = fors_pattern_get_angle(p, nearest_source);
                
                cpl_msg_debug(cpl_func, "scale, orientation, distance, norm.distance "
                              "= %f, %f, %f, %f",
                              scale, angle * 360/(2*M_PI), dist, dist_per_error);
                
                if (dist_per_error < 1.0 &&
                    fabs(scale - scale_avg)             <= kappa * scale_stdev &&
		    fors_angle_diff(&angle, &angle_avg) <= kappa * angle_stdev) {
                    
                    /* Compute shift of the two patterns' reference stars */
                    double shift_x = fors_pattern_get_ref(nearest_source)->x - fors_pattern_get_ref(p)->x;
                    double shift_y = fors_pattern_get_ref(nearest_source)->y - fors_pattern_get_ref(p)->y;
                    
                    cpl_msg_debug(cpl_func, "Accepted, shift = (%f, %f) pixels", 
                                  shift_x, shift_y);
                    
                    double_list_insert(shiftx, double_duplicate(&shift_x));
                    double_list_insert(shifty, double_duplicate(&shift_y));
                }
            } 
        }

	if (double_list_size(shiftx) > 0) {
	    *sx_00 = double_list_median(shiftx, double_eval, NULL);
	}
	else {
	    /* If this happens it is likely a bug, because
	       we already checked that 'scales' is non-empty.

	       But do not try to get the median of an empty list in any case,
	       which would cause a crash
	    */
	    cpl_msg_warning(cpl_func, "No standardstar identifications!");
            *status = 1;
	}

	if (double_list_size(shiftx) > 0) {
	    *sy_00 = double_list_median(shifty, double_eval, NULL);
	}
	else {
	    cpl_msg_warning(cpl_func, "No standardstar identifications!");
            *status = 1;
	}
    }
    else {
        cpl_msg_warning(cpl_func,
                        "Too few (%d) matching patterns: assuming zero shift",
                        double_list_size(scales));
        *sx_00 = 0;
        *sy_00 = 0;
        *med_scale = 1.0;
        *med_angle = 0.0;
    }

    cleanup;
    return;
}



    
/**@}*/
