// 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.hh"
#include "graph_python_interface.hh"
#include "graph_properties.hh"
#include "graph_filtering.hh"
#include "graph_selectors.hh"
#include "graph_util.hh"
#include "hash_map_wrap.hh"

#include <boost/mpl/for_each.hpp>

#include <boost/python/extract.hpp>

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

namespace graph_tool
{

std::tuple<std::string, std::string, bool> get_pmap_type(const std::any& prop)
{
    std::string key_type;
    std::string type_name;
    bool writable;
    typedef property_traits<edge_index_map_t>::key_type edge_t;
    hana::for_each(hana::concat(vertex_properties,
                                edge_properties),
                   [&](auto t)
                   {
                       typedef typename decltype(t)::type T;
                       const T* ptr = std::any_cast<T>(&prop);
                       if (ptr != 0)
                       {
                           typedef typename property_traits<T>::key_type
                               key_t;
                           typedef typename property_traits<T>::value_type
                               value_t;
                           if constexpr (std::is_same_v<key_t, edge_t>)
                               key_type = "edge";
                           else
                               key_type = "vertex";
                           type_name = get_type_name<value_t>();
                           writable = (!std::is_same_v<T, vertex_index_map_t> &&
                                       !std::is_same_v<T, edge_index_map_t>);
                       }
                   });
    return {key_type, type_name, writable};
}

std::any prop_map_as(std::any amap, std::any atgt, bool copy)
{
    std::any rmap;

    gt_dispatch<false>(false)
        ([&](auto map, auto tgt)
         {
             typedef typename decltype(tgt)::value_type tval_t;
             if (std::is_same_v<decltype(map), decltype(tgt)> && !copy)
                 rmap = map;
             else
                 rmap = map.template copy<tval_t>();
         },
         hana::concat(writable_vertex_properties,
                      writable_edge_properties),
         hana::concat(writable_vertex_properties,
                      writable_edge_properties))(amap, atgt);

    return rmap;
}

std::any vprop_map_as_dynamic(std::any amap, std::any atgt, bool identity)
{
    if (identity && amap.type() == atgt.type())
        return amap;
    std::any rmap;
    gt_dispatch<false>(false)
        ([&](auto tgt)
         {
             typedef typename decltype(tgt)::value_type val_t;
             typedef typename decltype(tgt)::key_type key_t;
             rmap = DynamicPropertyMapWrap<val_t, key_t>(amap, vertex_properties);
         },
         vertex_properties)(atgt);
    return rmap;
}

std::any eprop_map_as_dynamic(std::any amap, std::any atgt, bool identity)
{
    if (identity && amap.type() == atgt.type())
        return amap;

    std::any rmap;
    gt_dispatch<false>(false)
        ([&](auto tgt)
         {
             typedef typename decltype(tgt)::value_type val_t;
             typedef typename decltype(tgt)::key_type key_t;
             rmap = DynamicPropertyMapWrap<val_t, key_t>(amap, edge_properties);
         },
         edge_properties)(atgt);
    return rmap;
}

std::any vprop_map_as_dvelem(std::any amap, std::any atgt)
{
    auto overtex_scalar_vector_properties =
        hana::append(vertex_scalar_vector_properties,
                     hana::type<vprop_map_t<python::object>>());

    std::any rmap;
    gt_dispatch<false>(false)
        ([&](auto src, auto tgt)
         {
             typedef typename decltype(src)::value_type sval_t;
             typedef typename decltype(tgt)::value_type tval_t;
             typedef typename decltype(tgt)::key_type key_t;

             if constexpr (std::is_same_v<tval_t, python::object>)
             {
                 if (std::is_same_v<sval_t, python::object>)
                     rmap = src;
                 else
                     rmap = DynamicPropertyMapWrap<tval_t, key_t>(src, vertex_properties);
             }
             else
             {
                 if (std::is_same_v<sval_t, typename tval_t::value_type>)
                     rmap = src;
                 else
                     rmap = DynamicPropertyMapWrap<typename tval_t::value_type, key_t>(amap, vertex_properties);
             }
         },
         vertex_properties,
         overtex_scalar_vector_properties)(amap, atgt);
    return rmap;
}

std::any eprop_map_as_dvelem(std::any amap, std::any atgt)
{
    auto oedge_scalar_vector_properties =
        hana::append(edge_scalar_vector_properties,
                     hana::type<eprop_map_t<python::object>>());

    std::any rmap;
    gt_dispatch<false>(false)
        ([&](auto src, auto tgt)
         {
             typedef typename decltype(src)::value_type sval_t;
             typedef typename decltype(tgt)::value_type tval_t;
             typedef typename decltype(tgt)::key_type key_t;

             if constexpr (std::is_same_v<tval_t, python::object>)
             {
                 if (std::is_same_v<sval_t, python::object>)
                     rmap = src;
                 else
                     rmap = DynamicPropertyMapWrap<tval_t, key_t>(src, edge_properties);
             }
             else
             {
                 if (std::is_same_v<sval_t, typename tval_t::value_type>)
                     rmap = src;
                 else
                     rmap = DynamicPropertyMapWrap<typename tval_t::value_type, key_t>(amap, edge_properties);
             }
         },
         edge_properties,
         oedge_scalar_vector_properties)(amap, atgt);
    return rmap;
}

struct do_shift_vertex_property
{
    template <class PropertyMap, class Vec>
    void operator()(PropertyMap, const GraphInterface::multigraph_t& g,
                    std::any map, const Vec& vi, bool& found) const
    {
        try
        {
            PropertyMap pmap = std::any_cast<PropertyMap>(map);
            size_t back = num_vertices(g) - 1;
            for (auto v : vi)
            {
                for (size_t i = v; i < back; ++i)
                    pmap[vertex(i, g)] = pmap[vertex(i + 1, g)];
                back--;
            }
            found = true;
        }
        catch (std::bad_any_cast&) {}
    }
};

// this function will shift all the properties when a vertex is to be deleted
void GraphInterface::shift_vertex_property(std::any prop, python::object oindex) const
{
    boost::multi_array_ref<int64_t,1> index = get_array<int64_t,1>(oindex);
    bool found = false;
    hana::for_each(writable_vertex_properties,
                   [&](auto t)
                   {
                       return do_shift_vertex_property()
                           (typename decltype(+t)::type(), *_mg, prop, index,
                            found);
                   });
    if (!found)
        throw GraphException("invalid writable property map");
}

struct do_move_vertex_property
{
    template <class PropertyMap, class Vec>
    void operator()(PropertyMap, const GraphInterface::multigraph_t& g,
                    std::any map, const Vec& vi, size_t back,
                    bool& found) const
    {
        try
        {
            PropertyMap pmap = std::any_cast<PropertyMap>(map);
            for (auto v : vi)
            {
                pmap[vertex(v, g)] = pmap[vertex(back, g)];
                back--;
            }
            found = true;
        }
        catch (std::bad_any_cast&) {}
    }
};

// this function will move the back of the property map when a vertex in the middle is to be deleted
void GraphInterface::move_vertex_property(std::any prop, python::object oindex) const
{
    boost::multi_array_ref<int64_t,1> index = get_array<int64_t,1>(oindex);
    size_t back = num_vertices(*_mg) - 1;
    bool found = false;
    hana::for_each(writable_vertex_properties,
                   [&](auto t)
                   {
                       return do_move_vertex_property()
                           (typename decltype(+t)::type(), *_mg, prop, index, back,
                            found);
                   });
    if (!found)
        throw GraphException("invalid writable property map");
}


struct reindex_vertex_property
{
    template <class PropertyMap, class IndexMap>
    void operator()(PropertyMap, const GraphInterface::multigraph_t& g,
                    std::any map, IndexMap old_index, bool& found) const
    {
        try
        {
            PropertyMap pmap = std::any_cast<PropertyMap>(map);
            for (size_t i = 0; i < num_vertices(g); ++i)
            {
                GraphInterface::vertex_t v = vertex(i, g);
                if (old_index[v] != int(i))
                    pmap[v] = pmap[vertex(old_index[v], g)];
            }
            found = true;
        }
        catch (std::bad_any_cast&) {}
    }
};


void GraphInterface::re_index_vertex_property(std::any map,
                                              std::any aold_index) const
{
    typedef vprop_map_t<int64_t> index_prop_t;
    index_prop_t old_index = std::any_cast<index_prop_t>(aold_index);

    bool found = false;
    hana::for_each(writable_vertex_properties,
                   [&](auto t)
                   {
                       return reindex_vertex_property()
                           (typename decltype(+t)::type(), *_mg, map, old_index,
                            found);
                   });
    if (!found)
        throw GraphException("invalid writable property map");

}

} // graph_tool namespace


struct do_infect_vertex_property
{
    template <class Graph, class IndexMap, class PropertyMap>
    void operator()(Graph& g, IndexMap index, PropertyMap prop,
                    boost::python::object oval) const
    {
        typedef typename property_traits<PropertyMap>::value_type val_t;
        bool all = false;

        std::unordered_set<val_t> vals;
        if (oval == boost::python::object())
        {
            all = true;
        }
        else
        {
            for (int i = 0; i < len(oval); ++i)
            {
                val_t val = boost::python::extract<val_t>(oval[i]);
                vals.insert(val);
            }
        }

        GILRelease gil_release(!std::is_same_v<val_t,boost::python::object>);

        unchecked_vector_property_map<bool, IndexMap>
            marked(index, num_vertices(g));

        PropertyMap temp(index, num_vertices(g));

        parallel_vertex_loop
            (g,
             [&](auto v)
             {
                 if (!all && vals.find(prop[v]) == vals.end())
                     return;
                 for (auto a : adjacent_vertices_range(v, g))
                 {
                     if (prop[a] == prop[v])
                         continue;
                     marked[a] = true;
                     temp[a] = prop[v];
                 }
             });

        parallel_vertex_loop
            (g,
             [&](auto v)
             {
                 if (marked[v])
                     prop[v] = temp[v];
             });
    }
};

void infect_vertex_property(GraphInterface& gi, std::any prop,
                            boost::python::object val)
{
    run_action<>(false)
        (gi,
         [&](auto&& graph, auto&& a2)
         {
             return do_infect_vertex_property()
                 (std::forward<decltype(graph)>(graph), gi.get_vertex_index(),
                  std::forward<decltype(a2)>(a2), val);
         },
         writable_vertex_properties)(prop);
}

template <class Value>
static vector<Value> operator-(const vector<Value>& a, const vector<Value>& b)
{
    vector<Value> c(a);
    c.resize(max(a.size(), b.size()), Value(0));
    for (size_t i = 0; i < b.size(); ++i)
        c[i] = a[i] - b[i];
    return c;
}

struct do_mark_edges
{
    template <class Graph, class EdgePropertyMap>
    void operator()(Graph& g, EdgePropertyMap prop) const
    {
        parallel_edge_loop
            (g,
             [&](auto e)
             {
                 prop[e] = true;
             });
    }
};

void mark_edges(GraphInterface& gi, std::any prop)
{
    run_action<>()
        (gi,
         [&](auto&& graph, auto&& a2)
         {
             return do_mark_edges()
                 (std::forward<decltype(graph)>(graph),
                  std::forward<decltype(a2)>(a2));
         },
         writable_edge_scalar_properties)(prop);
}
