/*                                                                           *
 *   This file is part of the ESO X-shooter Pipeline                         *
 *   Copyright (C) 2006 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: 2011-12-02 14:15:28 $
 * $Revision: 1.13 $
 * $Name: not supported by cvs2svn $
 */

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

/*---------------------------------------------------------------------------*/
/**
 * @defgroup xsh_spectrum_detect_lines_flat detect lines in a input spectrum
 * @ingroup drl_functions
 *
 * This module is a DRL function 
 */
/*---------------------------------------------------------------------------*/
/**@{*/

/*---------------------------------------------------------------------------
                                Includes
 ----------------------------------------------------------------------------*/
#include <math.h>
#include <xsh_error.h>
#include <xsh_msg.h>
#include <xsh_eqwidth_lib.h>
#include <xsh_utils_table.h>

/*---------------------------------------------------------------------------
                            Typedefs
  ---------------------------------------------------------------------------*/
#define INV_DOUBLE          -9999.0
#define LIGHTSPEED1000      299792458         // Speed of light  [m/s]

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

/*---------------------------------------------------------------------------
                              Implementation
  ---------------------------------------------------------------------------*/
static cpl_table*
xsh_table_unique_wave(cpl_table* tab,const char* wname, const char* fname) {

    cpl_table* xtab=NULL;
    xtab=cpl_table_duplicate(tab);
    int norg=cpl_table_get_nrow(xtab);
    xsh_msg("N orig %d",norg);
    double* pwave=cpl_table_get_data_double(xtab,wname);
    double* pflux=cpl_table_get_data_double(xtab,fname);
    int ninv=1;
    int nrow=0;
    int nbad=0;
    int j=0;
    for(j=0;ninv>0,j<4;j++) {
        nrow=cpl_table_get_nrow(xtab);
        for(int i=1;i < nrow; i++) {
            if (!(pwave[i-1] < pwave[i])) {
                if(pflux[i-1]<= pflux[i]) {
                    cpl_table_set_invalid(xtab,wname,i-1);
                } else {
                    cpl_table_set_invalid(xtab,wname,i);
                }
                nbad++;
            }
        }

        ninv=cpl_table_count_invalid(xtab,wname);
        xsh_msg("iter=%d nrow=%d nbad=%d ninv=%d",j,nrow,nbad,ninv);
        if(ninv>0) {
            cpl_table_erase_invalid(xtab);
        } else {
            break;
        }
    }


    int nnew=cpl_table_get_nrow(xtab);
    xsh_msg("niter=%d",j);
    xsh_msg("N orig %d flagged %d expected %d new %d",norg,nbad,norg-nbad,nnew);
    return xtab;
}

/* test line detection on a spectrum provided in an input FITS table */
cpl_error_code
test_spectrum_detect_lines(int argc, char* argv[])
{

    char fname[256];

    double hwidth=0.3;//2.0;
    double overlap=0.5;
    int niter=10;
    double thres=0.01;
    double resol=0.01;
    int smwidth=20;
    double wdiff=0.02;
    cpl_table* spec_tab=NULL;
    double zem = 0.0;
    double vel_step=300.;
    xsh_msg_warning("argc=%d",argc);

    if((size_t)argc==1){
        return cpl_error_get_code();
    }
    if((size_t)argc==2){
       sprintf(fname,argv[1]);
       xsh_msg_warning("filename=%s",fname);
    }
    else if((size_t)argc>2){
       xsh_msg_warning("reading params");
       hwidth=atof(argv[2]);
       overlap=atof(argv[3]);
       niter=atoi(argv[4]);
       thres=atof(argv[5]);
       resol=atof(argv[6]);
       smwidth=atoi(argv[7]);
       wdiff=atof(argv[8]);

    }
    xsh_msg_warning("hwidth=%f overlap=%f niter=%d thres=%f resol=%f smdiff=%d wdiff=%f",hwidth,overlap,niter,thres,resol,smwidth,wdiff);

    /* load input data */
    cpl_table* org_tab=cpl_table_load(fname, 1, 0);
    /* remove wave duplicates */
    spec_tab=xsh_table_unique_wave(org_tab,"WAVE","FLUX");
    //cpl_table_dump_structure(spec_tab,stdout);
    /* convert emission in absorbtion spectrum to work with ARES routines */
    double max=cpl_table_get_column_max(spec_tab,"FLUX");
    check(cpl_table_add_scalar(spec_tab,"FLUX",max));
    check(cpl_table_duplicate_column(spec_tab,"INV_FLUX",spec_tab,"FLUX"));
    int nrow=cpl_table_get_nrow(spec_tab);
    check(cpl_table_fill_column_window_double(spec_tab,"INV_FLUX",0,nrow,1.));
    check(cpl_table_divide_columns(spec_tab,"INV_FLUX","FLUX"));
    cpl_table_erase_column(spec_tab,"FLUX");
    //cpl_table_dump_structure(spec_tab,stderr);
    cpl_table_name_column(spec_tab,"INV_FLUX","FLUX");

    cpl_table_name_column(spec_tab,"WAVE","WAVEL");
    check(xsh_sort_table_1(spec_tab,"WAVEL",CPL_FALSE));
    cpl_table_save(spec_tab,NULL,NULL,"spec_tab.fits",CPL_IO_DEFAULT);


    // Chunk definition   
    double wavel_min = cpl_table_get_double(spec_tab, "WAVEL", 0, NULL);
    double wavel_max = cpl_table_get_double(spec_tab, "WAVEL",
                                            cpl_table_get_nrow(spec_tab) - 1, NULL);
    double pos_start = wavel_min + hwidth;
    double pos_end   = wavel_max - hwidth;
    if (wavel_min < cpl_table_get_column_min(spec_tab, "WAVEL")) {
        pos_start = cpl_table_get_column_min(spec_tab, "WAVEL") + hwidth;
    }
    if (wavel_max > cpl_table_get_column_max(spec_tab, "WAVEL")) {
        pos_end   = cpl_table_get_column_max(spec_tab, "WAVEL") - hwidth;
    }
    xsh_msg_warning("wmin=%g wmax=%g",wavel_min,wavel_max);
    xsh_msg_warning("pos_start=%g pos_end=%g",pos_start,pos_end);

    cpl_table *spec_proc = NULL;
    cpl_table *line      = NULL;
    cpl_size   line_num  = 0;
    cpl_size   line_size = 0;
    if (pos_end < pos_start) {
        cpl_msg_warning(cpl_func, "Chunks size is too large w.r.t. the "
                        "wavelength range of the spectrum! It should be lower "
                        "than %f nm.", 0.5 * (wavel_max - wavel_min));
    }
    else {

        double pos_step  = hwidth * 2.0 * (1.0 - overlap);
        int    chunk_num = (int)ceil((pos_end - pos_start)/pos_step);       
        cpl_table  *line_chunk = NULL;
        cpl_table  *line_temp  = cpl_table_new(10000);
        int         i          = 0;
        int         flag       = 0;
        cpl_table_new_column(line_temp, "WAVEL", CPL_TYPE_DOUBLE);
        cpl_table_new_column(line_temp, "PEAK",  CPL_TYPE_DOUBLE);
        cpl_table_new_column(line_temp, "CONT",  CPL_TYPE_DOUBLE);
        cpl_table_new_column(line_temp, "CONTERR",  CPL_TYPE_DOUBLE);
        cpl_table_fill_column_window_double(line_temp, "CONT",    0, 10000, 
                                            INV_DOUBLE);
        cpl_table_fill_column_window_double(line_temp, "CONTERR", 0, 10000, 
                                            INV_DOUBLE);

        // Undersample QSO spectrum

        if (zem > 0.0) {
            cpl_msg_info(cpl_func, "*** Undersampling spectrum ***");
            double vel_step_test = 2.0 * hwidth / (wavel_min * 
                                   (exp(vel_step / LIGHTSPEED1000) - 1.0));
            if (vel_step_test < 3.0) {
                cpl_msg_warning(cpl_func, "Undersampling of the spectrum is "
                                "incompatible with the adopted chunk size!");
                vel_step = (double)floor(LIGHTSPEED1000 * 
                            log(2.0 * hwidth / (3.0 * wavel_min) + 1.0));
                cpl_msg_info(cpl_func, "Parameter vel-step is too low: redefined "
                             "to %6.1f.", vel_step);
            }
            /* AMO: comment out this not to include extra function 
            cpl_ensure_code(espda_spec_rebin(spec, kappa, wavel_min, wavel_max,
                            vel_step, &spec_proc) == CPL_ERROR_NONE, 
                            cpl_error_get_code());
            */
        }
        else {
            spec_proc = cpl_table_duplicate(spec_tab);
        }

        // Loop over chunks
        cpl_msg_info(cpl_func, "*** Creating a line list ***");
        cpl_msg_info(cpl_func, "Processing chunks...");
        for (double pos = pos_start; pos < pos_end; pos += pos_step) {
            i++;

            cpl_table *chunk = NULL;
            // Stellar spectra
            if (zem == 0.0) {

                // Chunk selection
               xsh_msg_warning("pos=%g hwidth=%g",pos,hwidth);
                cpl_ensure_code(select_local_spec(spec_tab, hwidth, pos, 
                                &chunk) == CPL_ERROR_NONE, 
                                cpl_error_get_code());
                cpl_table_unselect_all(chunk);
                //double    cont_rejt   = 0.995;
                double    cont_rejt   = 0.95;
                // Chunk sorting
                cpl_propertylist *chunk_col = cpl_propertylist_new();
                cpl_propertylist_append_bool(chunk_col, "WAVEL", FALSE);
                cpl_table_sort(chunk, chunk_col);
                cpl_propertylist_delete(chunk_col);
                // Local continuum fitting
                cpl_ensure_code(esp_fit_lcont(chunk, cont_rejt, niter)
                                == CPL_ERROR_NONE, cpl_error_get_code());
                // Line detection
                cpl_ensure_code(esp_det_line(chunk, thres, resol, smwidth, 
                                &line_chunk) == CPL_ERROR_NONE, 
                                cpl_error_get_code());
                // Line list creation
                cpl_table_unselect_all(line_chunk);
                cpl_table_or_selected_double (line_chunk, "WAVEL",
                    CPL_GREATER_THAN,     pos - hwidth * (1.0 - overlap));
                cpl_table_and_selected_double(line_chunk, "WAVEL",
                    CPL_NOT_GREATER_THAN, pos + hwidth * (1.0 - overlap)); 
                if (cpl_table_count_selected(line_chunk) > 0) {
                    cpl_table *line_part = cpl_table_extract_selected(line_chunk);
                    if (flag == 0 && cpl_table_get_nrow(line_part) != 0) {
                        line = cpl_table_duplicate(line_part);
                        flag = 1;
                    }
                    else {
                        cpl_table_insert(line, line_part, line_num);
                    }
                    line_num += cpl_table_get_nrow(line_part);     
                    cpl_table_delete(line_part);        
                } 

                cpl_table_delete(line_chunk);
            }
      
            // QSO spectra
            else {
                // Chunk selection
                cpl_ensure_code(select_local_spec(spec_proc, hwidth, pos, 
                                &chunk) == CPL_ERROR_NONE, 
                                cpl_error_get_code());
                cpl_table_unselect_all(chunk);
    //printf("%f %i\n", pos, cpl_table_get_nrow(chunk));
    //cpl_table_dump(chunk, 0, 100, NULL);
                // Alternative way
                /* 
                double wavel_lo = pos - hwidth;
                double wavel_up = pos + hwidth;
     
                // Last chunk is expanded to reach the end of the spectrum
                if (pos + pos_step > pos_end) {
                     wavel_up = pos_end + hwidth;
                }          
                cpl_table_unselect_all(spec_tab);
                cpl_table_or_selected_double (spec_tab, "WAVEL", CPL_NOT_LESS_THAN,
                                              wavel_lo);
                cpl_table_and_selected_double(spec_tab, "WAVEL", CPL_NOT_GREATER_THAN,
                                              wavel_up);
                chunk = cpl_table_extract_selected(spec_tab);
                */

                cpl_propertylist *chunk_col = cpl_propertylist_new();
                cpl_propertylist_append_bool(chunk_col, "FLUX", FALSE);
                cpl_table_sort(chunk, chunk_col);
                cpl_propertylist_delete(chunk_col);        
                int    med_row  = floor((int)cpl_table_get_nrow(chunk) / 2.0);
                double flux_ave = cpl_table_get_column_mean(chunk, "FLUX");
                double flux_med = 
                    (cpl_table_get_double(chunk, "FLUX", med_row,     NULL) +
                     cpl_table_get_double(chunk, "FLUX", med_row + 1, NULL)) 
                     / 2.0;
                if (flux_med - flux_ave > thres) {
                    cpl_table_set_double(line_temp, "WAVEL", line_num,
                        cpl_table_get_double(chunk, "WAVEL", 0, NULL));
                    cpl_table_set_double(line_temp, "PEAK",  line_num, 
                        cpl_table_get_double(chunk, "FLUX",  0, NULL));
                    line_num++;
                }
            }
            cpl_table_delete(chunk);

            // Loop printout
            int chunk_ord = (int)ceil(log10(chunk_num));
    //        if (i % (int)round(chunk_num/pow(10.0, (double)chunk_ord - 1.0)) == 0) {
            if ((i + 1) % (int)ceil(chunk_num/10.0) == 0 && i < chunk_num) {
                cpl_msg_warning(cpl_func, " %-*i chunks processed; %-*i remaining... ",
                             chunk_ord, i, chunk_ord, chunk_num - i);
            } 
            if (i == chunk_num) {
                cpl_msg_warning(cpl_func, "%i lines found...", (int)line_num);
            } 
        }
        line = cpl_table_extract(line_temp, 0, line_num);
        cpl_table_save(line,NULL,NULL,"line.fits",CPL_IO_DEFAULT);

        cpl_table_delete(line_temp); 
    }
cleanup:
    xsh_free_table(&spec_tab);
    xsh_free_table(&org_tab);
    return cpl_error_get_code();
}


/*----------------------------------------------------------------------------*/
/**
  @brief    Unit tests of xsh module
 */
/*----------------------------------------------------------------------------*/

int main(int argc, char* argv[])
{
	
    cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_WARNING);
    test_spectrum_detect_lines(argc,argv);

    return cpl_test_end(0);
}

/**@}*/
