/* -*- c++ -*- */
/*
 * Copyright 2006,2008,2010,2013 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio 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, or (at your option)
 * any later version.
 *
 * GNU Radio 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 GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "test.h"
#include <gnuradio/io_signature.h>
#include <string.h>
#include <iostream>
#include <stdexcept>

namespace gr {

test_sptr make_test(const std::string& name,
                    int min_inputs,
                    int max_inputs,
                    unsigned int sizeof_input_item,
                    int min_outputs,
                    int max_outputs,
                    unsigned int sizeof_output_item,
                    unsigned int history,
                    unsigned int output_multiple,
                    double relative_rate,
                    bool fixed_rate,
                    consume_type_t cons_type,
                    produce_type_t prod_type)
{
    return gnuradio::get_initial_sptr(new test(name,
                                               min_inputs,
                                               max_inputs,
                                               sizeof_input_item,
                                               min_outputs,
                                               max_outputs,
                                               sizeof_output_item,
                                               history,
                                               output_multiple,
                                               relative_rate,
                                               fixed_rate,
                                               cons_type,
                                               prod_type));
}

test::test(const std::string& name,
           int min_inputs,
           int max_inputs,
           unsigned int sizeof_input_item,
           int min_outputs,
           int max_outputs,
           unsigned int sizeof_output_item,
           unsigned int history,
           unsigned int output_multiple,
           double relative_rate,
           bool fixed_rate,
           consume_type_t cons_type,
           produce_type_t prod_type)
    : block(name,
            io_signature::make(min_inputs, max_inputs, sizeof_input_item),
            io_signature::make(min_outputs, max_outputs, sizeof_output_item)),
      d_sizeof_input_item(sizeof_input_item),
      d_sizeof_output_item(sizeof_output_item),
      d_check_topology(true),
      d_consume_type(cons_type),
      d_min_consume(0),
      d_max_consume(0),
      d_produce_type(prod_type),
      d_min_produce(0),
      d_max_produce(0)
{
    set_history(history);
    set_output_multiple(output_multiple);
    set_relative_rate(relative_rate);
    set_fixed_rate(fixed_rate);
}

int test::general_work(int noutput_items,
                       gr_vector_int& ninput_items,
                       gr_vector_const_void_star& input_items,
                       gr_vector_void_star& output_items)
{
    // touch all inputs and outputs to detect segfaults
    unsigned ninputs = input_items.size();
    unsigned noutputs = output_items.size();
    for (unsigned i = 0; i < ninputs; i++) {
        char* in = (char*)input_items[i];
        if (ninput_items[i] < (int)(noutput_items + history())) {
            std::cerr << "ERROR: ninput_items[" << i << "] < noutput_items+history()"
                      << std::endl;
            std::cerr << "ninput_items[" << i << "] = " << ninput_items[i] << std::endl;
            std::cerr << "noutput_items+history() = " << noutput_items + history()
                      << std::endl;
            std::cerr << "noutput_items = " << noutput_items << std::endl;
            std::cerr << "history() = " << history() << std::endl;
            throw std::runtime_error("test");
        } else {
            for (int j = 0; j < ninput_items[i]; j++) {
                // Touch every available input_item
                // We use a class variable to avoid the compiler to optimize this away
                for (unsigned int k = 0; k < d_sizeof_input_item; k++)
                    d_temp = in[j * d_sizeof_input_item + k];
            }
            switch (d_consume_type) {
            case CONSUME_NOUTPUT_ITEMS:
                consume(i, noutput_items);
                break;
            case CONSUME_NOUTPUT_ITEMS_LIMIT_MAX:
                consume(i, std::min(noutput_items, d_max_consume));
                break;
            case CONSUME_NOUTPUT_ITEMS_LIMIT_MIN:
                consume(
                    i, std::min(std::max(noutput_items, d_min_consume), ninput_items[i]));
                break;
            case CONSUME_ALL_AVAILABLE:
                consume(i, ninput_items[i]);
                break;
            case CONSUME_ALL_AVAILABLE_LIMIT_MAX:
                consume(i, std::min(ninput_items[i], d_max_consume));
                break;
                /*         //This could result in segfault, uncomment if you want to test
                   this case CONSUME_ALL_AVAILABLE_LIMIT_MIN:
                             consume(i,std::max(ninput_items[i],d_max_consume));
                             break;*/
            case CONSUME_ZERO:
                consume(i, 0);
                break;
            case CONSUME_ONE:
                consume(i, 1);
                break;
            case CONSUME_MINUS_ONE:
                consume(i, -1);
                break;
            default:
                consume(i, noutput_items);
            }
        }
    }
    for (unsigned i = 0; i < noutputs; i++) {
        char* out = (char*)output_items[i];
        {
            for (int j = 0; j < noutput_items; j++) {
                // Touch every available output_item
                for (unsigned int k = 0; k < d_sizeof_output_item; k++)
                    out[j * d_sizeof_input_item + k] = 0;
            }
        }
    }
    // Now copy input to output until max ninputs or max noutputs is reached
    int common_nports = std::min(ninputs, noutputs);
    if (d_sizeof_output_item == d_sizeof_input_item)
        for (int i = 0; i < common_nports; i++) {
            memcpy(output_items[i], input_items[i], noutput_items * d_sizeof_input_item);
        }
    int noutput_items_produced = 0;
    switch (d_produce_type) {
    case PRODUCE_NOUTPUT_ITEMS:
        noutput_items_produced = noutput_items;
        break;
    case PRODUCE_NOUTPUT_ITEMS_LIMIT_MAX:
        noutput_items_produced = std::min(noutput_items, d_max_produce);
        break;
        /*         //This could result in segfault, uncomment if you want to test this
                     case PRODUCE_NOUTPUT_ITEMS_LIMIT_MIN:
                     noutput_items_produced=std::max(noutput_items,d_min_produce);
                     break;*/
    case PRODUCE_ZERO:
        noutput_items_produced = 0;
        break;
    case PRODUCE_ONE:
        noutput_items_produced = 1;
        break;
    case PRODUCE_MINUS_ONE:
        noutput_items_produced = -1;
        break;
    default:
        noutput_items_produced = noutput_items;
    }
    return noutput_items_produced;
}

} /* namespace gr */
