/****************************************************************************
 *   This file is part of the aGrUM/pyAgrum library.                        *
 *                                                                          *
 *   Copyright (c) 2005-2026 by                                             *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *                                                                          *
 *   The aGrUM/pyAgrum library is free software; you can redistribute it    *
 *   and/or modify it under the terms of either :                           *
 *                                                                          *
 *    - the GNU Lesser General Public License as published by               *
 *      the Free Software Foundation, either version 3 of the License,      *
 *      or (at your option) any later version,                              *
 *    - the MIT license (MIT),                                              *
 *    - or both in dual license, as here.                                   *
 *                                                                          *
 *   (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html)    *
 *                                                                          *
 *   This aGrUM/pyAgrum library is distributed in the hope that it will be  *
 *   useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,          *
 *   INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
 *   FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT. IN NO EVENT SHALL THE   *
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,        *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  *
 *   OTHER DEALINGS IN THE SOFTWARE.                                        *
 *                                                                          *
 *   See LICENCES for more details.                                         *
 *                                                                          *
 *   SPDX-FileCopyrightText: Copyright 2005-2026                            *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *   SPDX-License-Identifier: LGPL-3.0-or-later OR MIT                      *
 *                                                                          *
 *   Contact  : info_at_agrum_dot_org                                       *
 *   homepage : http://agrum.gitlab.io                                      *
 *   gitlab   : https://gitlab.com/agrumery/agrum                           *
 *                                                                          *
 ****************************************************************************/

#pragma once


/**
 * @file
 * @brief Inline implementation of gum::PRMAggregate
 *
 * @author Lionel TORTI and Pierre-Henri WUILLEMIN(_at_LIP6)
 */

#include <climits>

#include <agrum/base/multidim/aggregators/amplitude.h>
#include <agrum/base/multidim/aggregators/and.h>
#include <agrum/base/multidim/aggregators/count.h>
#include <agrum/base/multidim/aggregators/exists.h>
#include <agrum/base/multidim/aggregators/forall.h>
#include <agrum/base/multidim/aggregators/max.h>
#include <agrum/base/multidim/aggregators/median.h>
#include <agrum/base/multidim/aggregators/min.h>
#include <agrum/base/multidim/aggregators/or.h>
#include <agrum/base/multidim/aggregators/sum.h>
#include <agrum/PRM/elements/PRMAggregate.h>   // to ease IDE parser

namespace gum {
  namespace prm {

    template < GUM_Numeric GUM_SCALAR >
    PRMAggregate< GUM_SCALAR >::PRMAggregate(std::string_view name,
                                             AggregateType    aggType,
                                             const PRMType&   rvType) :
        PRMClassElement< GUM_SCALAR >(name), _agg_type_(aggType), _type_(new PRMType(rvType)),
        _label_(std::shared_ptr< Idx >(new Idx(INT_MAX))) {
      GUM_CONSTRUCTOR(PRMAggregate);
      this->safeName_
          = (PRMObject::LEFT_CAST() + _type_->name() + PRMObject::RIGHT_CAST()).append(name);
      this->_type_->variable().setName(name);
    }

    template < GUM_Numeric GUM_SCALAR >
    PRMAggregate< GUM_SCALAR >::PRMAggregate(std::string_view name,
                                             AggregateType    aggType,
                                             const PRMType&   rvType,
                                             Idx              label) :
        PRMClassElement< GUM_SCALAR >(name), _agg_type_(aggType), _type_(new PRMType(rvType)),
        _label_(std::shared_ptr< Idx >(new Idx(label))) {
      GUM_CONSTRUCTOR(PRMAggregate);
      this->safeName_
          = (PRMObject::LEFT_CAST() + _type_->name() + PRMObject::RIGHT_CAST()).append(name);
      this->_type_->variable().setName(name);
    }

    template < GUM_Numeric GUM_SCALAR >
    PRMAggregate< GUM_SCALAR >::~PRMAggregate() {
      GUM_DESTRUCTOR(PRMAggregate);
      delete _type_;
    }

    template < GUM_Numeric GUM_SCALAR >
    PRMAggregate< GUM_SCALAR >::PRMAggregate(const PRMAggregate< GUM_SCALAR >& source) :
        PRMClassElement< GUM_SCALAR >(source) {
      GUM_CONS_CPY(PRMAggregate);
      GUM_ERROR(FatalError, "illegal call to gum::PRMAggregate copy constructor.")
    }

    template < GUM_Numeric GUM_SCALAR >
    PRMAggregate< GUM_SCALAR >&
        PRMAggregate< GUM_SCALAR >::operator=(const PRMAggregate< GUM_SCALAR >& source) {
      GUM_ERROR(FatalError, "illegal call to gum::PRMAggregate copy operator.")
    }

    template < GUM_Numeric GUM_SCALAR >
    typename PRMClassElement< GUM_SCALAR >::ClassElementType
        PRMAggregate< GUM_SCALAR >::elt_type() const {
      return this->prm_aggregate;
    }

    template < GUM_Numeric GUM_SCALAR >
    typename PRMAggregate< GUM_SCALAR >::AggregateType
        PRMAggregate< GUM_SCALAR >::agg_type() const {
      return _agg_type_;
    }

    template < GUM_Numeric GUM_SCALAR >
    Idx PRMAggregate< GUM_SCALAR >::label() const {
      if (*_label_ != INT_MAX) return *_label_;
      GUM_ERROR(OperationNotAllowed, "no label defined for this aggregate")
    }

    template < GUM_Numeric GUM_SCALAR >
    const std::string& PRMAggregate< GUM_SCALAR >::labelValue() const {
      return _label_value_;
    }

    template < GUM_Numeric GUM_SCALAR >
    void PRMAggregate< GUM_SCALAR >::setLabel(Idx idx) {
      (*_label_) = idx;
    }

    template < GUM_Numeric GUM_SCALAR >
    void PRMAggregate< GUM_SCALAR >::setLabel(std::string_view value) {
      _label_value_ = value;
    }

    template < GUM_Numeric GUM_SCALAR >
    bool PRMAggregate< GUM_SCALAR >::hasLabel() const {
      return *_label_ != INT_MAX;
    }

    template < GUM_Numeric GUM_SCALAR >
    bool PRMAggregate< GUM_SCALAR >::isDecomposable() const {
      switch (agg_type()) {
        case AggregateType::MIN : {
          return aggregator::Min< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::MAX : {
          return aggregator::Max< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::OR : {
          return aggregator::Or< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::AND : {
          return aggregator::And< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::AMPLITUDE : {
          return aggregator::Amplitude< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::MEDIAN : {
          return aggregator::Median< GUM_SCALAR >().isDecomposable();
        }
        case AggregateType::EXISTS : {
          return aggregator::Exists< GUM_SCALAR >(label()).isDecomposable();
        }
        case AggregateType::FORALL : {
          return aggregator::Forall< GUM_SCALAR >(label()).isDecomposable();
        }
        case AggregateType::COUNT : {
          return aggregator::Count< GUM_SCALAR >(label()).isDecomposable();
        }
        case AggregateType::SUM : {
          return aggregator::Sum< GUM_SCALAR >().isDecomposable();
        }
        default : {
          GUM_ERROR(OperationNotAllowed, "Unknown aggregator.")
        }
      }
    }

    template < GUM_Numeric GUM_SCALAR >
    PRMType& PRMAggregate< GUM_SCALAR >::type() {
      return *_type_;
    }

    template < GUM_Numeric GUM_SCALAR >
    const PRMType& PRMAggregate< GUM_SCALAR >::type() const {
      return *_type_;
    }

    template < GUM_Numeric GUM_SCALAR >
    Tensor< GUM_SCALAR >& PRMAggregate< GUM_SCALAR >::cpf() {
      GUM_ERROR(OperationNotAllowed, "This is an aggregate.")
    }

    template < GUM_Numeric GUM_SCALAR >
    const Tensor< GUM_SCALAR >& PRMAggregate< GUM_SCALAR >::cpf() const {
      GUM_ERROR(OperationNotAllowed, "This is an aggregate.")
    }

    template < GUM_Numeric GUM_SCALAR >
    MultiDimImplementation< GUM_SCALAR >* PRMAggregate< GUM_SCALAR >::buildImpl() const {
      switch (agg_type()) {
        case AggregateType::MIN : {
          return new aggregator::Min< GUM_SCALAR >();
        }
        case AggregateType::MAX : {
          return new aggregator::Max< GUM_SCALAR >();
        }
        case AggregateType::OR : {
          return new aggregator::Or< GUM_SCALAR >();
        }
        case AggregateType::AND : {
          return new aggregator::And< GUM_SCALAR >();
        }
        case AggregateType::AMPLITUDE : {
          return new aggregator::Amplitude< GUM_SCALAR >();
        }
        case AggregateType::MEDIAN : {
          return new aggregator::Median< GUM_SCALAR >();
        }
        case AggregateType::EXISTS : {
          return new aggregator::Exists< GUM_SCALAR >(label());
        }
        case AggregateType::FORALL : {
          return new aggregator::Forall< GUM_SCALAR >(label());
        }
        case AggregateType::COUNT : {
          return new aggregator::Count< GUM_SCALAR >(label());
        }
        case AggregateType::SUM : {
          return new aggregator::Sum< GUM_SCALAR >();
        }
        default : {
          GUM_ERROR(OperationNotAllowed, "Unknown aggregator.")
        }
      }
      return nullptr;
    }

    // See gum::PRMClassElement<GUM_SCALAR>::addParent_().
    template < GUM_Numeric GUM_SCALAR >
    void PRMAggregate< GUM_SCALAR >::addParent(const PRMClassElement< GUM_SCALAR >& elt) {}

    // See gum::PRMClassElement<GUM_SCALAR>::addChild_().
    template < GUM_Numeric GUM_SCALAR >
    void PRMAggregate< GUM_SCALAR >::addChild(const PRMClassElement< GUM_SCALAR >& elt) {}

    template < GUM_Numeric GUM_SCALAR >
    PRMAttribute< GUM_SCALAR >* PRMAggregate< GUM_SCALAR >::getCastDescendant() const {
      if (!type().isSubType()) {
        GUM_ERROR(OperationNotAllowed, "this PRMAggregate can not have cast descendant")
      }
      auto cast = new PRMScalarAttribute< GUM_SCALAR >(this->name(), type().superType());

      cast->addParent(*this);
      const DiscreteVariable& my_var   = type().variable();
      DiscreteVariable&       cast_var = cast->type().variable();
      Instantiation           inst(cast->cpf());

      for (inst.setFirst(); !inst.end(); inst.inc()) {
        if (type().label_map()[inst.val(my_var)] == inst.val(cast_var)) {
          cast->cpf().set(inst, 1);
        } else {
          cast->cpf().set(inst, 0);
        }
      }

      return cast;
    }

    template < GUM_Numeric GUM_SCALAR >
    std::shared_ptr< Idx > PRMAggregate< GUM_SCALAR >::sharedLabel() const {
      return _label_;
    }

    template < GUM_Numeric GUM_SCALAR >
    void PRMAggregate< GUM_SCALAR >::sharedLabel(std::shared_ptr< Idx > label) {
      this->_label_ = label;
    }

    template < GUM_Numeric GUM_SCALAR >
    typename PRMAggregate< GUM_SCALAR >::AggregateType
        PRMAggregate< GUM_SCALAR >::str2enum(std::string_view str) {
      const auto strLower = toLower(str);
      if (strLower == "min") {
        return AggregateType::MIN;
      } else if (strLower == "max") {
        return AggregateType::MAX;
      } else if (strLower == "count") {
        return AggregateType::COUNT;
      } else if (strLower == "exists") {
        return AggregateType::EXISTS;
      } else if (strLower == "or") {
        return AggregateType::OR;
      } else if (strLower == "and") {
        return AggregateType::AND;
      } else if (strLower == "forall") {
        return AggregateType::FORALL;
      } else if (strLower == "amplitude") {
        return AggregateType::AMPLITUDE;
      } else if (strLower == "median") {
        return AggregateType::MEDIAN;
      } else if (strLower == "sum") {
        return AggregateType::SUM;
      } else {
        std::string msg = "Unknown aggregate: ";
        msg.append(str);
        GUM_ERROR(NotFound, msg)
      }
    }

  } /* namespace prm */
} /* namespace gum */
