// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2025 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of 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.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_filtering.hh"
#include "graph.hh"
#include "graph_util.hh"
#include "graph_python_interface.hh"
#include "hash_map_wrap.hh"

#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>


using namespace std;
using namespace boost;
using namespace graph_tool;

namespace graph_tool
{

template <class Graph, class ValueList>
void add_edge_list(Graph& g, ValueList vals, python::object aedge_list,
                   python::object& oeprops, bool& found)
{
    hana::for_each
        (vals,
         [&](auto t)
         {
             if (found)
                 return;
             try
             {
                 typedef typename decltype(+t)::type val_t;
                 boost::multi_array_ref<val_t, 2> edge_list =
                     get_array<val_t, 2>(aedge_list);

                 if (edge_list.shape()[1] < 2)
                     throw GraphException("Second dimension in edge list must be of size (at least) two");

                 typedef typename graph_traits<Graph>::edge_descriptor edge_t;
                 vector<DynamicPropertyMapWrap<val_t, edge_t>> eprops;
                 python::stl_input_iterator<std::any> iter(oeprops), end;
                 for (; iter != end; ++iter)
                     eprops.emplace_back(*iter, writable_edge_properties);

                 GILRelease gil_release;

                 size_t n_props = std::min(eprops.size(), edge_list.shape()[1] - 2);
                 for (const auto& e : edge_list)
                 {
                     size_t s = e[0];
                     size_t t = e[1];
                     if (t == numeric_limits<size_t>::max() ||
                         e[1] == numeric_limits<val_t>::max() ||
                         std::isnan(e[1]) || std::isinf(e[1]))
                     {
                         while (s >= num_vertices(g))
                             add_vertex(g);
                         continue;
                     }
                     while (s >= num_vertices(g) || t >= num_vertices(g))
                         add_vertex(g);
                     auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
                     for (size_t i = 0; i < n_props; ++i)
                     {
                         try
                         {
                             put(eprops[i], ne, e[i + 2]);
                         }
                         catch(bad_lexical_cast&)
                         {
                             throw ValueException("Invalid edge property value: " +
                                                 lexical_cast<string>(e[i + 2]));
                         }
                     }
                 }
                 found = true;
             }
             catch (InvalidNumpyConversion& e) {}
         });
}

void do_add_edge_list(GraphInterface& gi, python::object aedge_list,
                      python::object eprops)
{
    auto vals = hana::tuple_t<bool, char, uint8_t, uint16_t, uint32_t, uint64_t,
                              int8_t, int16_t, int32_t, int64_t, uint64_t, double,
                              long double>;
    bool found = false;
    run_action<>(false)
        (gi,
         [&](auto& graph)
         {
             return add_edge_list(graph, vals, aedge_list, eprops,
                                  found);
         })();

    if (!found)
        throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar type");
}


template <class Graph>
void add_edge_list_iter(Graph& g, python::object& edge_list,
                        python::object& oeprops)
{
    typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops;
    python::stl_input_iterator<std::any> piter(oeprops), pend;
    for (; piter != pend; ++piter)
        eprops.emplace_back(*piter, writable_edge_properties);

    python::stl_input_iterator<python::object> iter(edge_list), end;
    for (; iter != end; ++iter)
    {
        const auto& row = *iter;
        python::stl_input_iterator<python::object> eiter(row), eend;

        size_t s = 0;
        size_t t = 0;

        typename graph_traits<Graph>::edge_descriptor e;
        size_t i = 0;
        for(; eiter != eend; ++eiter)
        {
            if (i >= eprops.size() + 2)
                break;
            const auto& val = *eiter;
            switch (i)
            {
            case 0:
                s = python::extract<size_t>(val);
                while (s >= num_vertices(g))
                    add_vertex(g);
                break;
            case 1:
                if (val == python::object() ||
                    python::extract<size_t>(val) == numeric_limits<size_t>::max() ||
                    python::extract<double>(val) == numeric_limits<double>::max() ||
                    std::isnan(python::extract<double>(val)) ||
                    std::isinf(python::extract<double>(val)))

                {
                    i = eprops.size() + 2;
                    break;
                }
                t = python::extract<size_t>(val);
                while (t >= num_vertices(g))
                    add_vertex(g);
                e = add_edge(vertex(s, g), vertex(t, g), g).first;
                break;
            default:
                try
                {
                    put(eprops[i - 2], e, val);
                }
                catch(bad_lexical_cast&)
                {
                    throw ValueException("Invalid edge property value: " +
                                         python::extract<string>(python::str(val))());
                }
            }
            i++;
        }
    }
}

void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
                           python::object eprops)
{
    run_action<>(false)
        (gi,
         [&](auto& graph)
         {
             return add_edge_list_iter(graph, edge_list, eprops);
         })();
}

struct add_edge_list_hash
{
    template <class Graph, class VProp>
    void operator()(Graph& g, python::object aedge_list, VProp vmap,
                    python::object& eprops) const
    {
        typedef typename property_traits<VProp>::value_type val_t;
        if constexpr (is_scalar_v<val_t>)
        {
            try
            {
                numpy_dispatch(g, aedge_list, vmap, eprops);
            }
            catch (InvalidNumpyConversion&)
            {
                dispatch(g, aedge_list, vmap, eprops);
            }
        }
        else
        {
            dispatch(g, aedge_list, vmap, eprops);
        }
    }

    template <class Graph, class VProp>
    void numpy_dispatch(Graph& g, python::object& aedge_list, VProp& vmap,
                        python::object& oeprops) const
    {
        typedef typename property_traits<VProp>::value_type val_t;

        boost::multi_array_ref<val_t, 2> edge_list = get_array<val_t, 2>(aedge_list);
        typedef typename std::conditional_t<std::is_integral_v<val_t>,
                                            gt_hash_map<val_t, size_t>,
                                            unordered_map<val_t, size_t>> vmap_t;
        vmap_t vertices;

        if (edge_list.shape()[1] < 2)
            throw GraphException("Second dimension in edge list must be of size (at least) two");

        typedef typename graph_traits<Graph>::edge_descriptor edge_t;
        vector<DynamicPropertyMapWrap<val_t, edge_t>> eprops;
        python::stl_input_iterator<std::any> iter(oeprops), end;
        for (; iter != end; ++iter)
            eprops.emplace_back(*iter, writable_edge_properties);

        GILRelease gil_release;

        size_t n_props = std::min(eprops.size(), edge_list.shape()[1] - 2);
        auto get_vertex = [&] (const val_t& r) -> size_t
            {
                auto iter = vertices.find(r);
                if (iter == vertices.end())
                {
                    auto v = add_vertex(g);
                    vertices[r] = v;
                    vmap[v] = r;
                    return v;
                }
                return iter->second;
            };

        for (const auto& e : edge_list)
        {
            size_t s = get_vertex(e[0]);
            if (std::is_same_v<val_t,python::object> && e[1] == python::object())
                continue;
            size_t t = get_vertex(e[1]);
            auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
            for (size_t i = 0; i < n_props; ++i)
            {
                try
                {
                    put(eprops[i], ne, e[i + 2]);
                }
                catch(bad_lexical_cast&)
                {
                    throw ValueException("Invalid edge property value: " +
                                         lexical_cast<string>(e[i + 2]));
                }
            }
        }
    }

    template <class Graph, class VProp>
    void dispatch(Graph& g, python::object& edge_list, VProp& vmap,
                  python::object& oeprops) const
    {
        typedef typename property_traits<VProp>::value_type val_t;

        typedef typename std::conditional_t<std::is_integral_v<val_t>,
                                            gt_hash_map<val_t, size_t>,
                                            unordered_map<val_t, size_t>> vmap_t;
        vmap_t vertices;

        typedef typename graph_traits<Graph>::edge_descriptor edge_t;
        vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops;
        python::stl_input_iterator<std::any> piter(oeprops), pend;
        for (; piter != pend; ++piter)
            eprops.emplace_back(*piter, writable_edge_properties);

        auto get_vertex = [&] (const val_t& r) -> size_t
            {
                auto iter = vertices.find(r);
                if (iter == vertices.end())
                {
                    auto v = add_vertex(g);
                    vertices[r] = v;
                    vmap[v] = r;
                    return v;
                }
                return iter->second;
            };

        python::stl_input_iterator<python::object> iter(edge_list), end;
        for (; iter != end; ++iter)
        {
            const auto& row = *iter;

            python::stl_input_iterator<python::object> eiter(row), eend;

            size_t s = 0;
            typename graph_traits<Graph>::edge_descriptor e;
            size_t i = 0;
            for(; eiter != eend; ++eiter)
            {
                if (i >= eprops.size() + 2)
                    break;
                const auto& val = *eiter;
                if (i < 2)
                {
                    if (i == 1 && val == python::object())
                        break;
                    val_t x;
                    if constexpr (std::is_same_v<val_t, python::object>)
                        x = val;
                    else
                        x = python::extract<val_t>(val);
                    auto v = get_vertex(x);
                    if (i == 0)
                        s = v;
                    else
                        e = add_edge(s, v, g).first;
                }
                else
                {
                    try
                    {
                        put(eprops[i - 2], e, val);
                    }
                    catch(bad_lexical_cast&)
                    {
                        throw ValueException("Invalid edge property value: " +
                                             python::extract<string>(python::str(val))());
                    }
                }
                i++;
            }
        }
    };
};

void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
                             std::any& vertex_map,
                             python::object eprops)
{
    run_action<decltype(all_graph_views), false>(false)
        (gi,
         [&](auto& graph, auto& vmap)
         {
             return add_edge_list_hash()(graph, aedge_list, vmap, eprops);
         },
         writable_vertex_properties)(vertex_map);
}

} // namespace graph_tool
