/*
   HARK Library
   Copyright (C) 2021 Honda Research Institute Japan Inc.
 */

#include "hark-lib/node/SourceIntervalExtender.h"

namespace hark
{
namespace node
{
    SourceIntervalExtender::SourceIntervalExtender():
        NodeDef("SourceIntervalExtender")
    {
        *this
            << Terminal::in("SOURCES")
            << Terminal::in("PREROLL_LENGTH").def(50)
            >> Terminal::out("OUTPUT");
    }

    data
    SourceIntervalExtender::find_source(std::vector<data> &sources, int &id)
    {
        for(std::vector<data>::iterator it = sources.begin(); it != sources.end(); ++it){
            Source src = it->cast<Source>();
            if(src.id == id){ return data(std::move(src)); } 
        }
        return data::create<bool>(false);
    }

    void
    SourceIntervalExtender::reset()
    {
    }

    void
    SourceIntervalExtender::initialize(InputDataBundle &input)
    {
        this->preroll_length = input["PREROLL_LENGTH"].last().as<int>();

        input["SOURCES"].set_look_window(1, this->preroll_length);
    }

    bool
    SourceIntervalExtender::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        // for debug
        /*
        if(!input["SOURCES"].is_source_ready()){ return false; }
        data in = input["SOURCES"].last();
        if(in.is_type<std::vector<Source> >()){
            throw RuntimeError("The previous node output was std::vector<Source>, but the correct output is std::vector<data>. This may be a bug!");
        }
        else if(in.is_type<std::vector<data> >()){
            const auto &in_vec = in.cast<std::vector<data> >();
            for(std::vector<data>::const_iterator it = in_vec.begin(); it != in_vec.end(); ++it){
                if(it->is_type<Source>()){ } // pass
                else{
                    std::cout << "std::vector<data>'s data TYPE: " << it->get_type().name() << std::endl;
                }
            }
        }
        else{
            std::cout << "INPUT TYPE: " << in.get_type().name() << std::endl;
        }
        */

#ifdef DEBUG
        ptime start_time;
        ptime end_time;
        start_time = microsec_clock::local_time();
#endif

        if(count == 0){ this->initialize(input); }

        if(!input["SOURCES"].is_source_ready()){ return false; }

        auto out = data::create<std::vector<data> >();
        auto &output_sources = out.cast<std::vector<data> >();

        const auto &input_sources = input["SOURCES"].at(count).cast<std::vector<data> >();
        for(auto it: input_sources){ output_sources.push_back(it); }

        const auto &later_sources = input["SOURCES"].at(count + this->preroll_length).cast<std::vector<data> >();

        if(count > 0){
            std::vector<data> last_sources = prev_out.cast<std::vector<data> >();

            for(std::map<int, int>::iterator it = this->remaining_time.begin(); it != this->remaining_time.end(); ++it){
                int id = it->first;
                it->second--;
                data cur_src_data = this->find_source(output_sources, id);
                if(!cur_src_data.is_type<Source>()){
                    data last_src_data = this->find_source(last_sources, id);
                    if(last_src_data.is_type<Source>()){
                        output_sources.push_back(last_src_data);
                    }
                }
            }

            for(std::map<int, int>::iterator it = this->remaining_time.begin(); it != this->remaining_time.end();){
                if(it->second == 0){ this->remaining_time.erase(it++); }
                else{ ++it; }
            }
        }

        for(std::vector<data>::const_iterator it = later_sources.begin(); it != later_sources.end(); ++it){
            Source later_src = it->cast<Source>();
            data cur_src_data = this->find_source(output_sources, later_src.id);
            if(!cur_src_data.is_type<Source>()){
                output_sources.push_back(data(std::move(later_src)));
                this->remaining_time[later_src.id] = this->preroll_length;
            }
        }

        prev_out = out;
        output["OUTPUT"].push(out);

#ifdef DEBUG
        end_time = microsec_clock::local_time();
        std::cout << end_time - start_time << std::endl;
#endif

        return true;
    }
}
}

/* end of file */
