/* $Id: wavelength_calibration.cpp,v 1.5 2013/08/07 15:47:01 cgarcia Exp $
 *
 * This file is part of the MOSCA library
 * Copyright (C) 2013 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 Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * $Author: cgarcia $
 * $Date: 2013/08/07 15:47:01 $
 * $Revision: 1.5 $
 * $Name:  $
 */

#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <sstream>
#include <cpl_table.h>
#include <cpl_msg.h>
#include <cpl_polynomial.h>
#include "wavelength_calibration.h"

mosca::wavelength_calibration::wavelength_calibration()
{   
}

mosca::wavelength_calibration::wavelength_calibration
(const std::string& fits_disp_coeff)
{
    /* We read from extension 1 of slit location table */
    cpl_table* idscoeff = cpl_table_load(fits_disp_coeff.c_str(), 1, 1);

    
    cpl_size ndegree = cpl_table_get_ncol(idscoeff) - 2;

    for(cpl_size irow = 0 ; irow < cpl_table_get_nrow(idscoeff); irow++)
    {
        std::vector<double> pol_coeff;
        int null = 0;
        for(cpl_size idx_coeff = 0; idx_coeff < ndegree; idx_coeff++)
        {
            std::ostringstream colname;
            colname<<std::left<<"c"<<idx_coeff;
            pol_coeff.push_back(cpl_table_get_double
                    (idscoeff, colname.str().c_str(), irow, &null));
            if(null)
                break;
        }
        cpl_polynomial * poly = NULL;
        if(!null)
        {
            poly = cpl_polynomial_new(1);
            cpl_size idx;
            std::vector<double>::reverse_iterator coeff;
            for( idx = pol_coeff.size() - 1, coeff = pol_coeff.rbegin(); coeff != pol_coeff.rend();
                    ++coeff, --idx)
                cpl_polynomial_set_coeff(poly, &idx, *coeff);
        }
        m_wave_coeff.push_back(poly);
    }
}

mosca::wavelength_calibration::wavelength_calibration
(const cpl_table * idscoeff)
{
    from_idscoeff(idscoeff);
}

void mosca::wavelength_calibration::from_idscoeff(const cpl_table * idscoeff)
{
    cpl_size ndegree = cpl_table_get_ncol(idscoeff) - 2;

    for(cpl_size irow = 0 ; irow < cpl_table_get_nrow(idscoeff); irow++)
    {
        std::vector<double> pol_coeff;
        int null = 0;
        for(cpl_size idx_coeff = 0; idx_coeff < ndegree; idx_coeff++)
        {
            std::ostringstream colname;
            colname<<std::left<<"c"<<idx_coeff;
            pol_coeff.push_back(cpl_table_get_double
                    (idscoeff, colname.str().c_str(), irow, &null));
            if(null)
                break;
        }
        cpl_polynomial * poly = NULL;
        if(!null)
        {
            poly = cpl_polynomial_new(1);
            cpl_size idx;
            std::vector<double>::reverse_iterator coeff;
            for( idx = pol_coeff.size() - 1, coeff = pol_coeff.rbegin(); coeff != pol_coeff.rend();
                    ++coeff, --idx)
                cpl_polynomial_set_coeff(poly, &idx, *coeff);
        }
        m_wave_coeff.push_back(poly);
    }
}

mosca::wavelength_calibration::~wavelength_calibration()
{
    std::vector<cpl_polynomial *>::iterator poly;
    for(poly = m_wave_coeff.begin(); poly != m_wave_coeff.end(); poly++)
    {
        if(*poly != NULL)
            cpl_polynomial_delete(*poly);
    }
}

double mosca::wavelength_calibration::get_wave(double spatial_corrected_pos,
                                               double dispersion_pos) const
{
    double wavelength = -1; //Denotes and error. TODO
    /* TODO: I think this is wrong, the reference wavelength probably has to be added */
    size_t row = (size_t)(spatial_corrected_pos);
    if(row >= m_wave_coeff.size())
        return wavelength;
    
    cpl_polynomial * poly = m_wave_coeff[row];
    
    if(poly != NULL)
        wavelength = cpl_polynomial_eval_1d(poly, dispersion_pos, NULL);
    
    return wavelength;
}


void mosca::wavelength_calibration::min_max_wave(double& min_wave,
                                                 double& max_wave,
                                                 int size_dispersion,
                                                 int min_spa_row,
                                                 int max_spa_row) const
{
     
    std::vector<double> wave_pix_0;
    std::vector<double> wave_pix_size;
    /* TODO: Check this limits */
    for(int irow = min_spa_row ; irow < max_spa_row; ++irow)
    {
        cpl_polynomial * poly = m_wave_coeff[irow];
        if(poly != NULL)
        {
            double wave_0 = cpl_polynomial_eval_1d(poly, 0., NULL);
            double wave_size = cpl_polynomial_eval_1d(poly, (double)size_dispersion, NULL);
            wave_pix_0.push_back(wave_0);
            wave_pix_size.push_back(wave_size);
        }
    }
    min_wave = *std::min_element(wave_pix_0.begin(), wave_pix_0.end());
    max_wave = *std::max_element(wave_pix_size.begin(), wave_pix_size.end());
}

double mosca::wavelength_calibration::mean_dispersion(int size_dispersion,
                                                      int min_spa_row,
                                                      int max_spa_row) const
{
    double min_wave, max_wave;
    
    min_max_wave(min_wave, max_wave, size_dispersion, min_spa_row, max_spa_row);
    
    double mean_dispersion = (max_wave - min_wave) / size_dispersion;
    
    return mean_dispersion;
}
