
#include <cmath>
#include <complex>
#include <algorithm>
#include <iostream>

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

namespace hark
{
namespace node
{
    CombineMap::CombineMap():
        NodeDef("CombineMap")
    {
        *this
            << Terminal::in("INPUT1")
            << Terminal::in("INPUT2")
            << Terminal::in("INPUT_PRIORITY").def("")
            << Terminal::in("DUPLICATE_ID_HANDLING_MODE").def("PRIORITIZE_INPUT1")
            << Terminal::in("INPUT1_OFFSET").def(0)
            << Terminal::in("INPUT2_OFFSET").def(10000)
            << Terminal::in("DEBUG").def(false)
            >> Terminal::out("OUTPUT");
    }

    void
    CombineMap::reset()
    {
    }

    void
    CombineMap::initialize(InputDataBundle &input)
    {
        this->debug = input["DEBUG"].last().as<bool>();

        const auto &hm = input["DUPLICATE_ID_HANDLING_MODE"].last().cast<std::string>();
        if(hm == "PRIORITIZE_INPUT1"){
            this->handling_mode = HANDLING_MODE::PRIORITIZE_INPUT1;
        }
        else if(hm == "PRIORITIZE_INPUT2"){
            this->handling_mode = HANDLING_MODE::PRIORITIZE_INPUT2;
        }
        else if(hm == "USE_OFFSET"){
            this->handling_mode = HANDLING_MODE::USE_OFFSET;
            this->input1_offset = input["INPUT1_OFFSET"].last().as<int>();
            this->input2_offset = input["INPUT2_OFFSET"].last().as<int>();
        }
        else{
            std::stringstream ss;
            ss << "CombineSource: Unexpected DUPLICATE_ID_HANDLING_MODE value '" << hm << "', expected 'PRIORITIZE_INPUT1', 'PRIORITIZE_INPUT2' or 'USE_OFFSET'.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
            throw RuntimeError(ss.str());
        }

        const auto &input_priority = input["INPUT_PRIORITY"].last().cast<std::string>();
        if(input_priority == "TOP"){
            if(this->debug){ std::cout << "A parameter with the old name was used. Set in alternative mode for compatibility. The new parameter name is 'DUPLICATE_ID_HANDLING_MODE' and the equivalent setting for 'TOP' is 'PRIORITIZE_INPUT1'." << std::endl; }
            this->handling_mode = HANDLING_MODE::PRIORITIZE_INPUT1;
        }
        else if(input_priority == "BOTTOM"){
            if(this->debug){ std::cout << "A parameter with the old name was used. Set in alternative mode for compatibility. The new parameter name is 'DUPLICATE_ID_HANDLING_MODE' and the equivalent setting for 'BOTTOM' is 'PRIORITIZE_INPUT2'." << std::endl; }
            this->handling_mode = HANDLING_MODE::PRIORITIZE_INPUT2;
        }
        else if(input_priority == ""){
            if(this->debug){ std::cout << "Since the parameter with the old name is not specified, the setting value of the new parameter is adopted." << std::endl; }
        }
        else{
            std::stringstream ss;
            ss << "CombineMap: Unexpected INPUT_PRIPRITY value '" << input_priority << "', expected 'TOP' or 'BOTTOM'.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
            throw RuntimeError(ss.str());
        }

        this->type = INPUT_TYPE::INPUT_UNDECIDED;
    }

    bool
    CombineMap::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        auto &input1 = input["INPUT1"].last().cast<std::map<int, data> >();
        auto &input2 = input["INPUT2"].last().cast<std::map<int, data> >();

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

        data out = data::create<std::map<int, data> >();

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

        if(this->type == INPUT_TYPE::INPUT_UNDECIDED){
            if(input1.empty() && input2.empty()){
                if(this->debug){ std::cout << "Both input1 and input2 are empty, so we defer type determination. The output will be an empty std::map<int, data> ." << std::endl; }
            }
            else if(input1.empty() && !input2.empty()){
                if(this->debug){ std::cout << "Since input1 is empty, we process it with the type of input2." << std::endl; }
                std::map<int, data>::const_iterator it = input2.begin();
                if(it->second.is_type<std::vector<float> >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_FLOAT;
                }
                else if(it->second.is_type<std::vector<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_COMPLEX_FLOAT;
                }
                else if(it->second.is_type<Matrix<float> >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_FLOAT;
                }
                else if(it->second.is_type<Matrix<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_COMPLEX_FLOAT;
                }
                else{
                    std::stringstream ss;
                    ss << "CombineMap: You entered an unsupported type. The CombineMap node supports 'std::map<int, std::vector<float> >', 'std::map<int, std::vector<std::complex<float> > >', 'std::map<int, Matrix<float> >' and 'std::map<int, Matrix<std::complex<float> > >' only.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
                    throw RuntimeError(ss.str());
                }
            }
            else if(!input1.empty() && input2.empty()){
                if(this->debug){ std::cout << "Since input2 is empty, we process it with the type of input1." << std::endl; }
                std::map<int, data>::const_iterator it = input1.begin();
                if(it->second.is_type<std::vector<float> >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_FLOAT;
                }
                else if(it->second.is_type<std::vector<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_COMPLEX_FLOAT;
                }
                else if(it->second.is_type<Matrix<float> >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_FLOAT;
                }
                else if(it->second.is_type<Matrix<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_COMPLEX_FLOAT;
                }
                else{
                    std::stringstream ss;
                    ss << "CombineMap: You entered an unsupported type. The CombineMap node supports 'std::map<int, std::vector<float> >', 'std::map<int, std::vector<std::complex<float> > >', 'std::map<int, Matrix<float> >' and 'std::map<int, Matrix<std::complex<float> > >' only.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
                    throw RuntimeError(ss.str());
                }
            }
            else if(!input1.empty() && !input2.empty()){
                if(this->debug){ std::cout << "Compare the types of input1 and input2 and if they match, process with that type." << std::endl; }
                std::map<int, data>::const_iterator it1 = input1.begin();
                std::map<int, data>::const_iterator it2 = input2.begin();
                if(it1->second.is_type<std::vector<float> >() && it2->second.is_type<std::vector<float> >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_FLOAT;
                }
                else if(it1->second.is_type<std::vector<std::complex<float> > >() && it2->second.is_type<std::vector<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_VECTOR_COMPLEX_FLOAT;
                }
                else if(it1->second.is_type<Matrix<float> >() && it2->second.is_type<Matrix<float> >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_FLOAT;
                }
                else if(it1->second.is_type<Matrix<std::complex<float> > >() && it2->second.is_type<Matrix<std::complex<float> > >()){
                    this->type = INPUT_TYPE::INPUT_MATRIX_COMPLEX_FLOAT;
                }
                else{
                    std::stringstream ss;
                    ss << "CombineMap: You entered an unsupported type or the types of input1 and input2 do not match. The CombineMap node supports 'std::map<int, std::vector<float> >', 'std::map<int, std::vector<std::complex<float> > >', 'std::map<int, Matrix<float> >' and 'std::map<int, Matrix<std::complex<float> > >' only.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
                    throw RuntimeError(ss.str());
                }
            }
            else{
                std::stringstream ss;
                ss << "CombineMap: It is impossible to enter this route. Check the program and check if the if and else if conditions are consistent.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
                throw RuntimeError(ss.str());
            }
        }

        if(this->type == INPUT_TYPE::INPUT_VECTOR_FLOAT){
            auto &out_map = out.cast<std::map<int, data> >();

            if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT1){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                    }
                }
            }
            else if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT2){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                    }
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                }
            }
            else if(this->handling_mode == HANDLING_MODE::USE_OFFSET){
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                    out_map.insert(std::pair<int, data>(it->first + this->input1_offset, data(std::move(out_vec))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    std::vector<float> out_vec = it->second.cast<std::vector<float> >();
                    out_map.insert(std::pair<int, data>(it->first + this->input2_offset, data(std::move(out_vec))));
                }
            }
        }
        else if(this->type == INPUT_TYPE::INPUT_VECTOR_COMPLEX_FLOAT){
            auto &out_map = out.cast<std::map<int, data> >();

            if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT1){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                    }
                }
            }
            else if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT2){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                    }
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_vec))));
                }
            }
            else if(this->handling_mode == HANDLING_MODE::USE_OFFSET){
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first + this->input1_offset, data(std::move(out_vec))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    std::vector<std::complex<float> > out_vec = it->second.cast<std::vector<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first + this->input2_offset, data(std::move(out_vec))));
                }
            }
        }
        else if(this->type == INPUT_TYPE::INPUT_MATRIX_FLOAT){
            auto &out_map = out.cast<std::map<int, data> >();

            if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT1){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                    }
                }
            }
            else if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT2){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                    }
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                }
            }
            else if(this->handling_mode == HANDLING_MODE::USE_OFFSET){
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                    out_map.insert(std::pair<int, data>(it->first + this->input1_offset, data(std::move(out_mat))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    Matrix<float> out_mat = it->second.cast<Matrix<float> >();
                    out_map.insert(std::pair<int, data>(it->first + this->input2_offset, data(std::move(out_mat))));
                }
            }
        }
        else if(this->type == INPUT_TYPE::INPUT_MATRIX_COMPLEX_FLOAT){
            auto &out_map = out.cast<std::map<int, data> >();

            if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT1){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                    }
                }
            }
            else if(this->handling_mode == HANDLING_MODE::PRIORITIZE_INPUT2){
                std::vector<int> skip_ids;
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    skip_ids.push_back(it->first);
                }

                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    if(*std::find(skip_ids.begin(), skip_ids.end(), it->first) != it->first){
                        Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                        out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                    }
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first, data(std::move(out_mat))));
                }
            }
            else if(this->handling_mode == HANDLING_MODE::USE_OFFSET){
                for(std::map<int, data>::const_iterator it = input1.begin(); it != input1.end(); ++it){
                    Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first + this->input1_offset, data(std::move(out_mat))));
                }
                for(std::map<int, data>::const_iterator it = input2.begin(); it != input2.end(); ++it){
                    Matrix<std::complex<float> > out_mat = it->second.cast<Matrix<std::complex<float> > >();
                    out_map.insert(std::pair<int, data>(it->first + this->input2_offset, data(std::move(out_mat))));
                }
            }
        }
        else if(this->type == INPUT_TYPE::INPUT_UNDECIDED){
            // The output std::map<int, data> is empty because both input1 and input2 are empty. That is, nothing is inserted.
        }
        else{
            std::stringstream ss;
            ss << "CombineMap: It was detected that a type that should not be supported yet has been set. Did you support more Map formats? Make sure that the else if condition INPUT_TYPE::INPUT_???? corresponding to the added std::map<int, ???> format is implemented.\n" << "FILE: " << __FILE__ << ", LINE: " << __LINE__;
            throw RuntimeError(ss.str());
        }

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