/*
 * Copyright 2011-2012 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.
 */

#include <runtime_block_gateway.h>
#include <gr_io_signature.h>
#include <iostream>
#include <boost/bind.hpp>

/***********************************************************************
 * Helper routines
 **********************************************************************/
template <typename OutType, typename InType>
void copy_pointers(OutType &out, const InType &in){
    out.resize(in.size());
    for (size_t i = 0; i < in.size(); i++){
        out[i] = (void *)(in[i]);
    }
}

/***********************************************************************
 * The gr_block gateway implementation class
 **********************************************************************/
class runtime_block_gateway_impl : public runtime_block_gateway{
public:
    runtime_block_gateway_impl(
        gr_feval_ll *handler,
        const std::string &name,
        gr_io_signature_sptr in_sig,
        gr_io_signature_sptr out_sig,
        const gr_block_gw_work_type work_type,
        const unsigned factor
    ):
        gr_block(name, in_sig, out_sig),
        _handler(handler),
        _work_type(work_type)
    {
        switch(_work_type){
        case GR_BLOCK_GW_WORK_GENERAL:
            _decim = 1; //not relevant, but set anyway
            _interp = 1; //not relevant, but set anyway
            break;

        case GR_BLOCK_GW_WORK_SYNC:
            _decim = 1;
            _interp = 1;
            this->set_fixed_rate(true);
            break;

        case GR_BLOCK_GW_WORK_DECIM:
            _decim = factor;
            _interp = 1;
            break;

        case GR_BLOCK_GW_WORK_INTERP:
            _decim = 1;
            _interp = factor;
            this->set_output_multiple(_interp);
            break;
        }
    }

    /*******************************************************************
     * Overloads for various scheduler-called functions
     ******************************************************************/
    void forecast(
        int noutput_items,
        gr_vector_int &ninput_items_required
    ){
        switch(_work_type){
        case GR_BLOCK_GW_WORK_GENERAL:
            _message.action = gr_block_gw_message_type::ACTION_FORECAST;
            _message.forecast_args_noutput_items = noutput_items;
            _message.forecast_args_ninput_items_required = ninput_items_required;
            _handler->calleval(0);
            ninput_items_required = _message.forecast_args_ninput_items_required;
            return;

        default:
            unsigned ninputs = ninput_items_required.size();
            for (unsigned i = 0; i < ninputs; i++)
                ninput_items_required[i] = fixed_rate_noutput_to_ninput(noutput_items);
            return;
        }
    }

    int general_work(
        int noutput_items,
        gr_vector_int &ninput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items
    ){
        switch(_work_type){
        case GR_BLOCK_GW_WORK_GENERAL:
            _message.action = gr_block_gw_message_type::ACTION_GENERAL_WORK;
            _message.general_work_args_noutput_items = noutput_items;
            _message.general_work_args_ninput_items = ninput_items;
            copy_pointers(_message.general_work_args_input_items, input_items);
            _message.general_work_args_output_items = output_items;
            _handler->calleval(0);
            return _message.general_work_args_return_value;

        default:
            int r = work (noutput_items, input_items, output_items);
            if (r > 0) consume_each(r*_decim/_interp);
            return r;
        }
    }

    int work(
        int noutput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items
    ){
        _message.action = gr_block_gw_message_type::ACTION_WORK;
        _message.work_args_ninput_items = fixed_rate_noutput_to_ninput(noutput_items);
        if (_message.work_args_ninput_items == 0) return -1;
        _message.work_args_noutput_items = noutput_items;
        copy_pointers(_message.work_args_input_items, input_items);
        _message.work_args_output_items = output_items;
        _handler->calleval(0);
        return _message.work_args_return_value;
    }

    int fixed_rate_noutput_to_ninput(int noutput_items){
        return (noutput_items*_decim/_interp) + history() - 1;
    }

    int fixed_rate_ninput_to_noutput(int ninput_items){
        return std::max(0, ninput_items - (int)history() + 1)*_interp/_decim;
    }

    bool start(void){
        _message.action = gr_block_gw_message_type::ACTION_START;
        _handler->calleval(0);
        return _message.start_args_return_value;
    }

    bool stop(void){
        _message.action = gr_block_gw_message_type::ACTION_STOP;
        _handler->calleval(0);
        return _message.stop_args_return_value;
    }

    gr_block_gw_message_type &gr_block_message(void){
        return _message;
    }

private:
    gr_feval_ll *_handler;
    gr_block_gw_message_type _message;
    const gr_block_gw_work_type _work_type;
    unsigned _decim, _interp;
};

boost::shared_ptr<runtime_block_gateway> runtime_make_block_gateway(
    gr_feval_ll *handler,
    const std::string &name,
    gr_io_signature_sptr in_sig,
    gr_io_signature_sptr out_sig,
    const gr_block_gw_work_type work_type,
    const unsigned factor
){
    return boost::shared_ptr<runtime_block_gateway>(
        new runtime_block_gateway_impl(handler, name, in_sig, out_sig,
                                       work_type, factor)
    );
}