/****************************************************************************
 *   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 O3prmReader.
 *
 * @author Pierre-Henri WUILLEMIN(_at_LIP6) and Lionel TORTI
 */

// to ease Parser
#include <agrum/PRM/o3prm/O3prmBNReader.h>

namespace gum {
  template < GUM_Numeric GUM_SCALAR >
  std::string O3prmBNReader< GUM_SCALAR >::_getVariableName_(std::string_view path,
                                                             std::string_view type,
                                                             std::string_view name,
                                                             std::string_view toRemove) {
    std::string res(path);
    res.append(name);   // path ends up with a "."
    if (!toRemove.empty()) {
      if (res.substr(0, toRemove.size()) == toRemove) { res = res.substr(toRemove.size()); }
    }
    return res;
  }

  template < GUM_Numeric GUM_SCALAR >
  std::string O3prmBNReader< GUM_SCALAR >::_getInstanceName_(std::string_view classname) {
    std::string res{classname.substr(0, 4)};
    std::transform(res.begin(), res.end(), res.begin(), ::tolower);
    return res;
  }

  template < GUM_Numeric GUM_SCALAR >
  std::string O3prmBNReader< GUM_SCALAR >::_getEntityName_(std::string_view filename) {
    auto b = filename.find_last_of("/\\");
    auto e = filename.find_last_of(".") - 1;
    GUM_ASSERT(e > b);   // we are waiting ../../basename.o3prm
    return std::string{filename.substr(b + 1, e - b)};
  }

  template < GUM_Numeric GUM_SCALAR >
  O3prmBNReader< GUM_SCALAR >::O3prmBNReader(BayesNet< GUM_SCALAR >* bn,
                                             const std::string&      filename,
                                             const std::string&      entityName,
                                             const std::string&      classpath) :
      BNReader< GUM_SCALAR >(bn, filename) {
    GUM_CONSTRUCTOR(O3prmBNReader);
    _bn_         = bn;
    _filename_   = filename;
    _entityName_ = entityName.empty() ? _getEntityName_(filename) : entityName;
    _classpath_  = classpath;
  }

  template < GUM_Numeric GUM_SCALAR >
  O3prmBNReader< GUM_SCALAR >::~O3prmBNReader() {
    GUM_DESTRUCTOR(O3prmBNReader);
  }

  /// parse.
  /// @return the number of detected errors
  /// @throws IOError if file not exists
  template < GUM_Numeric GUM_SCALAR >
  Size O3prmBNReader< GUM_SCALAR >::proceed() {
    prm::o3prm::O3prmReader< GUM_SCALAR > reader;
    if (_classpath_ != "") { reader.addClassPath(_classpath_); }
    reader.readFile(_filename_);
    gum::prm::PRM< GUM_SCALAR >* prm = reader.prm();
    _errors_                         = reader.errorsContainer();


    if (errors() == 0) {
      std::string instanceName = "";
      if (prm->isSystem(_entityName_)) {
        _generateBN_(prm->getSystem(_entityName_));
      } else if (prm->isClass(_entityName_)) {
        _errors_.addWarning("No system '" + _entityName_
                                + "' found but class found. Generating unnamed instance.",
                            _filename_,
                            0,
                            0);
        gum::prm::PRMSystem< GUM_SCALAR > s("S_" + _entityName_);
        instanceName = _getInstanceName_(_entityName_);
        auto i = new gum::prm::PRMInstance< GUM_SCALAR >(instanceName, prm->getClass(_entityName_));
        s.add(i);
        _generateBN_(s);
        instanceName += ".";   // to be removed in  _getVariableName_
      } else if (prm->classes().size() == 1) {
        const std::string& entityName = (*prm->classes().begin())->name();
        ParseError warn(false,
                        "Unique class '" + entityName + "' found. Generating unnamed instance.",
                        _filename_,
                        0);
        _errors_.add(warn);

        gum::prm::PRMSystem< GUM_SCALAR > s("S_" + entityName);
        instanceName = _getInstanceName_(entityName);
        auto i = new gum::prm::PRMInstance< GUM_SCALAR >(instanceName, prm->getClass(entityName));
        s.add(i);
        _generateBN_(s);

        // force the name of the BN to be the name of the class instead of the name
        // of the file
        _bn_->setProperty("name", entityName);
        instanceName += ".";   // to be removed in  _getVariableName_
      } else {
        _errors_.addError("Neither system nor class '" + _entityName_
                              + "' and more than one class.",
                          _filename_,
                          0,
                          0);
      }

      // renaming variables in th BN
      gum::Set< std::string > names;
      for (auto node: _bn_->nodes()) {
        // keeping the complete name in description
        const std::string& nn = _bn_->variable(node).name();
        _bn_->variable(node).setDescription(nn);

        // trying to simplify the
        auto start = nn.find_first_of('(');
        auto end   = nn.find_first_of(')');
        if (0 < start && start < end && end < nn.size()) {
          auto path = nn.substr(0, start);
          auto type = nn.substr(start + 1, end - start - 1);
          auto name = nn.substr(end + 1, std::string::npos);

          std::string newNameRadical = _getVariableName_(path, type, name, instanceName);

          std::string newName = newNameRadical;
          // forcing newName to be unique
          int num = 0;
          while (names.contains(newName)) {
            newName = newNameRadical + std::to_string(++num);
          }

          names.insert(newName);
          _bn_->changeVariableName(node, newName);
        } else {
          _errors_.addError("Name " + nn + " cannot be simplified.", _filename_, 0, 0);
        }
      }
    }

    delete prm;

    return errors();
  }

  template < GUM_Numeric GUM_SCALAR >
  void O3prmBNReader< GUM_SCALAR >::_generateBN_(prm::PRMSystem< GUM_SCALAR >& system) {
    system.instantiate();
    BayesNetFactory< GUM_SCALAR > factory(_bn_);
    system.groundedBN(factory);
    _bn_->setProperty("name", _entityName_);
  }

  template < GUM_Numeric GUM_SCALAR >
  Size O3prmBNReader< GUM_SCALAR >::errors() {
    return _errors_.error_count;
  }

  template < GUM_Numeric GUM_SCALAR >
  Size O3prmBNReader< GUM_SCALAR >::warnings() {
    return _errors_.warning_count;
  }

  template < GUM_Numeric GUM_SCALAR >
  Idx O3prmBNReader< GUM_SCALAR >::errLine(Idx i) {
    return _errors_.error(i).line;
  }

  template < GUM_Numeric GUM_SCALAR >
  Idx O3prmBNReader< GUM_SCALAR >::errCol(Idx i) {
    return _errors_.error(i).column;
  }

  template < GUM_Numeric GUM_SCALAR >
  bool O3prmBNReader< GUM_SCALAR >::errIsError(Idx i) {
    return _errors_.error(i).is_error;
  }

  template < GUM_Numeric GUM_SCALAR >
  std::string O3prmBNReader< GUM_SCALAR >::errMsg(Idx i) {
    return _errors_.error(i).msg;
  }

  template < GUM_Numeric GUM_SCALAR >
  void O3prmBNReader< GUM_SCALAR >::showElegantErrors(std::ostream& o) {
    _errors_.elegantErrors(o);
  }

  template < GUM_Numeric GUM_SCALAR >
  void O3prmBNReader< GUM_SCALAR >::showElegantErrorsAndWarnings(std::ostream& o) {
    _errors_.elegantErrorsAndWarnings(o);
  }

  template < GUM_Numeric GUM_SCALAR >
  void O3prmBNReader< GUM_SCALAR >::showErrorCounts(std::ostream& o) {
    _errors_.syntheticResults(o);
  }

}   // namespace gum
