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


#include <agrum/BN/io/cnf/FactorisedValuesCNFWriter.h>   // to ease IDE parser
#ifndef DOXYGEN_SHOULD_SKIP_THIS

// to ease parsing in IDE
#  include <agrum/BN/io/cnf/FactorisedValuesCNFWriter.h>

namespace gum {

  /* =========================================================================*/
  /* ===                           GUM_BN_WRITER === */
  /* =========================================================================*/
  // Default constructor.
  template < GUM_Numeric GUM_SCALAR, template < class > class IApproximationPolicy >
  FactorisedValuesCNFWriter< GUM_SCALAR, IApproximationPolicy >::FactorisedValuesCNFWriter() {
    GUM_CONSTRUCTOR(FactorisedValuesCNFWriter);
  }

  // Default destructor.
  template < GUM_Numeric GUM_SCALAR, template < class > class IApproximationPolicy >
  FactorisedValuesCNFWriter< GUM_SCALAR, IApproximationPolicy >::~FactorisedValuesCNFWriter() {
    GUM_DESTRUCTOR(FactorisedValuesCNFWriter);
  }

  //
  // Writes a Bayesian network in the output stream using the BN format.
  //
  // @param output The output stream.
  // @param bn The Bayesian network writen in output.
  // @throws Raised if an I/O error occurs.
  template < GUM_Numeric GUM_SCALAR, template < class > class IApproximationPolicy >
  void FactorisedValuesCNFWriter< GUM_SCALAR, IApproximationPolicy >::_doWrite(
      std::ostream&                  output,
      const IBayesNet< GUM_SCALAR >& bn) {
    if (!output.good()) GUM_ERROR(IOError, "Input/Output error : stream not writable.")

    std::stringstream strfile, strfile2;

    Idx num      = 0;
    Idx numparam = 0;

    for (auto node: bn.nodes())
      numparam += bn.variable(node).domainSize();

    Idx                                clause = 0;
    std::stringstream                  clausstr;
    gum::HashTable< std::string, Idx > vartable;   // key name::label val num;
    gum::HashTable< std::string, Idx > protable;

    for (auto node: bn.nodes()) {
      for (Idx i = 0; i < bn.variable(node).domainSize(); i++) {
        auto str = std::format("{}_{}", bn.variable(node).name(), bn.variable(node).label(i));
        vartable.insert(str, ++num);
        strfile << std::format("{}::{}\n", num, str);
      }

      const Tensor< GUM_SCALAR >& cpt = bn.cpt(node);

      Instantiation inst(cpt);

      for (inst.setFirst(); !inst.end(); ++inst) {
        auto strinst = std::format("{}_val={}", inst.toString(), this->fromExact(cpt[inst]));

        if (!protable.exists(strinst)) {
          protable.insert(inst.toString(), ++numparam);
          strfile2 << std::format("{}::{}\n", numparam, strinst);
        }
      }
    }

    for (auto node: bn.nodes()) {
      std::stringstream str0, str2;

      for (Idx i = 0; i < bn.variable(node).domainSize(); i++) {
        auto stri = std::format("{}_{}",
                                bn.variable(node).name(),
                                bn.variable(node).label(i));   //= bn.variable(iter).name()+"_"+
        // bn.variable(iter).label( i ) ;
        str0 << std::format("{} ", vartable[stri]);
      }

      str0 << "0\n";
      clause++;
      clausstr << str0.str();
      const Tensor< GUM_SCALAR >& cpt = bn.cpt(node);
      Instantiation               inst(cpt);

      for (inst.setFirst(); !inst.end(); ++inst) {
        if (this->fromExact(cpt[inst]) != 1.0) {
          for (Idx i = 0; i < inst.nbrDim(); i++) {
            auto str = std::format("{}_{}", inst.variable(i).name(), inst.val(inst.variable(i)));
            str2 << std::format("-{} ", vartable[str]);
          }

          if (this->fromExact(cpt[inst])) {
            auto strinst
                = std::format("{}_val={}", bn.variable(node).name(), this->fromExact(cpt[inst]));
            str2 << protable[strinst];
          }

          str2 << " 0\n";
          clause++;
        }
      }

      clausstr << str2.str();
    }

    output << std::format("p cnf {} {}\n", num + numparam, clause) << clausstr.str() << '\n';
    output.flush();
  }

  // Writes a Bayesian network in the referenced file using the BN format.
  // If the file doesn't exists, it is created.
  // If the file exists, it's content will be erased.
  //
  // @param filePath The path to the file used to write the Bayesian network.
  // @param bn The Bayesian network writed in the file.
  // @throws Raised if an I/O error occurs.
  template < GUM_Numeric GUM_SCALAR, template < class > class IApproximationPolicy >
  void FactorisedValuesCNFWriter< GUM_SCALAR, IApproximationPolicy >::_doWrite(
      std::string_view               filePath,
      const IBayesNet< GUM_SCALAR >& bn) {
    std::ofstream output(std::filesystem::path{filePath}, std::ios_base::trunc);
    std::ofstream outputvar(std::string{filePath} + ".var", std::ios_base::trunc);

    if (!output.good()) GUM_ERROR(IOError, "Input/Output error : " << filePath << " not writable.")

    std::stringstream strfile, strfile2;

    if (!outputvar.good())
      GUM_ERROR(IOError,
                "Input/Output error : " << (std::string(filePath) + ".var") << " not writable.")

    Idx num      = 0;
    Idx numparam = 0;

    for (auto node: bn.nodes())
      numparam += bn.variable(node).domainSize();

    Idx                                clause = 0;
    std::stringstream                  clausstr;
    gum::HashTable< std::string, Idx > vartable;   // key name::label val num;
    gum::HashTable< std::string, Idx > protable;

    for (auto node: bn.nodes()) {
      const auto& var = bn.variable(node);

      for (Idx i = 0; i < var.domainSize(); i++) {
        auto str = std::format("{}_{}", var.name(), var.label(i));
        vartable.insert(str, ++num);
        strfile << std::format("{}::{}\n", num, str);
      }

      const Tensor< GUM_SCALAR >& cpt = bn.cpt(node);

      Instantiation inst(cpt);

      for (inst.setFirst(); !inst.end(); ++inst) {
        if (this->fromExact(cpt[inst]) && this->fromExact(cpt[inst]) != 1.0) {
          auto strinst = std::format("{}_val={}", var.name(), this->fromExact(cpt[inst]));

          if (!protable.exists(strinst)) {
            protable.insert(strinst, ++numparam);
            strfile2 << std::format("{}::{}\n", numparam, strinst);
          }
        }
      }
    }

    for (auto node: bn.nodes()) {
      std::stringstream str0, str2;

      for (Idx i = 0; i < bn.variable(node).domainSize(); i++) {
        auto stri = std::format("{}_{}",
                                bn.variable(node).name(),
                                bn.variable(node).label(i));   //= bn.variable(iter).name()+"_"+
        // bn.variable(iter).label( i ) ;
        str0 << std::format("{} ", vartable[stri]);
      }

      str0 << "0\n";
      clause++;
      clausstr << str0.str();
      const Tensor< GUM_SCALAR >& cpt = bn.cpt(node);
      Instantiation               inst(cpt);

      for (inst.setFirst(); !inst.end(); ++inst) {
        if (this->fromExact(cpt[inst]) != 1.0) {
          for (Idx i = 0; i < inst.nbrDim(); i++) {
            auto str = std::format("{}_{}", inst.variable(i).name(), inst.val(inst.variable(i)));
            str2 << std::format("-{} ", vartable[str]);
          }

          if (this->fromExact(cpt[inst])) {
            auto strinst
                = std::format("{}_val={}", bn.variable(node).name(), this->fromExact(cpt[inst]));
            str2 << protable[strinst];
          }

          str2 << " 0\n";
          clause++;
        }
      }

      clausstr << str2.str();
    }

    output << std::format("p cnf {} {}\n", num + numparam, clause) << clausstr.str() << '\n';
    output.flush();
    outputvar << strfile.str() << strfile2.str();
    outputvar.flush();
    outputvar.close();
    output.close();

    if (outputvar.fail()) GUM_ERROR(IOError, "Writing in the ostream failed.")

    if (output.fail()) GUM_ERROR(IOError, "Writing in the ostream failed.")
  }

  // Returns a bloc defining a variable's CPT in the BN format.
  /*  template<GumScalar GUM_SCALAR> INLINE
      std::string
      OCNFWriter<GUM_SCALAR>:: _variableCPT_( const Tensor<GUM_SCALAR>& cpt )
    {
        std::stringstream str;
        str << "";
        return str.str();
      }

    // Returns the header of the BN file.
    template<GumScalar GUM_SCALAR> INLINE
      std::string
      OCNFWriter<GUM_SCALAR>:: _header_( const IBayesNet<GUM_SCALAR>& ) {
        std::stringstream str;
        str << "";
        return str.str();
      }

    // Returns a bloc defining a variable in the BN format.
    template<GumScalar GUM_SCALAR> INLINE
      std::string
      OCNFWriter<GUM_SCALAR>:: _variableBloc_( const DiscreteVariable& var ) {
        std::stringstream str;
        str << "" ;
        return str.str();
      }*/

  // Returns the modalities labels of the variables in varsSeq

} /* namespace gum */

#endif   // DOXYGEN_SHOULD_SKIP_THIS
