/* $Id: naco_util_spc_model.c,v 1.10 2011-12-22 11:21:03 llundin Exp $
 *
 * This file is part of the NACO 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: 2011-12-22 11:21:03 $
 * $Revision: 1.10 $
 * $Name: not supported by cvs2svn $
 */

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

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

#include <string.h>

#include "naco_recipe.h"

#include "naco_spc.h"

/*-----------------------------------------------------------------------------
                            Recipe defines
 -----------------------------------------------------------------------------*/

#define RECIPE_STRING "naco_util_spc_model"

#ifndef NACO_SPC_MODEL_SIZE
#define NACO_SPC_MODEL_SIZE 1024
#endif

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

NACO_RECIPE_DEFINE(naco_util_spc_model, 0,
                   "Generate FITS table with physical spectro model",
                   "The sof file shall consist of 1 line with the name of an "
                   "ASCII-file\n"
                   "currently tagged with " NACO_SPC_MODEL_ASCII ".\n"
                   "The file must comprise these "
                   IRPLIB_STRINGIFY(NACO_SPC_MODEL_COLS) " columns:\n"
                   NACO_SPC_LAB_MODE
                   " (with a value found in " NACO_PFITS_STRING_SPECMODE ")\n"
                   NACO_SPC_LAB_RESID
                   " (with fitting residual, currently ignored)\n"
                   NACO_SPC_LAB_ORDER
                   " (with the number of fitted coefficients in the "
                   "Legendre polynomial)\n"
                   NACO_SPC_LAB_XMIN
                   " (with the minimum pixel value used for the fit)\n"
                   NACO_SPC_LAB_XMAX
                   " (with the maximum pixel value used for the fit)\n"
                   NACO_SPC_LAB_C1
                   " (with the coefficient of the 1st Legendre term)\n"
                   NACO_SPC_LAB_C2
                   " (with the coefficient of the 2nd Legendre term)\n"
                   NACO_SPC_LAB_C3
                   " (with the coefficient of the 3rd Legendre term)\n"
                   NACO_SPC_LAB_C4
                   " (with the coefficient of the 4th Legendre term)\n"
                   "\n"
                   "The default input ASCII-file is in the catalogs/ "
                   "directory of the NACO source-code distribution.");

static IRPLIB_UTIL_SET_ROW(naco_util_spc_model_set_row);
static IRPLIB_UTIL_CHECK(naco_util_spc_model_check);

static double naco_util_spc_model_eval(double, double *, double, double,
                                       double, double, double, double);

/*----------------------------------------------------------------------------*/
/**
 * @defgroup naco_util_spc_model   Generate spectrum model FITS table
 */
/*----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------

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

/*----------------------------------------------------------------------------*/
/**
  @brief    Converts an ASCII file with nine columns to a FITS file
  @param    framelist   the frames list
  @return   0 iff everything is ok

 */
/*----------------------------------------------------------------------------*/
static int naco_util_spc_model(cpl_frameset            * framelist,
                               const cpl_parameterlist * parlist)
{
    irplib_framelist * allframes = NULL;
    irplib_framelist * rawframes = NULL;
    cpl_frameset     * useframes = NULL;
    cpl_table        * self      = NULL;
    

    if (cpl_error_get_code()) return cpl_error_get_code();

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

    /* FIXME: Using framelists is the simplest way to extract the relevant
       frames :-( */

    allframes = irplib_framelist_cast(framelist);
    bug_if(allframes == NULL);

    rawframes = irplib_framelist_extract(allframes, NACO_SPC_MODEL_ASCII);
    skip_if (rawframes == NULL);

    irplib_framelist_empty(allframes);

    useframes = irplib_frameset_cast(rawframes);
    bug_if(allframes == NULL);

    /* At least one row per file */
    self = cpl_table_new(irplib_framelist_get_size(rawframes));

    irplib_framelist_empty(rawframes);


    /* Create the table columns - with units */
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_MODE,  CPL_TYPE_STRING));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_ORDER, CPL_TYPE_INT));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_RESID, CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_XMIN,  CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_XMAX,  CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_C1,    CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_C2,    CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_C3,    CPL_TYPE_DOUBLE));
    bug_if (cpl_table_new_column(self, NACO_SPC_LAB_C4,    CPL_TYPE_DOUBLE));

    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_XMIN, "pixel"));
    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_XMAX, "pixel"));
    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_C1,   "Angstrom"));
    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_C2,   "Angstrom"));
    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_C3,   "Angstrom"));
    bug_if(cpl_table_set_column_unit(self, NACO_SPC_LAB_C4,   "Angstrom"));


    skip_if(irplib_dfs_table_convert(self, framelist, useframes, 
                                     NACO_SPC_MODEL_SIZE, '#', NULL,
                                     NACO_SPC_MODEL, parlist, RECIPE_STRING,
                                     NULL, NULL, NULL, "NACO", naco_pipe_id,
                                     naco_util_spc_model_set_row,
                                     naco_util_spc_model_check));
    end_skip;

    cpl_table_delete(self);
    cpl_frameset_delete(useframes);
    irplib_framelist_delete(allframes);
    irplib_framelist_delete(rawframes);

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Convert one ASCII line and insert it into the atble
  @param    self      The table to set
  @param    line      The line to parse
  @param    irow      The row to set (zero for 1st)
  @param    parlist   The recipe parameters
  @return   CPL_TRUE iff the row was modified

  The input catalogs are ASCII files respecting this format:
  
 */
/*----------------------------------------------------------------------------*/
static
cpl_boolean naco_util_spc_model_set_row(cpl_table * self,
                                        const char * line,
                                        int irow,
                                        const cpl_frame * rawframe,
                                        const cpl_parameterlist * parlist)
{


    /* gcc can only check sscanf()s format when it is a string literal */
#define FORMAT "%s %lg %d %lg %lg %lg %lg %lg %lg"

    int nvals;
    char mode[NACO_SPC_MODEL_SIZE];
    double resid, xmin, xmax;
    double c1, c2, c3, c4;
    int order;

    double wlmin, wlmax;
    double nf, wlf, pposf;
    double nc, wlc, pposc;
    double nl, wll, pposl;


    bug_if(0);
    bug_if(self     == NULL);
    bug_if(line     == NULL);
    bug_if(irow   <  0);
    bug_if(rawframe == NULL);
    bug_if(parlist  == NULL);

    nvals = sscanf(line, FORMAT, mode, &resid, &order, &xmin, &xmax,
                   &c1, &c2, &c3, &c4);

    error_if (nvals != NACO_SPC_MODEL_COLS, CPL_ERROR_BAD_FILE_FORMAT,
              "Line with length=%u has %d not "
              IRPLIB_STRINGIFY(NACO_SPC_MODEL_COLS) " items formatted: %s",
              (unsigned)strlen(line), nvals, FORMAT);

    error_if (order > NACO_SPC_MODEL_COEFFS, CPL_ERROR_BAD_FILE_FORMAT,
              "Too high fit order=%d > " IRPLIB_STRINGIFY(NACO_SPC_MODEL_COEFFS)
              " in line %s", order, line);

    error_if (order < 2, CPL_ERROR_BAD_FILE_FORMAT,
              "Too low fit order=%d < 2 in line %s", order, line);

    error_if (xmin < 0.0, CPL_ERROR_BAD_FILE_FORMAT,
              "Negative XMin=%g in line %s", xmin, line);

    error_if (xmax <= xmin, CPL_ERROR_BAD_FILE_FORMAT,
              "XMax=%g is not greater than XMin=%g in line %s", xmax,
              xmin, line);


    bug_if(cpl_table_set_string(self, NACO_SPC_LAB_MODE,  irow, mode));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_RESID, irow, resid));
    bug_if(cpl_table_set_int   (self, NACO_SPC_LAB_ORDER, irow, order));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_XMIN,  irow, xmin));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_XMAX,  irow, xmax));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_C1,    irow, c1));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_C2,    irow, c2));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_C3,    irow, c3));
    bug_if(cpl_table_set_double(self, NACO_SPC_LAB_C4,    irow, c4));

    wlmin = naco_util_spc_model_eval(-1.0, NULL, xmin, xmax, c1, c2, c3, c4);
    wlmax = naco_util_spc_model_eval( 1.0, NULL, xmin, xmax, c1, c2, c3, c4);

    cpl_msg_info(cpl_func, "%-10s has range [Angstrom @ pixel]: "
                 "%8.2f @ %6.2f => %8.2f @ %6.2f", mode, wlmin, xmin, wlmax, xmax);

    /* Pixel position 1, 512.5 and 1024 */
    nf = (2.0 * 1.0 - (xmax + xmin)) / (xmax - xmin);
    wlf = naco_util_spc_model_eval(nf, &pposf, xmin, xmax, c1, c2, c3, c4);

    nc = (2.0 * 512.5 - (xmax + xmin)) / (xmax - xmin);
    wlc = naco_util_spc_model_eval(nc, &pposc, xmin, xmax, c1, c2, c3, c4);

    nl = (2.0 * 1024.0 - (xmax + xmin)) / (xmax - xmin);
    wll = naco_util_spc_model_eval(nl, &pposl, xmin, xmax, c1, c2, c3, c4);

    cpl_msg_info(cpl_func, "%-10s detector full range [Angstrom @ pixel]: "
                 "%8.2f @ %6.2f => %8.2f @ %6.2f => %8.2f @ %6.2f", mode, wlf, pposf, wlc, pposc, wll, pposl);


    end_skip;

    return CPL_TRUE;

}


/*----------------------------------------------------------------------------*/
/**
  @brief    Issue a message regarding the insterted FITS rows
  @param    self      The table to check
  @param    useframes The frames used to fill the table
  @param    parlist   The recipe parameters
  @return   CPL_ERROR_NONE on success, otherwise the relevant CPL error code
  
 */
/*----------------------------------------------------------------------------*/
static
cpl_error_code naco_util_spc_model_check(cpl_table * self,
                                         const cpl_frameset * useframes,
                                         const cpl_parameterlist * parlist)
{

    bug_if(0);
    bug_if(self     == NULL);
    bug_if(parlist  == NULL);

    cpl_msg_info(cpl_func, "Created table of %d spectrum modes from %d file(s)",
                 (int)cpl_table_get_nrow(self),
                 (int)cpl_frameset_get_size(useframes));

    end_skip;

    return cpl_error_get_code();
}

/*----------------------------------------------------------------------------*/
/**
  @brief    Evaluate the 4-coefficient Legendre polynomial L(x)
  @param    x    The evaluation point, -1 <= x <= 1
  @param    pp   Iff non-NULL, the pixel position of x is returned in *pp
  @param    pmin The pixel position at x = -1
  @param    pmax The pixel position at x =  1
  @param    c1   The 1st Legendre coefficient
  @param    c2   The 2nd Legendre coefficient
  @param    c3   The 3rd Legendre coefficient
  @param    c4   The 4th Legendre coefficient
  @param    parlist   The recipe parameters
  @return   CPL_ERROR_NONE on success, otherwise the relevant CPL error code

  L(x) = sum z_i * c_i,

  z_1 = 1
  z_2 = x
  z_3 = (3x^2 - 1)/2
  z_4 = (5x^3 - 3x)/2

  z_i = ((2*i-3) * x * z_{i-1} - (i-2) * z_{i-2}) / (i-1)

 */
/*----------------------------------------------------------------------------*/
static double naco_util_spc_model_eval(double x, double * pp, double pmin,
                                       double pmax,
                                       double c1, double c2,
                                       double c3, double c4)
{

    const double value
        = c1 - 0.5*c3 + x*( c2 - 1.5 * c4 + x*( 1.5 * c3 + x*( 2.5 * c4 )));

    if (pp != NULL) *pp = 0.5 * ( (pmax + pmin) + x * (pmax - pmin) );

    return value;

}
