/* $Id: visir_util_img_std_cat.c,v 1.58 2012-02-06 10:15:47 llundin Exp $
 *
 * This file is part of the VISIR Pipeline
 * Copyright (C) 2002,2003 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  02111-1307  USA
 */

/*
 * $Author: llundin $
 * $Date: 2012-02-06 10:15:47 $
 * $Revision: 1.58 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#include "visir_recipe.h"

/*-----------------------------------------------------------------------------
                                Defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING   "visir_util_img_std_cat"

/*-----------------------------------------------------------------------------
                                Typedefs
 -----------------------------------------------------------------------------*/

typedef int (procfld_func_t)(const char * src, const int offset, void * extra);
typedef struct { procfld_func_t * func; void * data; } procfld_ftor_t;
typedef struct { int * count; double * dest; } scan_in_jansky_args_t;
typedef struct { char ** dest; } scan_in_filtname_args_t;

typedef struct
{
    const char * buf;  // the char buffer to operate on (not changed)
    off_t bufsz;       // the number of chars to consume, in total
    int skipf;         // the number of fields to skip before calling ftor
    int maxf;          // the number of fields to (try to) process, after skipf
    procfld_ftor_t * ftor;  // a function to call for each field, after skipf
} process_fields_args_t;

typedef struct { int sz; double * const head; } darray_t;  // array of doubles
typedef struct { off_t const sz; char const * const head; } carray_t;
typedef struct { int sz; char ** head; } sarray_t;  // array of strings

/*-----------------------------------------------------------------------------
                            Private Functions prototypes
 -----------------------------------------------------------------------------*/

static
int visir_util_img_std_cat(cpl_frameset * framelist,
        const cpl_parameterlist * parlist);

static
cpl_error_code visir_uisc_save(cpl_frameset *,
        const cpl_parameterlist * parlist, const cpl_table * tab);

static
cpl_error_code visir_uisc_parse(carray_t src, char * sname, double * pra,
        double * pdec, char * stype, darray_t dest);

static
cpl_error_code visir_uisc_check(cpl_frameset * framelist, sarray_t * filters);

static
cpl_error_code visir_uisc_flist_read(sarray_t * filts, carray_t src);

static
void visir_uisc_flist_free(sarray_t * flist);

static
char * visir_uisc_flist_2_str(sarray_t flist);

static
int scan_in_jansky(const char * src, const int offset, void * extra);

static
int scan_in_filtname(const char * src, const int offset, void * extra);

static
int process_fields(carray_t src, int skipf, int maxf, procfld_ftor_t * ftor);

/*-----------------------------------------------------------------------------
                            Static variables
 -----------------------------------------------------------------------------*/

VISIR_RECIPE_DEFINE(visir_util_img_std_cat, 0,
    "Convert ASCII-file(s) to a FITS standard star catalog",
    "This recipe generates a FITS standard star catalog for imaging from one \n"
    "or more ASCII-files.  Each line in the text file must have at least 8   \n"
    "fields separated by white-space.  The first field is the star name, e.g.\n"
    "'HD108903' which will be stored in a table column labeled 'STARS'.  The \n"
    "next 3 fields are the RA (hh mm ss) which will be stored in degrees in a\n"
    "table column labeled 'RA' - all three are non-negative and hh and mm are\n"
    "integer.  The 3 next fields are the DEC (dd mm ss) which will be stored \n"
    "in degrees in a table column labeled 'DEC' - all three are non-negative,\n"
    "dd and mm are integer, while dd has either a '+' or a '-' prepended (in-\n"
    "cluding -00).  The next field is the spectral type which will be stored \n"
    "in a table column labeled 'SP_TYPE'.  Every field after the first 8 are \n"
    "the JY values for the supported filters, and may be a mixture of image  \n"
    "or spectral filters.  All JY values must be positive.  For each filter  \n"
    "the JY value is stored in a table column labeled with the filter name   \n"
    "taken from the first row of column headings or, if there is no 1st row  \n"
    "of column headings, using a list of 44 filter names hard-coded in the   \n"
    "recipe (in which case the number of JY fields must be exactly 42 or else\n"
    "an error is printed and execution ends).                                \n"
    "                                                                        \n"
    "As mentioned, the 1st row may be a list of column headings.  If this row\n"
    "is supplied, the 1st 4 fields will be ignored (but dummy values must be \n"
    "supplied anyway), and every field after those will be used as the column\n"
    "label in the output table for that column's JY values.                  \n" 
    "                                                                        \n"
    "Lines beginning with a hash (#) are treated as comments.                \n"
    "                                                                        \n"
    "The ASCII-input should be tagged "VISIR_IMG_LINES_ASCII", but all input \n"
    "files will currently be processed regardless.                           \n"
);

const int sanity_check_failed = 0 + CPL_ERROR_EOL;

/*----------------------------------------------------------------------------*/
/**
 * @defgroup visir_util_img_std_cat   Convert ASCII star catalog(s) to FITS
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
                                Functions code
 -----------------------------------------------------------------------------*/

static
void visir_uisc_flist_free(sarray_t * flist)
{
    for (int j = flist->sz; j-- > 0;) {
        if (!flist->head[j]) continue;
        cpl_free(flist->head[j]);
        flist->head[j] = NULL;
    }
    if (flist->head) {
        cpl_free(flist->head);
        flist->head = NULL;
    }
    flist->sz = 0;
}

/* process whitespace-separated fields in a char buffer (a field starts on the
 * transition from whitespace to non-whitespace; stops processing after a \n is
 * hit, or maxf has been reached, or src.sz has been reached, whichever comes
 * first (src.sz should always be provided otherwise -999 is returned) */
static
int process_fields(carray_t src, int skipf, int maxf, procfld_ftor_t * ftor)
{
    if (!src.sz) return -999;

    int nfields = -skipf;
    int ws = 1;
    off_t i = 0;

    // loop condition below reduces to the following:
    // i < src.sz && src.head[i] != '\n'                    // the default
    // i < src.sz && src.head[i] != '\n' && nfields < maxf  // maxf > 0
    for (; i < src.sz && src.head[i] != '\n' && (!maxf || nfields < maxf); ++i) {
        if (src.head[i] == ' ' || src.head[i] == '\t') {
            ws = 1;
            continue;
        }
        if (ws) {
            // new field detected: call given functor (if any) to process it
            if (++nfields > 0 && ftor && ftor->func) {
                // return value ignored for now
                (void)ftor->func(src.head + i, nfields - 1, ftor->data);
            }
        }
        ws = 0;
    }

    return nfields;
}

static
int scan_in_filtname(const char * src, const int offset, void * extra)
{
    scan_in_filtname_args_t * in = extra;

    char tmp[50] = {0};
    if (EOF == sscanf(src, "%s", tmp)) {
        cpl_msg_error(cpl_func, "Couldn't read formatted input");
        return CPL_ERROR_FILE_IO;
    }

    // capitalise & correct incompat chars in filt name
    in->dest[offset] = cpl_sprintf("%s", tmp);
    for (char * p = in->dest[offset]; *p; ++p) {
        if (*p == '.' || *p == '-') *p = '_';
        *p = toupper(*p);
    }

    // "spec", if present, must be in lowercase
    char * here = strstr(in->dest[offset], "SPEC");
    if (here) for (char * p = here; *p; ++p)  // assumes it's always at the end
        *p = tolower(*p);

    return CPL_ERROR_NONE;
}

/* read column headings into filters array */
static
cpl_error_code visir_uisc_flist_read(sarray_t * filts, carray_t src)
{
    assert(filts->sz);  // function assumes size is prepopulated

    // allocate space for & initialise filters array
    filts->head = cpl_malloc(filts->sz * sizeof(char*));
    for (int i = filts->sz; i > 0; filts->head[--i] = NULL);

    // populate the array with filter names read in from the file contents
    scan_in_filtname_args_t fargs = { .dest = filts->head };
    procfld_ftor_t ftor = { scan_in_filtname, &fargs };
    int n_fields_read = process_fields(src, 4, filts->sz, &ftor);

    if (n_fields_read < filts->sz) {
        cpl_msg_error(cpl_func, "buffer had unexpected number of fields");
        cpl_ensure_code(0, CPL_ERROR_ILLEGAL_INPUT);
        visir_uisc_flist_free(filts);
    }

    return cpl_error_get_code();
}

static
char * visir_uisc_flist_2_str(sarray_t flist)
{
    char * slist = NULL;
    for (int i = 0; i < flist.sz; ++i) {
        char * new = cpl_sprintf("%s%s, ", slist ? slist : "", flist.head[i]);
        if (slist) { cpl_free(slist); slist = NULL; }
        slist = new;
    }
    return slist;
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Check that all files have the same column structure
  @param    framelist   the frames list
  @return   0 iff everything is ok

 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_uisc_check(cpl_frameset * framelist, sarray_t * filters)
{
    int fd = -1;
    char * buffer = NULL;

    int prev_data_cols = -1;
    for (int file = 0; file < cpl_frameset_get_size(framelist); ++file) {
        const cpl_frame * frame = cpl_frameset_get_position_const(framelist,
                                                                  file);
        skip_if (cpl_error_get_code() || !frame);
        const char * fname = cpl_frame_get_filename(frame);
        skip_if (cpl_error_get_code() || !fname);

        fd = open(fname, O_RDONLY);
        if (fd == -1) {
            cpl_msg_error(cpl_func, "Could not open file %d: %s", file + 1,
                          fname);
            cpl_ensure_code(0, CPL_ERROR_ASSIGNING_STREAM);
            skip_if (1);
        }
  
        struct stat stbuf;
        bool fail = (fstat(fd, &stbuf) != 0) || (!S_ISREG(stbuf.st_mode));
        if (fail) {
            cpl_msg_error(cpl_func, "Could not stat file %d, %s, or it is not a"
                          " regular file", file + 1, fname);
            cpl_ensure_code(0, CPL_ERROR_FILE_IO);
            skip_if (1);
        }
  
        off_t fsize = stbuf.st_size;
        buffer = cpl_malloc(fsize);
        if (!buffer) {
            cpl_msg_error(cpl_func, "Could not allocate space to hold file "
                          "contents");
            cpl_ensure_code(0, CPL_ERROR_NULL_INPUT);
            skip_if (1);
        }

        off_t count = 0;
        while (count < fsize) {
            ssize_t batch = read(fd, buffer+count, fsize);
            if (batch == -1) {
                cpl_msg_error(cpl_func, "Error reading from file %d: %s",
                              file + 1, fname);
                cpl_ensure_code(0, CPL_ERROR_FILE_IO);
                skip_if (1);
            }
            count += batch;
        }

        if (count != fsize) {
            cpl_msg_error(cpl_func, "Mismatch in bytes read vs. file size");
            cpl_ensure_code(0, sanity_check_failed);
            skip_if (1);
        }

        //// count the total of uncommented lines
        //int nlines = 0;
        //for (off_t i = 0; i < fsize; ++i)
        //    if (buffer[i] == '\n') ++nlines;
        //if (buffer[fsize - 1] != '\n') ++nlines;
        //for (off_t i = 0; i < fsize; ++i)
        //    if (i < fsize-1 && buffer[i] == '\n' && buffer[i+1] == '#')
        //        --nlines;
        //if (buffer[0] == '#') --nlines;

        // find first uncommented line
        off_t head = 0;
        for (; head < fsize && buffer[head] == '#'; ++head) {
            while (head < fsize && buffer[head] != '\n') ++head;
            if (head >= fsize) break;
        }

        if (head >= fsize) {
            cpl_msg_warning(cpl_func, "No uncommented lines in file %d: %s",
                            file + 1, fname);
            if (buffer) { cpl_free(buffer); buffer = NULL; }
            if (fd != -1) { close(fd); fd = -1; }
            continue;
        }

        // find second uncommented line
        off_t data = head+1;
        do {
            while (data < fsize && buffer[data] != '\n') ++data;
            if (data >= fsize) break;
            ++data;
        } while (data < fsize && buffer[data] == '#');

        if (data >= fsize) {
            cpl_msg_warning(cpl_func, "There must at least 2 uncommented lines "
                            "in file %d: %s", file + 1, fname);
            if (buffer) { cpl_free(buffer); buffer = NULL; }
            if (fd != -1) { close(fd); fd = -1; }
            continue;
        }

        carray_t head_src = { fsize - head, buffer + head };
        carray_t data_src = { fsize - data, buffer + data };
        int nheads = process_fields(head_src, 0, 0, 0);
        int ndatas = process_fields(data_src, 0, 0, 0);
        int diff = ndatas - nheads;

        // data row has the correct # of additional cols
        if (diff == 4) {
            int nfilts = nheads - 4;

            // filters found in a prior file: compare with those in this file
            if (filters->head) {
                // check that this file has the same number of filters
                if (nfilts != filters->sz) {
                    cpl_msg_error(cpl_func, "Column headings in file %d, "
                                  "%s, don't match a previous file (the "
                                  "count differs)", file + 1, fname);
                    cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
                    skip_if (1);
                }

                // allocate & initialise filters array with column headings
                sarray_t filts = { nfilts, NULL };
                skip_if (visir_uisc_flist_read(&filts, head_src));

                for (int i = 0; i < filts.sz; ++i) {
                    if (strcmp(filts.head[i], filters->head[i])) {
                        cpl_msg_error(cpl_func, "Column headings in file %d, "
                                      "%s, don't match a previous file (check "
                                      "order and spelling)", file + 1, fname);
                        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
                        visir_uisc_flist_free(&filts);
                        skip_if (1);
                    }
                }

                visir_uisc_flist_free(&filts);
            }
            else {
                // allocate & initialise filters array with column headings
                filters->sz = nfilts;
                skip_if (visir_uisc_flist_read(filters, head_src));

                char * flist = visir_uisc_flist_2_str(*filters);
                cpl_msg_info(cpl_func, "Found %d filters in file %d, %s, which "
                        "will be used as the column headings in the output "
                        "file.  The filters are, in order: %s", nfilts,
                        file + 1, fname, flist);
                cpl_free(flist);
            }
        }

        // same col count in "header" and data row: prob both are data rows
        else if (diff == 0) {
            cpl_msg_info(cpl_func, "No filter names in file %d: %s", file + 1,
                         fname);
            if (filters->head) {
                if (ndatas-8 != filters->sz) {
                    cpl_msg_error(cpl_func, "Column structure in file %d, %s, "
                                  "doesn't match a previous file", file + 1,
                                  fname);
                    cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
                    skip_if (1);
                }
            }
            else if (prev_data_cols > -1 && prev_data_cols != ndatas) {
                cpl_msg_error(cpl_func, "Column structure in file %d, %s, "
                              "doesn't match a previous file (different number "
                              "data columns)", file + 1, fname);
                cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
                skip_if (1);
            }
        }

        // some other weird configuration exists
        else {
            cpl_msg_error(cpl_func, "Unexpected column structure in file %d, "
                          "%s (header row doesn't match data rows, or data "
                          "rows don't match each other)", file + 1, fname);
            cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
            skip_if (1);
        }

        prev_data_cols = ndatas;

        cpl_free(buffer); buffer = NULL;
        close(fd); fd = -1;
    }

    // no headers defined after processing all files: use the default list
    if (!filters->head) {
        char * defaults[] = {
            "MV", "N_BAND", "SIC", "PAH1_1", "PAH1", "ARIII", "SIV_1", "SIV",
            "PAH2_1", "SIV_2", "PAH2", "PAH2_2", "NEII_1", "NEII", "NEII_2",
            "J7_9", "J8_9", "J9_8", "J12_1", "J12_2", "B8_7", "B9_7", "B10_7",
            "B11_7", "B12_4", "Q0", "QH2", "Q1", "Q2", "Q3", "Q4", "Q7", "Q8",
            "12_4_AGP", "10_5_4QP", "11_3_4QP", "N_SW_spec", "H2S4_spec",
            "ARIII_spec", "NEII_2_spec", "H2S3_spec", "H2S1_spec",
            "M_BAND", "L_BAND"
        };
        filters->sz = sizeof(defaults)/sizeof(char*);
        filters->head = cpl_malloc(filters->sz * sizeof(char*));
        for (int i = filters->sz; i-- > 0; )
            filters->head[i] = cpl_sprintf("%s", defaults[i]);

        prev_data_cols -= 8;
        if (filters->sz > prev_data_cols) {
            cpl_msg_error(cpl_func, "Fewer Jansky columns found (%d) than "
                          "there are default filters defined (%d)",
                          prev_data_cols, filters->sz);
            cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
            skip_if (1);
        }

        char * flist = visir_uisc_flist_2_str(*filters);
        cpl_msg_warning(cpl_func, "*** BIG ASSUMPTION ***: No filter names "
                "were found in the file(s).  Defaulting to a pre-defined set "
                "and hoping they match the column data found!  The default "
                "filters are, in order: %s", flist);
        cpl_free(flist);
    }

    end_skip;

    if (cpl_error_get_code()) visir_uisc_flist_free(filters);
    if (buffer) cpl_free(buffer);
    if (fd != -1) close(fd);
    
    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    The catalog creation occurs here 
  @param    parlist     the parameters list
  @param    framelist   the frames list
  @return   0 iff everything is ok

 */
/*----------------------------------------------------------------------------*/
static
int visir_util_img_std_cat(cpl_frameset * framelist,
        const cpl_parameterlist * parlist)
{
    FILE       * in = NULL;
    char         line[2048];
    cpl_table  * tab = NULL;
    char         sname[512];
    char         stype[512];
    const double max_radius = VISIR_STAR_MAX_RADIUS;
    double       mindist;
    double     * jys = NULL;
    int          iloc1, iloc2;
    int          nrows = 425; /* Some positive number */
    int          irow  = 0;


    if (cpl_error_get_code()) return cpl_error_get_code();

    /* Identify the RAW frames in the input frameset */
    skip_if (visir_dfs_set_groups(framelist));

    /* check the files for consistency & read in the filters */
    sarray_t filters = { 0, NULL };
    skip_if (visir_uisc_check(framelist, &filters));
    jys = cpl_malloc(filters.sz * sizeof(double));

    /* Create the table */
    tab = cpl_table_new(nrows);
    skip_if (cpl_table_new_column(tab, "STARS", CPL_TYPE_STRING));
    skip_if (cpl_table_new_column(tab, "SP_TYPE", CPL_TYPE_STRING));
    skip_if (cpl_table_new_column(tab, "RA", CPL_TYPE_DOUBLE));
    skip_if (cpl_table_new_column(tab, "DEC", CPL_TYPE_DOUBLE));

    for (int j = 0; j < filters.sz; j++)
        skip_if (cpl_table_new_column(tab, filters.head[j], CPL_TYPE_DOUBLE));
    
    /* Loop on all input frames */
    for (int i = 0; i < cpl_frameset_get_size(framelist); i++) {
        const cpl_frame * frame = cpl_frameset_get_position_const(framelist, i);

        /* Get file name */
        const char * filename = cpl_frame_get_filename(frame);

        /* Open the file */
        skip_if (filename == NULL);
        in = fopen(filename, "r");
        if (in == NULL) {
            cpl_msg_error(cpl_func, "Could not open the %d. file: %s", i+1,
                          filename);
            skip_if (1);
        }

        int jrow = 0;
        while (fgets(line, sizeof(line), in) != NULL) {
            jrow++;
            if (line[0] != '#') {
                double ra, dec;

                carray_t src = { sizeof(line), line };
                darray_t dest = { filters.sz, jys };
                if (visir_uisc_parse(src, sname, &ra, &dec, stype, dest)) {
                    cpl_msg_warning(cpl_func, "Unparsable line %d in file %d: "
                                    "%s (may be header line, or corrupt)", jrow,
                                    i+1, filename);
                    cpl_error_reset();
                    --jrow;
                    continue;
                }
                if (irow == nrows) {
                    /* Table needs more rows */
                    nrows *= 2;
                    skip_if (cpl_table_set_size(tab, nrows));
                }
                skip_if (cpl_table_set_string(tab, "STARS", irow, sname));
                skip_if (cpl_table_set_string(tab, "SP_TYPE", irow, stype));
                skip_if (cpl_table_set_double(tab, "RA", irow, ra));
                skip_if (cpl_table_set_double(tab, "DEC", irow, dec));
                for (int j = 0; j < filters.sz; j++) skip_if
                    (cpl_table_set_double(tab, filters.head[j], irow, jys[j]));
                irow++;

            }
        }
        fclose(in);
        in = NULL;
        if (jrow == 0) {
            cpl_msg_warning(cpl_func, "No usable lines in file %s", filename);
        }
    }

    skip_if (irow == 0);

    /* Resize the table to the actual number of rows read */
    nrows = irow;
    skip_if (cpl_table_set_size(tab, nrows));

    mindist = visir_star_dist_min(cpl_table_get_data_double(tab, "RA"),
                                  cpl_table_get_data_double(tab, "DEC"), nrows,
                                  &iloc1, &iloc2);

    if (mindist < max_radius)
        cpl_msg_warning(cpl_func, "The pair of closest stars is %s (%d) and %s "
                        "(%d) with the distance: %g",
                        cpl_table_get_string(tab, "STARS", iloc1), iloc1,
                        cpl_table_get_string(tab, "STARS", iloc2), iloc2,
                        mindist);
    else
        cpl_msg_info(cpl_func, "The pair of closest stars is %s (%d) and %s "
                     "(%d) with the distance: %g",
                     cpl_table_get_string(tab, "STARS", iloc1), iloc1,
                     cpl_table_get_string(tab, "STARS", iloc2), iloc2,
                     mindist);

    /* Save the table */
    cpl_msg_info(cpl_func, "Saving the table with %d rows and %d filters",
                 nrows, filters.sz);

    skip_if (visir_uisc_save(framelist, parlist, tab));

    end_skip;

    visir_uisc_flist_free(&filters);
    if (in) { fclose(in); in = NULL; }
    if (jys) { cpl_free(jys); jys = NULL; }
    cpl_table_delete(tab);
    
    return cpl_error_get_code();
}

static
int scan_in_jansky(const char * src, const int offset, void * extra)
{
    scan_in_jansky_args_t * in = extra;
    return *in->count += sscanf(src, "%lg", in->dest + offset);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Parse a line of ASCII input
  @param    line   The ASCII line
  @param    sname  The Star name
  @param    ra     The Right Ascension
  @param    dec    The Declination
  @param    stype  The Star type
  @param    jys    The array of Jansky values
  @param    njys   The number of Jansky values
  @return   CPL_ERROR_NONE iff the line was parsed OK
  @note The functions will assert() on NULL input or wrong njys
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_uisc_parse(carray_t src, char * sname, double * pra,
        double * pdec, char * stype, darray_t dest)
{
    int ra1, ra2;
    int dec1, dec2;
    double ra3, dec3;
    char isign;

    assert( src.head );
    assert( sname );
    assert( stype );
    assert( dest.head );

    // scan in the non-Janksy values
    const char * format = "%s %d %d %lg %c%d %d %lg %s ";
    int nvals = sscanf(src.head, format, sname, &ra1, &ra2, &ra3, &isign, &dec1,
                       &dec2, &dec3, stype);

    // scan in the Jansky values
    scan_in_jansky_args_t fargs = { .count = &nvals, .dest = dest.head };
    procfld_ftor_t ftor = { scan_in_jansky, &fargs };
    int nfields = process_fields(src, 8, dest.sz, &ftor);

    if (nvals != dest.sz+9 || nfields < dest.sz) {
        cpl_msg_warning(cpl_func, "Read %d items from line (length=%u) instead "
                        "of the expected %d items", nvals,
                        (unsigned)strlen(src.head), dest.sz+9);
        cpl_ensure_code(0, CPL_ERROR_BAD_FILE_FORMAT);
        return cpl_error_get_code();
    }

    return visir_star_convert(src.head, ra1, ra2, ra3, isign, dec1, dec2, dec3,
                              dest.head, dest.sz, pra, pdec);
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Save the visir_util_img_std_cat recipe products on disk
  @param    set     the input frame set
  @param    parlist the input list of parameters
  @param    tab     table to save
  @return   0 iff everything is ok
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code visir_uisc_save(cpl_frameset * set,
        const cpl_parameterlist * parlist, const cpl_table * tab)
{
    cpl_propertylist * applist = cpl_propertylist_new();


    bug_if (cpl_propertylist_append_string(applist, "INSTRUME", "VISIR"));

    skip_if (irplib_dfs_save_table(set, parlist, set, tab, NULL, RECIPE_STRING,
                                   VISIR_IMA_STD_CAT_PROCATG,
                                   applist, NULL, visir_pipe_id,
                                   RECIPE_STRING CPL_DFS_FITS));

    end_skip;

    cpl_propertylist_delete(applist);

    return cpl_error_get_code();
}

