/*                                                                              *
 *   This file is part of the ESO UVES Pipeline                                 *
 *   Copyright (C) 2004,2005 European Southern Observatory                      *
 *                                                                              *
 *   This library 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, 51 Franklin St, Fifth Floor, Boston, MA  02111-1307  USA       *
 *                                                                              */

/*
 * $Author: amodigli $
 * $Date: 2010-09-24 09:32:10 $
 * $Revision: 1.13 $
 * $Name: not supported by cvs2svn $
 * $Log: not supported by cvs2svn $
 * Revision 1.11  2007/06/06 08:17:34  amodigli
 * replace tab with 4 spaces
 *
 * Revision 1.10  2007/05/07 14:25:43  jmlarsen
 * Changed formatting
 *
 * Revision 1.9  2007/03/05 10:43:16  jmlarsen
 * Reject outliers based on line FWHM and fit residual
 *
 * Revision 1.8  2006/11/06 15:19:42  jmlarsen
 * Removed unused include directives
 *
 * Revision 1.7  2006/10/10 11:20:11  jmlarsen
 * Renamed line table columns to match MIDAS
 *
 * Revision 1.6  2006/07/14 12:52:57  jmlarsen
 * Exported/renamed function find_nearest
 *
 * Revision 1.5  2006/06/06 08:40:25  jmlarsen
 * Shortened max line length
 *
 * Revision 1.4  2006/06/01 14:43:17  jmlarsen
 * Added missing documentation
 *
 * Revision 1.3  2006/04/21 12:29:30  jmlarsen
 * Write QC parameters to line table
 *
 * Revision 1.2  2006/02/15 13:19:15  jmlarsen
 * Reduced source code max. line length
 *
 * Revision 1.1  2006/02/03 07:46:30  jmlarsen
 * Moved recipe implementations to ./uves directory
 *
 * Revision 1.3  2005/12/19 16:17:55  jmlarsen
 * Replaced bool -> int
 *
 * Revision 1.2  2005/11/14 13:18:44  jmlarsen
 * Minor update
 *
 * Revision 1.1  2005/11/11 13:18:54  jmlarsen
 * Reorganized code, renamed source files
 *
 */

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

/*----------------------------------------------------------------------------*/
/**
 * @addtogroup uves_wavecal
 */
/*----------------------------------------------------------------------------*/
/**@{*/

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

#include <uves_wavecal_utils.h>
#include <uves_utils.h>
#include <uves_utils_wrappers.h>
#include <uves_error.h>
#include <uves_msg.h>

#include <cpl.h>

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

/*-----------------------------------------------------------------------------
                            Implementation
 -----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/**
   @brief    Allocate line table
   @param    windows        Number of extraction windows per trace
   @param    traces         Number of traces
   @return  newly allocated line table which must be deallocated using
   @c uves_lt_delete().
*/
/*----------------------------------------------------------------------------*/

lt_type *uves_lt_new(int windows, int traces)
{
    /* Allocate all line tables for this chip */
    lt_type *lt = cpl_malloc(sizeof(lt_type));

    assure_mem( lt );
    
    lt->windows = windows;
    lt->traces = traces;

    /* Initialize pointer to NULL */
    lt->table                = cpl_calloc(windows*traces, sizeof(cpl_table *));
    lt->dispersion_relation  = cpl_calloc(windows*traces, sizeof(polynomial *));
    lt->absolute_order       = cpl_calloc(windows*traces, sizeof(polynomial *));
    lt->first_absolute_order = cpl_calloc(windows*traces, sizeof(int));
    lt->last_absolute_order  = cpl_calloc(windows*traces, sizeof(int));

  cleanup:
    return lt;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Deallocate line table
   @param    lt     the line table to deallocate
*/
/*----------------------------------------------------------------------------*/

void uves_lt_delete(lt_type **lt)
{
    if (lt != NULL && *lt != NULL)
    {
        int i;
        for (i = 0; i < (*lt)->windows * (*lt)->traces; i++)
        {
            uves_free_table       (&((*lt)->table[i]));
            uves_polynomial_delete(&((*lt)->dispersion_relation[i]));
            uves_polynomial_delete(&((*lt)->absolute_order[i]));
        }

        cpl_free((*lt)->table);
        cpl_free((*lt)->dispersion_relation);
        cpl_free((*lt)->absolute_order);
        cpl_free((*lt)->first_absolute_order);
        cpl_free((*lt)->last_absolute_order);
        
        cpl_free(*lt);
    }

    return;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get the table structure
   @param    lt        the line table
   @param    window    window number
   @param    trace     trace number
   @return   the CPL table of this 'line table'
*/
/*----------------------------------------------------------------------------*/
cpl_table **
uves_lt_get_table(const lt_type *lt, int window, int trace)
{
    return &(lt->table[trace + (window-1)*lt->traces]);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Get dispersion relation
   @param    lt        the line table
   @param    window    window number
   @param    trace     trace number
   @return   the dispersion relation
*/
/*----------------------------------------------------------------------------*/
polynomial **
uves_lt_get_disprel(const lt_type *lt, int window, int trace)
{
    return &(lt->dispersion_relation[trace + (window-1)*lt->traces]);
}
/*----------------------------------------------------------------------------*/
/**
   @brief    Get absolute order polynomial
   @param    lt        the line table
   @param    window    window number
   @param    trace     trace number
   @return   the absolute order polynomial
*/
/*----------------------------------------------------------------------------*/
polynomial **
uves_lt_get_absord(const lt_type *lt, int window, int trace)
{
    return &(lt->absolute_order[trace + (window-1)*lt->traces]);
}
/*----------------------------------------------------------------------------*/
/**
   @brief    Get first absolute order
   @param    lt        the line table
   @param    window    window number
   @param    trace     trace number
   @return   the first absolute order
*/
/*----------------------------------------------------------------------------*/
int *
uves_lt_get_firstabs(const lt_type *lt, int window, int trace)
{
    return &(lt->first_absolute_order[trace + (window-1)*lt->traces]);
}
/*----------------------------------------------------------------------------*/
/**
   @brief    Get last absolute order
   @param    lt        the line table
   @param    window    window number
   @param    trace     trace number
   @return   the last absolute order
*/
/*----------------------------------------------------------------------------*/
int *
uves_lt_get_lastabs(const lt_type *lt, int window, int trace)
{
    return &(lt->last_absolute_order[trace + (window-1)*lt->traces]);
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Find best matching catalogue wavelength
   @param    line_refer    Table to search
   @param    lambda        Find catalogue wavelength nearest to this wavelength
   @param    lo            First row (inclusive, counting from 0) of table to search
   @param    hi            Last row (inclusive, counting from 0) of table to search
   @return   The index of the row containing the wavelength closest to @em lambda

   This function returns the index of the nearest wavelength in the range {lo, ..., hi}
   The input table must be sorted according to the column @em "Wave" which is the column
   that is searched.

   @note The function implements a binary search (using recursion)
   which is why the input table must be sorted.
**/
/*----------------------------------------------------------------------------*/
int
uves_wavecal_find_nearest(const cpl_table *line_refer, double lambda, int lo, int hi)
{
    if (lo == hi) /* One-row interval */
    {
        return lo;
    }
    else if (lo + 1 == hi)
    {  /* Two-row interval */
        double llo, lhi;
        lhi = cpl_table_get_double(line_refer, "Wave", hi, NULL);
        llo = cpl_table_get_double(line_refer, "Wave", lo, NULL);
        
        /* Return the one of 'llo' and 'lhi' that is closest to 'lambda' */
        return ((llo-lambda)*(llo-lambda) < (lhi-lambda)*(lhi-lambda)) ? lo : hi;
    }
    else
    { /* Three or more rows to consider */ 
        double lmid;
        int mid = (lo + hi)/2;
        /* mid is different from both 'lo' and 'hi', so this will terminate */
        
        lmid = cpl_table_get_double(line_refer, "Wave", mid, NULL);
        
        if (lmid < lambda)
        {
            return uves_wavecal_find_nearest(line_refer, lambda, mid, hi);
        }
        else
        {
            return uves_wavecal_find_nearest(line_refer, lambda, lo, mid);
        }
    }
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Delete bad lines from line table
  @param    table         The line table containing the bad lines to be removed
  @param    TOLERANCE     If positive, this tolerance is considered in 
                          pixel units, otherwise in w.l.u.
  @param    kappa         Used in kappa sigma clipping
  @return   The number of deleted bad lines (i.e. deleted table rows), 
            or undefined on error

  Rows with invalid @em "Ident" (un-identified lines) are removed, as are
  rows with @em "Residual_pix" worse than the specified @em TOLERANCE. If
  the @em TOLERANCE is negative, rows with @em LINETAB_RESIDUAL worse than
  |@em TOLERANCE | are removed.
**/
/*----------------------------------------------------------------------------*/
int
uves_delete_bad_lines(cpl_table *table, double TOLERANCE, double kappa)
{    
    int result = 0;
    int numb_lines = cpl_table_get_nrow(table);

    /* Delete rows with invalid 'Ident' (and therefore invalid 'Residual_xxx') */
    check( uves_erase_invalid_table_rows(table, "Ident"),
       "Error erasing un-identified lines");

    assure( cpl_table_has_column(table, "Residual_pix"),
            CPL_ERROR_DATA_NOT_FOUND,
            "Missing column 'Residual_pix'");

    assure( cpl_table_has_column(table, LINETAB_RESIDUAL),
            CPL_ERROR_DATA_NOT_FOUND,
            "Missing column '" LINETAB_RESIDUAL "'");

    if (TOLERANCE > 0)
    {   
            /* Pixel units */
        check(( uves_erase_table_rows(table,        /*  >  tol  */
                      "Residual_pix", CPL_GREATER_THAN, TOLERANCE),
            uves_erase_table_rows(table,        /*  < -tol */
                      "Residual_pix", CPL_LESS_THAN   , -TOLERANCE)),
            "Error removing rows");
    }
    else
    {  
            /* Wavelength units */
        check(( uves_erase_table_rows(table,       /*  >  |tol| */
                      LINETAB_RESIDUAL, CPL_GREATER_THAN, -TOLERANCE),
            uves_erase_table_rows(table,       /*  < -|tol| */
                      LINETAB_RESIDUAL, CPL_LESS_THAN   , TOLERANCE)),
          "Error removing rows");
    }


    /* Also reject outliers (if more than 2 points)
       by kappa sigma clipping                       */

    if (cpl_table_get_nrow(table) - cpl_table_count_invalid(table, "Residual_pix") >= 2)
        {
            check_nomsg( uves_average_reject(table,
                                             "Residual_pix", "temp",
                                             kappa));

            check_nomsg( uves_average_reject(table,
                                             "Xwidth", "temp",
                                             kappa));


            /* Don't do this:
               the same is achieved by detecting fewer
               lines from the start 
            check_nomsg( uves_erase_table_rows(table,
                                               "Peak",
                                               CPL_LESS_THAN,
                                               0.5 *
                                               cpl_table_get_column_median(table, "Peak")));
            */
        }
    
    result = numb_lines - cpl_table_get_nrow(table);

  cleanup:
    return result;
}

/*----------------------------------------------------------------------------*/
/**
   @brief    Draw lines in an echelle image
   @param    image            Image to draw on
   @param    dispersion       The dispersion relation
   @param    order_locations  The order locations (relative order numbering)
   @param    t                Table containing wavelengths to plot
   @param    lambda_column    Name of column containing the wavelengths to plot
   @param    abs_order        Name of column containing the absolute order numbers
   @param    relative_order   The map from absolute to relative orders
   @param    minorder         The first absolute order used
   @param    maxorder         The last absolute order used
   @param    vertical         Flag indicating if plotted lines are vertical (true) 
                              or horizontal (false)
   @param    offset           Horizontal offset (in pixels) of the plotted line
   @return   CPL_ERROR_NONE iff OK

   This function is used for debugging. It plots a line (length = 7 pixels) on the
   image corresponding to each wavelength listed in the specified table column.

   If @em abs_order is non-NULL, each wavelength is drawn on the order specified by
   @em abs_order. In this case @em minorder and @em maxorder are ignored.

   If @em abs_order is NULL, each wavelength is drawn on every order from @em minorder
   (inclusive) to @em maxorder (inclusive). This mode can be used to
   plot catalogue wavelengths that a priori are located in no specific order.

   The y position of a wavelength is inferred from the polynomial @em order_locations 
   (the order table), and the x position is inferred from the dispersion relation.
*/
/*----------------------------------------------------------------------------*/
cpl_error_code
uves_draw_lines(cpl_image *image, polynomial *dispersion, 
        const polynomial *order_locations, const cpl_table *t, 
        const char *lambda_column, const char *abs_order, 
        const int *relative_order, int minorder, int maxorder,
        bool vertical, int offset)
{
    int nx, ny;
    int row;
    
    /* Check input */
    passure( image != NULL, " ");
    passure( dispersion != NULL, " ");
    passure( uves_polynomial_get_dimension(dispersion) == 2, "%d", 
         uves_polynomial_get_dimension(dispersion));
    passure( order_locations != NULL, " ");
    passure( uves_polynomial_get_dimension(order_locations) == 2, "%d",
         uves_polynomial_get_dimension(order_locations));
    passure( t != NULL, " ");
    passure( cpl_table_has_column(t, lambda_column), "%s", lambda_column);
    if (abs_order != NULL)
    {
        passure( cpl_table_has_column(t, abs_order), "%s", abs_order);
    }

    nx = cpl_image_get_size_x(image);
    ny = cpl_image_get_size_y(image);
    
    for (row = 0; row < cpl_table_get_nrow(t); row++) {
    double x, xguess;
    double lambda, lambda_left, lambda_right;
    int order;
    
    check( lambda    = cpl_table_get_double(t, lambda_column, row, NULL),
           "Error reading table");
    
    if (abs_order != NULL)
        check (minorder = maxorder = cpl_table_get_int(t, abs_order, row, NULL),
           "Error reading table");
    
    for (order = minorder; order <= maxorder; order++) {
        lambda_left  = uves_polynomial_evaluate_2d(dispersion, 1 , order)/order;
        lambda_right = uves_polynomial_evaluate_2d(dispersion, nx, order)/order;
        
        /* Solve  dispersion(x, m) = ident*m */
        xguess = 1 + (nx - 1) * (lambda - lambda_left)/(lambda_right - lambda_left);
            /* Simple linear interpolation */
        
        /* Skip if lambda is not in this order */
        if (1 <= xguess && xguess <= nx) {
        x = uves_polynomial_solve_2d(dispersion, lambda*order, xguess,
                         1,                 /* multiplicity */
                         2,                 /* fix this variable number */
                         order);            /* ... to this value */
        
        /* Ignore if solve failed */
        if (cpl_error_get_code() != CPL_ERROR_NONE)
            {
            uves_error_reset();
            }
        else {
            /* Otherwise plot the solution */
            uves_msg_debug("lambda(x=%f)\t = %f", x     ,
                   uves_polynomial_evaluate_2d(dispersion, x     , order)/order);
            uves_msg_debug("lambda(x0=%f)\t = %f", xguess,
                   uves_polynomial_evaluate_2d(dispersion, xguess, order)/order);
            
            if (1 <= x && x <= nx) {
            double y = uves_polynomial_evaluate_2d(order_locations, x,
                                   relative_order[order]);
            int i;
            
            for (i = -3; i <= 3; i++) {
                if (vertical) {
                check( cpl_image_set(image,
                             uves_min_int(nx, uves_max_int(1, x    )),
                             uves_min_int(ny, uves_max_int(1, (int) y + i +
                                           offset)), 0),
                    "Error writing image");
                }
                else {
                check( cpl_image_set(image, 
                             uves_min_int(nx, uves_max_int(1, x - i)),
                             uves_min_int(ny, uves_max_int(1, (int) y +
                                           offset)), 0),
                       "Error writing image");
                }
            }
            }
        } /* Solve succeeded */
        } /* if lambda was inside this order */
        
    } /* for each order */
    
    } /* for each lambda */
    
  cleanup:
    return cpl_error_get_code();
}

/**@}*/
