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

#include <agrum/PRM/gspan/interfaceGraph.h>   // to ease IDE parser

namespace gum {
  namespace prm {
    namespace gspan {

      // NodeData

      template < GUM_Numeric GUM_SCALAR >
      NodeData< GUM_SCALAR >::NodeData() : n(0), l(0) {
        GUM_CONSTRUCTOR(NodeData);
      }

      template < GUM_Numeric GUM_SCALAR >
      NodeData< GUM_SCALAR >::NodeData(const NodeData< GUM_SCALAR >& from) : n(from.n), l(from.l) {
        GUM_CONS_CPY(NodeData);
      }

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

      template < GUM_Numeric GUM_SCALAR >
      bool NodeData< GUM_SCALAR >::operator==(const NodeData< GUM_SCALAR >& from) const {
        return (n == from.n) && (l == from.l);
      }

      template < GUM_Numeric GUM_SCALAR >
      bool NodeData< GUM_SCALAR >::operator!=(const NodeData< GUM_SCALAR >& from) const {
        return (n != from.n) && (l != from.l);
      }

      // EdgeData<GUM_SCALAR>

      template < GUM_Numeric GUM_SCALAR >
      EdgeData< GUM_SCALAR >::EdgeData() : u(0), v(0), l(0) {
        GUM_CONSTRUCTOR(EdgeData);
      }

      template < GUM_Numeric GUM_SCALAR >
      EdgeData< GUM_SCALAR >::EdgeData(const EdgeData< GUM_SCALAR >& from) :
          u(from.u), v(from.v), l(from.l) {
        GUM_CONS_CPY(EdgeData);
      }

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

      template < GUM_Numeric GUM_SCALAR >
      bool EdgeData< GUM_SCALAR >::operator==(const EdgeData< GUM_SCALAR >& from) const {
        return (u == from.u) && (l_u == from.l_u) && (v == from.v) && (l_v == from.l_v)
            && (l == from.l);
      }

      template < GUM_Numeric GUM_SCALAR >
      bool EdgeData< GUM_SCALAR >::operator!=(const EdgeData< GUM_SCALAR >& from) const {
        return (u != from.u) && (l_u != from.l_u) && (v != from.v) && (l_v != from.l_v)
            && (l != from.l);
      }

      // InterfaceGraph

      template < GUM_Numeric GUM_SCALAR >
      InterfaceGraph< GUM_SCALAR >::InterfaceGraph(const PRMSystem< GUM_SCALAR >& sys) :
          _sys_(&sys), _labels_(new Bijection< Idx, LabelData* >()), _counter_(0),
          _erase_flag_(true) {
        GUM_CONSTRUCTOR(InterfaceGraph);
        HashTable< std::string, LabelData* > label_map;

        // We need to add each instance in  _graph_
        for (auto iter = sys.begin(); iter != sys.end(); ++iter) {
          NodeData< GUM_SCALAR >* node = new NodeData< GUM_SCALAR >();
          node->n                      = iter.val();
          _label_(node, label_map);
          _graph_.addNodeWithId(iter.key());
          _idMap_.insert(node->n, iter.key());
          _nodes_.insert(iter.key(), node);
        }

        NodeData< GUM_SCALAR >* u = nullptr;
        NodeData< GUM_SCALAR >* v = nullptr;

        for (const auto& elt: _nodes_) {
          NodeData< GUM_SCALAR >* data = elt.second;

          for (const auto chain: data->n->type().slotChains()) {
            for (const auto inst: data->n->getInstances(chain->id())) {
              u = (_nodes_[_idMap_[inst]]->l < data->l) ? _nodes_[_idMap_[inst]] : data;
              v = (u != data) ? data : _nodes_[_idMap_[inst]];

              if (!_graph_.existsEdge(_idMap_[u->n], _idMap_[v->n])) {
                EdgeData< GUM_SCALAR >* edge = new EdgeData< GUM_SCALAR >();
                edge->u                      = u->n;
                edge->l_u                    = u->l;
                edge->v                      = v->n;
                edge->l_v                    = v->l;
                _label_(edge, label_map);
                _graph_.addEdge(_idMap_[u->n], _idMap_[v->n]);
                _edges_.insert(Edge(_idMap_[u->n], _idMap_[v->n]), edge);
              }
            }
          }
        }
      }

      template < GUM_Numeric GUM_SCALAR >
      InterfaceGraph< GUM_SCALAR >::InterfaceGraph(const InterfaceGraph< GUM_SCALAR >& source) :
          _sys_(source._sys_), _graph_(source._graph_), _nodes_(source._nodes_),
          _idMap_(source._idMap_), _edges_(source._edges_),
          _labels_(new Bijection< Idx, LabelData* >(*(source._labels_))),
          _nodeMap_(source._nodeMap_), _edgeMap_(source._edgeMap_), _counter_(source._counter_),
          _erase_flag_(false) {
        GUM_CONS_CPY(InterfaceGraph);
      }

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

        if (_erase_flag_) {
          for (const auto& elt: _nodes_)
            delete elt.second;

          for (const auto& elt: _edges_)
            delete elt.second;

          for (const auto& elt: _nodeMap_) {
            delete elt.first;
            delete elt.second;
          }

          for (const auto& elt: _edgeMap_) {
            delete elt.first;
            delete elt.second;
          }
        }

        delete _labels_;
      }

      template < GUM_Numeric GUM_SCALAR >
      InterfaceGraph< GUM_SCALAR >&
          InterfaceGraph< GUM_SCALAR >::operator=(const InterfaceGraph< GUM_SCALAR >& source) {
        GUM_ERROR(FatalError, "not implemented")
      }

      template < GUM_Numeric GUM_SCALAR >
      void InterfaceGraph< GUM_SCALAR >::_label_(NodeData< GUM_SCALAR >*               node,
                                                 HashTable< std::string, LabelData* >& label_map) {
        Size        size  = Size(1);
        std::string sBuff = node->n->type().name();

        // First we search for multiple inputs
        for (const auto chain: node->n->type().slotChains()) {
          if (chain->isMultiple()) {
            sBuff += std::format("-{}{}", node->n->getInstances(chain->id()).size(), chain->name());
            size *= node->n->getInstances(chain->id()).size()
                  * chain->lastElt().type().variable().domainSize();
          } else {
            size *= chain->lastElt().type().variable().domainSize();
          }
        }

        // Second we search for active outputs
        for (const auto nn: node->n->type().containerDag().nodes()) {
          if (node->n->type().isOutputNode(node->n->type().get(nn))) {
            if (node->n->hasRefAttr(nn) && node->n->exists(nn)) {
              sBuff
                  += std::format("-{}{}", node->n->getRefAttr(nn).size(), node->n->get(nn).name());
              size *= node->n->get(nn).type().variable().domainSize();
            }
          }
        }

        // Label is ready
        auto p_lm = label_map.tryGet(sBuff);
        if (!p_lm) {
          LabelData* label = new LabelData();
          label_map.insert(sBuff, label);
          label->id         = ++_counter_;
          label->tree_width = size;
          label->l          = sBuff;
          _labels_->insert(label->id, label);
          _nodeMap_.insert(label, new Set< NodeData< GUM_SCALAR >* >());
          p_lm = label_map.tryGet(sBuff);
        }

        node->l = *p_lm;
        _nodeMap_[node->l]->insert(node);
      }

      template < GUM_Numeric GUM_SCALAR >
      void InterfaceGraph< GUM_SCALAR >::_label_(EdgeData< GUM_SCALAR >*               edge,
                                                 HashTable< std::string, LabelData* >& label_map) {
        Size        size  = Size(1);
        std::string sBuff = std::format("{}-{}", edge->u->type().name(), edge->v->type().name());

        // First looking for edge->u output nodes in v
        for (const auto chain: edge->u->type().slotChains()) {
          if (edge->u->getInstances(chain->id()).exists(edge->v)) {
            sBuff += std::format("-{}.{}", edge->v->type().name(), chain->lastElt().name());
            size *= chain->lastElt().type().variable().domainSize();
          }
        }

        // Second looking for edge->v output nodes in u
        for (const auto chain: edge->v->type().slotChains())
          if (edge->v->getInstances(chain->id()).exists(edge->u)) {
            sBuff += std::format("-{}.{}", edge->u->type().name(), chain->lastElt().name());
            size *= chain->lastElt().type().variable().domainSize();
          }

        // Label is ready
        auto p_elm = label_map.tryGet(sBuff);
        if (!p_elm) {
          LabelData* label = new LabelData();
          label_map.insert(sBuff, label);
          label->id         = ++_counter_;
          label->l          = sBuff;
          label->tree_width = size;
          _labels_->insert(label->id, label);
          _edgeMap_.insert(label, new Set< EdgeData< GUM_SCALAR >* >());
          p_elm = label_map.tryGet(sBuff);
        }

        edge->l = *p_elm;
        _edgeMap_[edge->l]->insert(edge);
      }

      template < GUM_Numeric GUM_SCALAR >
      UndiGraph& InterfaceGraph< GUM_SCALAR >::internalGraph() {
        return _graph_;
      }

      template < GUM_Numeric GUM_SCALAR >
      const UndiGraph& InterfaceGraph< GUM_SCALAR >::internalGraph() const {
        return _graph_;
      }

      template < GUM_Numeric GUM_SCALAR >
      Bijection< Idx, LabelData* >& InterfaceGraph< GUM_SCALAR >::labels() {
        return *_labels_;
      }

      template < GUM_Numeric GUM_SCALAR >
      const Bijection< Idx, LabelData* >& InterfaceGraph< GUM_SCALAR >::labels() const {
        return *_labels_;
      }

      template < GUM_Numeric GUM_SCALAR >
      Size InterfaceGraph< GUM_SCALAR >::size(const LabelData* l) const {
        if (auto p = _nodeMap_.tryGet(const_cast< LabelData* >(l))) return (*p)->size();
        return _edgeMap_[const_cast< LabelData* >(l)]->size();
      }

      template < GUM_Numeric GUM_SCALAR >
      Set< NodeData< GUM_SCALAR >* >& InterfaceGraph< GUM_SCALAR >::nodes(const LabelData* l) {
        return *(_nodeMap_[const_cast< LabelData* >(l)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      const Set< NodeData< GUM_SCALAR >* >&
          InterfaceGraph< GUM_SCALAR >::nodes(const LabelData* l) const {
        return *(_nodeMap_[const_cast< LabelData* >(l)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      Set< EdgeData< GUM_SCALAR >* >& InterfaceGraph< GUM_SCALAR >::edges(const LabelData* l) {
        return *(_edgeMap_[const_cast< LabelData* >(l)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      const Set< EdgeData< GUM_SCALAR >* >&
          InterfaceGraph< GUM_SCALAR >::edges(const LabelData* l) const {
        return *(_edgeMap_[const_cast< LabelData* >(l)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      LabelData* InterfaceGraph< GUM_SCALAR >::label(Idx id) {
        return _labels_->second(id);
      }

      template < GUM_Numeric GUM_SCALAR >
      NodeId InterfaceGraph< GUM_SCALAR >::id(const PRMInstance< GUM_SCALAR >& i) const {
        return _idMap_[const_cast< PRMInstance< GUM_SCALAR >* >(&i)];
      }

      template < GUM_Numeric GUM_SCALAR >
      NodeId InterfaceGraph< GUM_SCALAR >::id(const PRMInstance< GUM_SCALAR >* i) const {
        return _idMap_[const_cast< PRMInstance< GUM_SCALAR >* >(i)];
      }

      template < GUM_Numeric GUM_SCALAR >
      NodeData< GUM_SCALAR >&
          InterfaceGraph< GUM_SCALAR >::node(const PRMInstance< GUM_SCALAR >* i) {
        return node(id(i));
      }

      template < GUM_Numeric GUM_SCALAR >
      const NodeData< GUM_SCALAR >&
          InterfaceGraph< GUM_SCALAR >::node(const PRMInstance< GUM_SCALAR >* i) const {
        return node(id(i));
      }

      template < GUM_Numeric GUM_SCALAR >
      NodeData< GUM_SCALAR >& InterfaceGraph< GUM_SCALAR >::node(NodeId id) {
        return *(_nodes_[id]);
      }

      template < GUM_Numeric GUM_SCALAR >
      const NodeData< GUM_SCALAR >& InterfaceGraph< GUM_SCALAR >::node(NodeId id) const {
        return *(_nodes_[id]);
      }

      template < GUM_Numeric GUM_SCALAR >
      EdgeData< GUM_SCALAR >& InterfaceGraph< GUM_SCALAR >::edge(NodeId u, NodeId v) {
        if (auto p = _edges_.tryGet(Edge(u, v))) return *(*p);
        return *(_edges_[Edge(v, u)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      const EdgeData< GUM_SCALAR >& InterfaceGraph< GUM_SCALAR >::edge(NodeId u, NodeId v) const {
        if (auto p = _edges_.tryGet(Edge(u, v))) return *(*p);
        return *(_edges_[Edge(v, u)]);
      }

      template < GUM_Numeric GUM_SCALAR >
      std::ostream& operator<<(std::ostream& out, const NodeData< GUM_SCALAR >& data) {
        out << data.n->name() << "(" << data.l->l << ")";
        return out;
      }

      template < GUM_Numeric GUM_SCALAR >
      std::ostream& operator<<(std::ostream& out, const EdgeData< GUM_SCALAR >& data) {
        out << data.u->name() << " -> " << data.v->name() << "(" << data.l->l << ")";
        return out;
      }

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