// 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_properties.hh"


#ifndef __clang__
#include <ext/numeric>
using __gnu_cxx::power;
#else
template <class Value>
static Value power(Value value, int n)
{
    return pow(value, n);
}
#endif

#include <boost/graph/fruchterman_reingold.hpp>

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

namespace graph_tool
{
// convert point types
template <class Val>
struct convert_dispatch<vector<Val>, typename convex_topology<2>::point>
{
    vector<Val> operator()(const typename convex_topology<2>::point& p) const
    {
        vector<Val> v(2);
        for (size_t i = 0; i < 2; ++i)
            v[i] = p[i];
        return v;
    }
};

template <class Val>
struct convert_dispatch<typename convex_topology<2>::point, vector<Val> >
{
    typename convex_topology<2>::point operator()(const vector<Val>& v) const
    {
        typename convex_topology<2>::point p;
        for (size_t i = 0; i < min(size_t(2), v.size()); ++i)
            p[i] = v[i];
        return p;
    }
};
} // graph_tool namespace

namespace boost
{
template <>
std::string lexical_cast(const typename convex_topology<2>::point& p)
{
    std::string out = "(" + boost::lexical_cast<std::string>(p[0]) + ", " +
        boost::lexical_cast<std::string>(p[1]) + ")";
    return out;
}
}

template<class T>
struct anneal_cooling
{
    typedef T result_type;

    anneal_cooling(T ti, T tf, std::size_t iterations)
        : _ti(ti), _tf(tf), _iter(0), _n_iter(iterations)
    {
        _beta = (log(_tf) - log(_ti)) / _n_iter;
    }

    T operator()()
    {
        T temp = _ti * exp(T(_iter) * _beta);
        ++_iter;
        if (_iter == _n_iter)
            temp = 0;
        return temp;
    }

private:
    T _ti, _tf;
    size_t _iter;
    size_t _n_iter;
    T _beta;
};

template <class Topology>
struct get_layout
{
    template <class WeightMap, class Value>
    struct attr_force
    {
        attr_force(WeightMap w, Value a): _w(w), _a(a) {}
        WeightMap _w;
        Value _a;

        template <class Graph, class Edge, class KVal, class DVal>
        Value operator()(Edge e, KVal k, DVal dist, const Graph&) const
        {
            return _a * get(_w, e) * power(dist, 2) / k;
        }
    };

    template <class Value>
    struct rep_force
    {
        rep_force(Value r): _r(r){}
        Value _r;

        template <class Graph, class Vertex, class KVal, class DVal>
        Value operator()(Vertex, Vertex, KVal k, DVal dist, const Graph&) const
        {
            return _r * power(k, 2) / dist;
        }
    };


    template <class Graph, class PosMap, class WeightMap>
    void operator()(Graph& g, PosMap pos, WeightMap weight,
                    pair<double, double> f, double scale, bool grid,
                    pair<double, double> temp, size_t n_iter) const
    {
        typedef typename property_traits<PosMap>::value_type::value_type pos_t;
        anneal_cooling<pos_t> cool(temp.first, temp.second, n_iter);
        attr_force<WeightMap, pos_t> af(weight, f.first);
        rep_force<pos_t> rf(f.second);
        Topology topology(scale);
        ConvertedPropertyMap<PosMap, typename Topology::point_type> cpos(pos);
        if (grid)
            fruchterman_reingold_force_directed_layout
                (g, cpos,
                 topology,
                 attractive_force(af).
                 repulsive_force(rf).
                 cooling(cool));
        else
            fruchterman_reingold_force_directed_layout
                (g, cpos,
                 topology,
                 attractive_force(af).
                 repulsive_force(rf).
                 cooling(cool).
                 force_pairs(all_force_pairs()));
    }
};


void fruchterman_reingold_layout(GraphInterface& g, std::any pos,
                                 std::any weight, double a, double r,
                                 bool square, double scale, bool grid,
                                 double ti, double tf, size_t max_iter)
{
    typedef UnityPropertyMap<int,GraphInterface::edge_t> weight_map_t;
    auto edge_props_t = hana::append(edge_scalar_properties,
                                     hana::type<weight_map_t>());

    if(!weight.has_value())
        weight = weight_map_t();
    if (square)
        run_action<decltype(graph_tool::never_directed)>()
            (g,
             [&](auto&& g, auto pos, auto weight)
             {
                 return get_layout<square_topology<>>()
                     (g, pos, weight, make_pair(a, r), scale,
                      grid, make_pair(ti, tf), max_iter);
             },
             vertex_floating_vector_properties, edge_props_t)(pos, weight);
    else
        run_action<decltype(graph_tool::never_directed)>()
            (g,
             [&](auto& g, auto pos, auto weight)
             {
                 return get_layout<circle_topology<>>()
                     (g, pos, weight, make_pair(a, r), scale,
                      grid, make_pair(ti, tf), max_iter);
             },
             vertex_floating_vector_properties, edge_props_t)(pos, weight);
}

#include <boost/python.hpp>

#define __MOD__ layout
#include "module_registry.hh"
REGISTER_MOD
([]
 {
     python::def("fruchterman_reingold_layout", &fruchterman_reingold_layout);
 });
