#include "ArrayOperation.hh"

////////////////////////////////////////
ArrayOperation::
ArrayOperation(){
    message_tag = "ArrayOperation>> ";
    v_op.reserve(4);
    v_op.push_back("+");
    v_op.push_back("-");
    v_op.push_back("*");
    v_op.push_back("/");
#ifdef MULTH
    omp_set_num_threads( MlfGetNumOfMulTh() );
#endif

}

////////////////////////////////////////
bool ArrayOperation::
ValidateOperator(std::string op) const{
    return (bool) count(v_op.begin(), v_op.end(), op);
}

////////////////////////////////////////
bool ArrayOperation::
ValidateKey(const ElementContainer& ec) const{
    if (ec.PutYKey() == "None"){
        return false;
    }
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
ValidateKey(const ElementContainerArray& eca, bool isLeftOperand) const{
    std::string operand_tag;
    if (isLeftOperand){
        operand_tag = "left operand";
    } else {
        operand_tag = "right operand";
    }
#ifdef MULTH
    std::vector<Int4> key_validation(eca.PutSize(), 1); // 0: keys are not set, 1: keys are set
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i< eca.PutSize(); ++i){
#else
  for (Int4 i=0; i< (Int4)(eca.PutSize()); ++i){
#endif
        key_validation[i] = (Int4) ValidateKey(eca.ref(i));
    }
    size_t index = distance(key_validation.begin(), find(key_validation.begin(), key_validation.end(), 0));
    if (index < eca.PutSize()){
        std::cout << message_tag << "Key error: " << operand_tag << " [" << index << "]" << std::endl;
        return false;
    }
#else
    for (UInt4 i=0; i< eca.PutSize(); ++i){
        if (! ValidateKey(eca.ref(i)) ){
            std::cout << message_tag << "Key error: " << operand_tag << " [" << i << "]" << std::endl;
            return false;
        }
    }
#endif
    return true;
}
////////////////////////////////////////
bool ArrayOperation::
ValidateKey(const ElementContainerMatrix& ecm, bool isLeftOperand) const{
    std::string operand_tag;
    if (isLeftOperand){
        operand_tag = "left operand";
    } else {
        operand_tag = "right operand";
    }
    for (UInt4 i=0; i<ecm.PutSize(); ++i){
        size_t size = ecm.ref(i).PutSize();
#ifdef MULTH
        std::vector<Int4> key_validation(size, 1); // 0: keys are not set, 1: keys are set
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
        for (UInt4 j=0; j<size; ++j){
#else
        for (Int4 j=0; j<size; ++j){
#endif
            key_validation[j] = (Int4) ValidateKey(ecm.ref(i).ref(j));
        }
        size_t index = distance(key_validation.begin(), find(key_validation.begin(), key_validation.end(), 0));
        if (index < size){
            std::cout << message_tag << "Key error: " << operand_tag << " [" << i << ", " << index << "]" << std::endl;
            return false;
        }
#else
        for (UInt4 j=0; j<size; ++j){
            if ( !ValidateKey(ecm.ref(i).ref(j)) ){
                std::cout << message_tag << "Key error: " << operand_tag << " [" << i << ", " << j << "]" << std::endl;
                return false;
            }
        }
#endif
    }
    return true;
}
////////////////////////////////////////
void ArrayOperation::
TakeoverMask(ElementContainer& ec_l, ElementContainer& ec_r){
    HeaderBase* h_ec_r = ec_r.PutHeaderPointer();
    if (h_ec_r->CheckKey("MASKED")){
        HeaderBase* h_ec_l = ec_l.PutHeaderPointer();
        if (h_ec_l->CheckKey("MASKED")){
            h_ec_l->OverWrite("MASKED", (h_ec_l->PutInt4("MASKED")||h_ec_r->PutInt4("MASKED")));
        } else {
            h_ec_l->Add("MASKED", h_ec_r->PutInt4("MASKED"));
        }
    }
}

////////////////////////////////////////
void ArrayOperation::
TakeoverMask(ElementContainerArray& eca_l, ElementContainerArray& eca_r){
    HeaderBase* h_eca_r = eca_r.PutHeaderPointer();
    if (h_eca_r->CheckKey("MASKED")){
        HeaderBase* h_eca_l = eca_l.PutHeaderPointer();
        if (h_eca_l->CheckKey("MASKED")){
            h_eca_l->OverWrite("MASKED", (h_eca_l->PutInt4("MASKED")||h_eca_r->PutInt4("MASKED")));
        } else {
            h_eca_l->Add("MASKED", h_eca_r->PutInt4("MASKED"));
        }
    }
}

////////////////////////////////////////
void ArrayOperation::
TakeoverMask(ElementContainerMatrix& ecm_l, ElementContainerMatrix& ecm_r){
    HeaderBase* h_ecm_r = ecm_r.PutHeaderPointer();
    if (h_ecm_r->CheckKey("MASKED")){
        HeaderBase* h_ecm_l = ecm_l.PutHeaderPointer();
        if (h_ecm_l->CheckKey("MASKED")){
            h_ecm_l->OverWrite("MASKED", (h_ecm_l->PutInt4("MASKED")||h_ecm_r->PutInt4("MASKED")));
        } else {
            h_ecm_l->Add("MASKED", h_ecm_r->PutInt4("MASKED"));
        }
    }
}

////////////////////////////////////////
void ArrayOperation::
_Calculate(ElementContainerArray& eca_l, std::string op, const Double d){
    if (op == "+"){
        eca_l.PlusMySelf(d);
    } else if (op == "-"){
        eca_l.PlusMySelf(-1.*d);
    } else if (op == "*"){
        eca_l.MulMySelf(d);
    } else if (op == "/"){
        eca_l.MulMySelf(1./d);
    }
    return;
}

////////////////////////////////////////
void ArrayOperation::
_Calculate(ElementContainerArray& eca_l, std::string op, const std::vector<Double>& vd){
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i<eca_l.PutSize(); ++i){
#else
    for (Int4 i=0; i<(Int4)(eca_l.PutSize()); ++i){
#endif
        if (op == "+"){
            eca_l.ref(i).PlusMySelf(vd[i]);
        } else if (op == "-"){
            eca_l.ref(i).PlusMySelf(-1.*vd[i]);
        } else if (op == "*"){
            eca_l.ref(i).MulMySelf(vd[i]);
        } else if (op == "/"){
            eca_l.ref(i).MulMySelf(1./vd[i]);
        }
    }
    return;
}

////////////////////////////////////////
void ArrayOperation::
_Calculate(ElementContainerArray& eca_l, std::string op, ElementContainer& ec){
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i<eca_l.PutSize(); ++i){
#else
    for (Int4 i=0; i<(Int4)(eca_l.PutSize()); ++i){
#endif
        if (op == "+"){
            eca_l.ref(i) += ec;
        } else if (op == "-"){
            eca_l.ref(i) -= ec;
        } else if (op == "*"){
            eca_l.ref(i) *= ec ;
        } else if (op == "/"){
            eca_l.ref(i) /= ec;
        }
        TakeoverMask(eca_l.ref(i), ec);
    }
    return;
}

////////////////////////////////////////
void ArrayOperation::
_Calculate(ElementContainerArray& eca_l, std::string op, ElementContainerArray& eca_r){
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i<eca_l.PutSize(); ++i){
#else
    for (Int4 i=0; i<(Int4)(eca_l.PutSize()); ++i){
#endif
        if (op == "+"){
            eca_l.ref(i) += (eca_r.ref(i));
        } else if (op == "-"){
            eca_l.ref(i) -= (eca_r.ref(i));
        } else if (op == "*"){
            eca_l.ref(i) *= (eca_r.ref(i));
        } else if (op == "/"){
            eca_l.ref(i) /= (eca_r.ref(i));
        }
        TakeoverMask(eca_l.ref(i), eca_r.ref(i));
    }
    return;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerArray& eca_l, std::string op, const Double d){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (! ValidateKey(eca_l)){
        return false;
    }
    _Calculate(eca_l, op, d);
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerArray& eca_l, std::string op, const std::vector<Double>& vd){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (eca_l.PutSize() != vd.size()){
        std::cout << message_tag << "Size error" << std::endl;
        return false;
    }
    if (! ValidateKey(eca_l)){
        return false;
    }
    _Calculate( eca_l, op, vd);
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerArray& eca_l, std::string op, ElementContainer& ec){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (! ValidateKey(eca_l)){
        return false;
    }
    if (! ValidateKey(ec)){
        std::cout << message_tag << "Key error: right operand" << std::endl;
        return false;
    }
    _Calculate(eca_l, op, ec);
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerArray& eca_l, std::string op, ElementContainerArray& eca_r){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (eca_l.PutSize() != eca_r.PutSize()){
        std::cout << message_tag << "Size error" << std::endl;
        return false;
    }
    if (! ValidateKey(eca_l)){
        return false;
    }
    if (! ValidateKey(eca_r, false)){
        return false;
    }
    _Calculate(eca_l, op, eca_r);
    TakeoverMask(eca_l, eca_r);
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, const Double d){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
#else
    for (Int4 i=0; i<(Int4)(ecm_l.PutSize()); ++i){
#endif
        _Calculate(ecm_l.ref(i), op, d);
    }
/*
    if (op == "+"){
        ecm_l.PlusMySelf(d);
    } else if (op == "-"){
        ecm_l.PlusMySelf(-1.*d);
    } else if (op == "*"){
        ecm_l.MulMySelf(d);
    } else if (op == "/"){
        ecm_l.MulMySelf(1./d);
    }
*/
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, const std::vector<Double>& vd, bool direction){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (direction){
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
            if (ecm_l(i)->PutSize() != vd.size()){
                std::cout << message_tag << "Size error" << std::endl;
                return false;
            }
        }
    } else {
        if (ecm_l.PutSize() != vd.size()){
            std::cout << message_tag << "Size error" << std::endl;
            return false;
        }
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
    if (direction){
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
            _Calculate(ecm_l.ref(i), op, vd);
        }
    } else {
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
#else
        for (Int4 i=0; i<(Int4)(ecm_l.PutSize()); ++i){
#endif
            _Calculate(ecm_l.ref(i), op, vd[i]);
        }
    }
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, ElementContainer& ec){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (! ValidateKey(ec)){
        std::cout << message_tag << "Key error: right operand" << std::endl;
        return false;
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
        _Calculate(ecm_l.ref(i), op, ec);
    }
    return true;
}
////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, ElementContainerArray& eca_r, bool direction){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (direction){
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
            if (ecm_l(i)->PutSize() != eca_r.PutSize()){
                std::cout << message_tag << "Size error" << std::endl;
                return false;
            }
        }
    } else {
        if (ecm_l.PutSize() != eca_r.PutSize()){
            std::cout << message_tag << "Size error" << std::endl;
            return false;
        }
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
    if (! ValidateKey(eca_r, false)){
        return false;
    }
    if (direction){
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
            _Calculate(ecm_l.ref(i), op, eca_r);
        }
    } else {
        for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
            _Calculate(ecm_l.ref(i), op, eca_r.ref(i));
        }
    }
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, ElementContainerMatrix& ecm_r){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (ecm_l.PutSize() != ecm_r.PutSize()){
        std::cout << message_tag << "Size error" << std::endl;
        return false;
    }
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
        if (ecm_l.ref(i).PutSize() != ecm_r.ref(i).PutSize()){
            std::cout << message_tag << "Size error" << std::endl;
            return false;
        }
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
    if (! ValidateKey(ecm_r, false)){
        return false;
    }
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
        _Calculate(ecm_l.ref(i), op, ecm_r.ref(i));
    }
    TakeoverMask(ecm_l, ecm_r);
    return true;
}

////////////////////////////////////////
bool ArrayOperation::
Calculate(ElementContainerMatrix& ecm_l, std::string op, const std::vector< std::vector<Double> >& vvd){
    if (! ValidateOperator(op)){
        std::cout << message_tag << "Unavailable operator" << std::endl;
        return false;
    }
    if (ecm_l.PutSize() != vvd.size()){
        std::cout << message_tag << "Size error" << std::endl;
        return false;
    }
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
        if (ecm_l.ref(i).PutSize() != vvd[i].size()){
            std::cout << message_tag << "Size error" << std::endl;
            return false;
        }
    }
    if (! ValidateKey(ecm_l)){
        return false;
    }
    for (UInt4 i=0; i<ecm_l.PutSize(); ++i){
        _Calculate(ecm_l.ref(i), op, vvd[i]);
    }
    return true;
}
