/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA 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 3 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 MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdexcept>
#include <iostream>
#include <mia/core/gsl_wavelet.hh>
#include <gsl/gsl_wavelet.h>
#include <vector>

using std::invalid_argument;
using std::vector;
using std::copy;

namespace gsl
{


struct C1DWaveletImpl {
public:
       C1DWaveletImpl(EWaveletType wt, size_t k);

       ~C1DWaveletImpl();

       template <typename T>
       vector<double> forward(const vector<T>& x) const;

       vector<double> backward(const vector<double>& x) const;

private:
       gsl_wavelet *m_wavelet;
};


C1DWavelet::C1DWavelet(EWaveletType wt, size_t k):
       impl(new C1DWaveletImpl(wt, k))
{
}

C1DWavelet::~C1DWavelet()
{
       delete impl;
}

std::vector<double> C1DWavelet::backward(const std::vector<double>& x) const
{
       return impl->backward(x);
}

std::vector<double> C1DWavelet::forward(const std::vector<double>& x) const
{
       return impl->forward(x);
}

std::vector<double> C1DWavelet::forward(const std::vector<float>& x) const
{
       return impl->forward(x);
}

C1DWaveletImpl::C1DWaveletImpl(EWaveletType wt, size_t k):
       m_wavelet(NULL)
{
       switch (wt) {
       case wt_haar:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_haar_centered, k);
              break;

       case wt_haar_centered:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_haar_centered, k);
              break;

       case wt_daubechies:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_daubechies, k);
              break;

       case wt_daubechies_centered:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_daubechies_centered, k);
              break;

       case wt_bspline:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_bspline_centered, k);
              break;

       case wt_bspline_centered:
              m_wavelet = gsl_wavelet_alloc (gsl_wavelet_bspline_centered, k);
              break;

       default:
              throw invalid_argument("C1DWavelet: Unknown wavelet type requested");
       }

       if (!m_wavelet)
              throw invalid_argument("C1DWavelet: Unsupported member or insufficienr memory");
}


C1DWaveletImpl::~C1DWaveletImpl()
{
       gsl_wavelet_free(m_wavelet);
}

vector<double> C1DWaveletImpl::backward(const vector<double>& x) const
{
       vector<double> result(x);
       gsl_wavelet_workspace *ws = gsl_wavelet_workspace_alloc (x.size());
       gsl_wavelet_transform_inverse(m_wavelet, &result[0], 1, x.size(), ws);
       gsl_wavelet_workspace_free (ws);
       return result;
}

template <typename T>
vector<double> C1DWaveletImpl::forward(const vector<T>& x) const
{
       size_t s = x.size();
       size_t s1 = 1;

       while (s) {
              s /= 2;
              s1 *= 2;
       }

       if (s1 < x.size())
              s1 *= 2;

       vector<double> x_size_to_pow2(s1);
       copy(x.begin(), x.end(), x_size_to_pow2.begin());
       gsl_wavelet_workspace *ws = gsl_wavelet_workspace_alloc (s1);
       gsl_wavelet_transform_forward (m_wavelet, &x_size_to_pow2[0], 1, s1, ws);
       gsl_wavelet_workspace_free (ws);
       return x_size_to_pow2;
}



}
