/* -*- 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/>.
 *
 */

#define BOOST_TEST_DYN_LINK
#include <cassert>
#include <iostream>
#include <cmath>
#include <numeric>
#include <climits>

#include <boost/test/test_tools.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/floating_point_comparison.hpp>


#include <mia/core/cmdlineparser.hh>
#include <mia/core/fifofilter.hh>

#include <mia/core/fifofilter.cxx>

using namespace std;
using namespace mia;


typedef TFifoFilter<char> CCharFifoFilter;
typedef std::shared_ptr<TFifoFilter<char>>  PCharFifoFilter;

class CCharFifoFilterSink : public CCharFifoFilter
{
public:
       CCharFifoFilterSink();
       const string result();
private:
       virtual void do_push(char c);

       string m_result;

};

CCharFifoFilterSink::CCharFifoFilterSink(): CCharFifoFilter(0, 1, 0)
{
}

const string CCharFifoFilterSink::result()
{
       return m_result;
}

void CCharFifoFilterSink::do_push(char c)
{
       m_result.append(1, c);
}



BOOST_AUTO_TEST_CASE( test_sink_mechanics )
{
       CCharFifoFilterSink sink;

       for ( size_t i = 0; i < 10; ++i)
              sink.push('a');

       sink.finalize();
       BOOST_CHECK_EQUAL(sink.result(), string(10, 'a'));
}

typedef TFifoFilter<int> CIntFifoFilter;
typedef std::shared_ptr<TFifoFilter<int>>  PIntFifoFilter;
typedef TFifoFilterSink<int> CIntFifoFilterSink;

class CAddSomeFifoFilter : public CIntFifoFilter
{
public:
       CAddSomeFifoFilter(int value);
private:
       virtual int do_filter();
       virtual void do_push(int c);
       virtual void evaluate(size_t slice);

       int m_value;
       int m_buf;
};

CAddSomeFifoFilter::CAddSomeFifoFilter(int value):
       CIntFifoFilter(0, 1, 0),
       m_value(value),
       m_buf(0)
{
}

int CAddSomeFifoFilter::do_filter()
{
       cvdebug() << "do_filter()" << m_buf << "\n";
       return m_buf;
}


void CAddSomeFifoFilter::do_push(int c)
{
       cvdebug() << "do_push()" << m_buf << " -> " << c << "\n";
       m_buf = c;
}


void CAddSomeFifoFilter::evaluate(size_t /*slice*/)
{
       cvdebug() << "CAddSomeFifoFilter::evaluate" << "\n";
       m_buf += m_value;
}

class CMeanIntFifoFilter : public CIntFifoFilter
{
public:
       CMeanIntFifoFilter(size_t width);
private:
       virtual int do_filter();
       virtual void shift_buffer();
       virtual void do_push(int c);
       vector<int> m_buf;
};

CMeanIntFifoFilter::CMeanIntFifoFilter(size_t hwidth):
       CIntFifoFilter(2 * hwidth + 1, hwidth + 1, 0),
       m_buf(2 * hwidth + 1)
{
}

int CMeanIntFifoFilter::do_filter()
{
       int result;
       result = accumulate(m_buf.begin() + get_start(), m_buf.begin() + get_end(), 0);
       result /= get_end() - get_start();
       return result;
}

void CMeanIntFifoFilter::shift_buffer()
{
       copy_backward(m_buf.begin(), m_buf.end() - 1, m_buf.end());
}

void CMeanIntFifoFilter::do_push(int c)
{
       m_buf[0] = c;
}


BOOST_AUTO_TEST_CASE( test_mean )
{
       int test_input[10] = {
              2, 1, 3, 6, 13, 7, 6, 13, 11, 18
       };
       int test_result[10] = {
              2, 3, 5, 6, 7, 9, 10, 11, 12, 14
       };
       CMeanIntFifoFilter filter(2);
       CIntFifoFilterSink *sink = new CIntFifoFilterSink();
       CIntFifoFilter::Pointer psink(sink);
       filter.append_filter(psink);

       for ( size_t i = 0; i < 10; ++i)
              filter.push(test_input[i]);

       filter.finalize();
       const vector<int>& result = sink->result();
       BOOST_CHECK_EQUAL(result.size(), 10u);

       for (int i = 0; i < 10; ++i)
              BOOST_CHECK_EQUAL(result[i], test_result[i]);
}


class CMeanAddIntFifoFilter : public CIntFifoFilter
{
public:
       CMeanAddIntFifoFilter(size_t width);
private:
       virtual int do_filter();
       virtual void shift_buffer();
       virtual void evaluate(size_t slice);
       virtual void do_push(int c);

       vector<int> m_buf;
};

CMeanAddIntFifoFilter::CMeanAddIntFifoFilter(size_t hwidth):
       CIntFifoFilter(2 * hwidth + 1, hwidth + 1, hwidth),
       m_buf(3 * hwidth + 1)
{
       fill(m_buf.begin(), m_buf.end(), 400);
}

int CMeanAddIntFifoFilter::do_filter()
{
       cvdebug() << get_start() << ", " <<  get_end() << "\n";
       int result;
       result = accumulate(m_buf.begin() + get_start(), m_buf.begin() + get_end(), 0);
       result /= get_end() - get_start();
       return result;
}

void CMeanAddIntFifoFilter::shift_buffer()
{
       copy_backward(m_buf.begin(), m_buf.end() - 1, m_buf.end());
}

void CMeanAddIntFifoFilter::evaluate(size_t slice)
{
       m_buf[slice] += 1;
}

void CMeanAddIntFifoFilter::do_push(int c)
{
       m_buf[0] = c;
}



class CTrackCallersFilter : public CIntFifoFilter
{
public:
       CTrackCallersFilter();
       const string get_starts() const;
       const string get_ends() const;
       const string get_callseries() const;
private:
       virtual void do_initialize(int x);
       virtual void post_finalize();
       virtual int do_filter();
       virtual void shift_buffer();
       virtual void evaluate(size_t slice);
       virtual void do_push(int c);
       stringstream m_starts;
       stringstream m_ends;
       stringstream m_callseries;
};


CTrackCallersFilter::CTrackCallersFilter():
       CIntFifoFilter(3, 2, 2)
{
}

void CTrackCallersFilter::do_initialize(int /*x*/)
{
       m_callseries << 'b';
}

void CTrackCallersFilter::post_finalize()
{
       m_callseries << 'x';
}

const string CTrackCallersFilter::get_starts() const
{
       return m_starts.str();
}

const string CTrackCallersFilter::get_ends() const
{
       return m_ends.str();
}

const string CTrackCallersFilter::get_callseries() const
{
       return m_callseries.str();
}


int CTrackCallersFilter::do_filter()
{
       m_callseries << 'f';
       m_starts << get_start() << " ";
       m_ends   << get_end()   << " ";
       return 0;
}

void CTrackCallersFilter::shift_buffer()
{
       m_callseries << 's';
}

void CTrackCallersFilter::evaluate(size_t /*slice*/)
{
       m_callseries << 'e';
}

void CTrackCallersFilter::do_push(int /*c*/)
{
       m_callseries << 'p';
}


BOOST_AUTO_TEST_CASE( test_callseries )
{
       CTrackCallersFilter filter;

       for ( size_t i = 0; i < 5; ++i)
              filter.push(i);

       filter.finalize();
       BOOST_CHECK_EQUAL(filter.get_starts(), "2 2 2 2 3 ");
       BOOST_CHECK_EQUAL(filter.get_ends(),   "4 5 5 5 5 ");
       BOOST_CHECK_EQUAL(filter.get_callseries(), "bpspspespefspefeesfsfsfx");
}


BOOST_AUTO_TEST_CASE( test_chaining)
{
       CAddSomeFifoFilter filter(1);
       CIntFifoFilter::Pointer filter2(new CAddSomeFifoFilter(2));
       CIntFifoFilterSink::Pointer sink = CIntFifoFilterSink::Pointer(new CIntFifoFilterSink());
       filter.append_filter(filter2);
       filter.append_filter(sink);
       filter.push(3);
       filter.finalize();
       const vector< int >& result = sink->result();
       BOOST_REQUIRE(result.size() == 1);
       BOOST_CHECK_EQUAL(result[0], 6);
}



BOOST_AUTO_TEST_CASE( test_mean_add )
{
       int test_input[10] = {
              2, 1, 3, 6, 13, 7, 6, 13, 11, 18
       };
       int test_result[10] = {
              2, 3, 5, 6, 7, 9, 10, 11, 12, 14
       };
       CMeanIntFifoFilter filter(2);
       CIntFifoFilterSink *sink = new CIntFifoFilterSink();
       CIntFifoFilter::Pointer psink(sink);
       filter.append_filter(psink);

       for ( size_t i = 0; i < 10; ++i)
              filter.push(test_input[i]);

       filter.finalize();
       const vector<int>& result = sink->result();
       BOOST_CHECK_EQUAL(result.size(), 10u);

       for (int i = 0; i < 10; ++i)
              BOOST_CHECK_EQUAL(result[i], test_result[i]);
}
