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

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

namespace hark
{
namespace node
{
    SmallerEqual::SmallerEqual():
        NodeDef("SmallerEqual")
    {
        *this
            << Terminal::in("INPUT1")
            << Terminal::in("INPUT2")
            >> Terminal::out("OUTPUT");
    }

    void
    SmallerEqual::reset()
    {
    }

    void
    SmallerEqual::initialize(InputDataBundle &input)
    {
        const auto &input1 = input["INPUT1"].last();
        const auto &input2 = input["INPUT2"].last();

        if(input1.is_type<float>() && input2.is_type<float>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<float>() <= src2.as<float>(); };
        }
        else if(input1.is_type<double>() && input2.is_type<double>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<double>() <= src2.as<double>(); };
        }
        else if(input1.is_type<short int>() && input2.is_type<short int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<short int>() <= src2.as<short int>(); };
        }
        else if(input1.is_type<unsigned short int>() && input2.is_type<unsigned short int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<unsigned short int>() <= src2.as<unsigned short int>(); };
        }
        else if(input1.is_type<int>() && input2.is_type<int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<int>() <= src2.as<int>(); };
        }
        else if(input1.is_type<unsigned int>() && input2.is_type<unsigned int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<unsigned int>() <= src2.as<unsigned int>(); };
        }
        else if(input1.is_type<long int>() && input2.is_type<long int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<long int>() <= src2.as<long int>(); };
        }
        else if(input1.is_type<unsigned long int>() && input2.is_type<unsigned long int>()){
            this->eval = [] (const data &src1, const data &src2){ return src1.as<unsigned long int>() <= src2.as<unsigned long int>(); };
        }
        else if((input1.is_type<float>() || input1.is_type<double>()) &&
                (input2.is_type<float>() || input2.is_type<double>())){
            // Cast to double if input1 and input2 are of different types and both input1 and input2 are floating point.
            this->eval = [] (const data &src1, const data &src2){ return src1.cast<double>() <= src2.cast<double>(); };
        }
        else if((input1.is_type<short int>() || input1.is_type<int>() || input1.is_type<long int>()) &&
                (input2.is_type<short int>() || input2.is_type<int>() || input2.is_type<long int>())){
            // Cast to long int if input1 and input2 are of different types and both input1 and input2 are signed integer type.
            this->eval = [] (const data &src1, const data &src2){ return src1.cast<long int>() <= src2.cast<long int>(); };
        }
        else if((input1.is_type<unsigned short int>() || input1.is_type<unsigned int>() || input1.is_type<unsigned long int>()) &&
                (input2.is_type<unsigned short int>() || input2.is_type<unsigned int>() || input2.is_type<unsigned long int>())){
            // Cast to unsigned long int if input1 and input2 are of different types and both input1 and input2 are unsigned integer type.
            this->eval = [] (const data &src1, const data &src2){ return src1.cast<unsigned long int>() <= src2.cast<unsigned long int>(); };
        }
        // If the types of input1 and input2 are different, and either input1 or input2 is an signed integer,
        // cast it to a floating point value and check if it is out of the maximum size signed integer type range.
        // If the value is in the range of the largest signed integer type, cast it to the largest signed integer type.
        else if((input1.is_type<float>() || input1.is_type<double>() ||
                 input1.is_type<unsigned short int>() || input1.is_type<unsigned int>() || input1.is_type<unsigned long int>()) &&
                (input2.is_type<short int>() || input2.is_type<int>() || input2.is_type<long int>())){
            this->eval = [] (const data &src1, const data &src2){
                // Known issue: This implementation has a problem comparing to integer values whose absolute value is greater than 53 bits.
                if(src1.cast<double>() <= static_cast<double>(std::numeric_limits<long int>::lowest())){ return true; }
                if(src1.cast<double>() > static_cast<double>(std::numeric_limits<long int>::max())){ return false; }
                return src1.cast<long int>() <= src2.cast<long int>();
            };
        }
        else if((input1.is_type<short int>() || input1.is_type<int>() || input1.is_type<long int>()) &&
                (input2.is_type<float>() || input2.is_type<double>() ||
                 input2.is_type<unsigned short int>() || input2.is_type<unsigned int>() || input2.is_type<unsigned long int>())){
            this->eval = [] (const data &src1, const data &src2){
                if(src2.cast<double>() < static_cast<double>(std::numeric_limits<long int>::lowest())){ return false; }
                if(src2.cast<double>() >= static_cast<double>(std::numeric_limits<long int>::max())){ return true; }
                return src1.cast<long int>() <= src2.cast<long int>();
            };
        }
        // If the types of input1 and input2 are different, and either input1 or input2 is an unsigned integer,
        // cast it to a floating point value and check if it is out of the maximum size unsigned integer type range.
        // If the value is in the range of the largest unsigned integer type, cast it to the largest unsigned integer type.
        else if((input1.is_type<float>() || input1.is_type<double>() ||
                 input1.is_type<short int>() || input1.is_type<int>() || input1.is_type<long int>()) &&
                (input2.is_type<unsigned short int>() || input2.is_type<unsigned int>() || input2.is_type<unsigned long int>())){
            this->eval = [] (const data &src1, const data &src2){
                // Known issue: This implementation has a problem comparing to integer values whose absolute value is greater than 53 bits.
                if(src1.cast<double>() <= static_cast<double>(std::numeric_limits<unsigned long int>::lowest())){ return true; }
                if(src1.cast<double>() > static_cast<double>(std::numeric_limits<unsigned long int>::max())){ return false; }
                return src1.cast<unsigned long int>() <= src2.cast<unsigned long int>();
            };
        }
        else if((input1.is_type<unsigned short int>() || input1.is_type<unsigned int>() || input1.is_type<unsigned long int>()) &&
                (input2.is_type<float>() || input2.is_type<double>() ||
                 input2.is_type<short int>() || input2.is_type<int>() || input2.is_type<long int>())){
            this->eval = [] (const data &src1, const data &src2){
                if(src2.cast<double>() < static_cast<double>(std::numeric_limits<unsigned long int>::lowest())){ return false; }
                if(src2.cast<double>() >= static_cast<double>(std::numeric_limits<unsigned long int>::max())){ return true; }
                return src1.cast<unsigned long int>() <= src2.cast<unsigned long int>();
            };
        }
        else{ // try cast it to double.
            this->eval = [] (const data &src1, const data &src2){ return src1.cast<double>() <= src2.cast<double>(); };
        }
    }

    bool
    SmallerEqual::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        const auto &input1 = input["INPUT1"].at(count);
        const auto &input2 = input["INPUT2"].at(count);

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

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

        output["OUTPUT"].push(data::create<bool>(this->eval(input1, input2)));

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

        return true;
    }
}
}

/* end of file */
