// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2022 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/>.

#ifndef GML_HH
#define GML_HH

#define BOOST_SPIRIT_UNICODE

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant/recursive_variant.hpp>
#include <boost/variant/get.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

#include <boost/algorithm/string/replace.hpp>

#include <boost/property_map/dynamic_property_map.hpp>
#include <boost/graph/graph_traits.hpp>

#include <boost/python.hpp>

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <codecvt>

#include "base64.hh"

namespace graph_tool{

using namespace std;
using namespace boost;

class gml_parse_error: public std::exception
{
public:
    gml_parse_error(const string& w): _what(w) {}
    ~gml_parse_error() throw() {}
    virtual const char* what() const throw() {return _what.c_str();}

private:
    std::string _what;
};

struct to_dict_visitor: public boost::static_visitor<>
{
    to_dict_visitor(const std::string& key, boost::python::dict& dict)
        : key(key), dict(dict) {}

    template <class Val>
    void operator()(Val&& val) const
    {
        const_cast<boost::python::dict&>(dict)[key] = val;
    }

    void operator()(std::wstring& val) const
    {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
        const_cast<boost::python::dict&>(dict)[key] =  conv.to_bytes(val);
    }

    template <class Val>
    void operator()(std::unordered_map<std::string, Val>& val) const
    {
        boost::python::dict n_dict;
        for (auto& kv : val)
            boost::apply_visitor(to_dict_visitor(kv.first, n_dict), kv.second);
        const_cast<boost::python::dict&>(dict)[key] = n_dict;
    }

    const std::string& key;
    const boost::python::dict& dict;
};

template <class Desc>
struct prop_val_visitor: public boost::static_visitor<>
{
    prop_val_visitor(const std::string& name, dynamic_properties& dp, Desc v)
        : name(name), dp(dp), v(v) {}

    template <class Val>
    void operator()(Val&& val) const
    {
        put(name, const_cast<dynamic_properties&>(dp), v, val);
    }

    void operator()(std::wstring& val) const
    {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
        put(name, const_cast<dynamic_properties&>(dp), v, conv.to_bytes(val));
    }

    template <class Val>
    void operator()(std::unordered_map<std::string, Val>& val) const
    {
        boost::python::dict dict;
        for (auto& kv : val)
            boost::apply_visitor(to_dict_visitor(kv.first, dict), kv.second);
        put(name, const_cast<dynamic_properties&>(dp), v,
            boost::python::object(dict));
    }

    const std::string& name;
    const dynamic_properties& dp;
    Desc v;
};


template <class Graph>
class gml_state
{
public:
    gml_state(Graph& g, dynamic_properties& dp,
              const std::unordered_set<std::string>& ignore_vp = std::unordered_set<std::string>(),
              const std::unordered_set<std::string>& ignore_ep = std::unordered_set<std::string>(),
              const std::unordered_set<std::string>& ignore_gp = std::unordered_set<std::string>())
        : _g(g), _dp(dp), _directed(false), _ignore_vp(ignore_vp),
          _ignore_ep(ignore_ep), _ignore_gp(ignore_gp) {}

    typedef boost::make_recursive_variant<std::string, std::wstring, int, double,
                                          std::unordered_map<std::string,
                                                             boost::recursive_variant_>>
        ::type val_t;

    // key / value mechanics
    void push_key(const std::string& key)
    {
        _stack.push_back(make_pair(key, prop_list_t()));
    }

    void push_value(const val_t& value)
    {
        if (_stack.empty())
            return;
        std::string k = _stack.back().first;
        _stack.pop_back();
        if (!_stack.empty())
            _stack.back().second[k] = value;
    }

    // actual parsing
    void finish_list()
    {
        if (_stack.empty())
            return;

        std::string &k = _stack.back().first;
        if (k == "node")
        {
            int id;
            if (_stack.back().second.find("id") == _stack.back().second.end())
                throw gml_parse_error("node does not have an id");
            try
            {
                id = boost::get<double>(_stack.back().second["id"]);
            }
            catch (bad_get&)
            {
                throw gml_parse_error("invalid node id");
            }

            typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
            vertex_t v = get_vertex(id);

            // put properties
            for (auto& iter : _stack.back().second)
            {
                if (iter.first == "id")
                    continue;
                if (_ignore_vp.find(iter.first) != _ignore_vp.end())
                    continue;
                boost::apply_visitor(prop_val_visitor<vertex_t>(iter.first, _dp, v),
                                     iter.second);
            }
        }
        else if (k == "edge")
        {
            int source, target;
            if (_stack.back().second.find("source") ==
                _stack.back().second.end() ||
                _stack.back().second.find("target") ==
                _stack.back().second.end())
                throw gml_parse_error("edge does not have source and target ids");
            try
            {
                source = boost::get<double>(_stack.back().second["source"]);
                target = boost::get<double>(_stack.back().second["target"]);
            }
            catch (bad_get&)
            {
                throw gml_parse_error("invalid source and target ids");
            }

            typename graph_traits<Graph>::vertex_descriptor s, t;
            s = get_vertex(source);
            t = get_vertex(target);

            typedef typename graph_traits<Graph>::edge_descriptor edge_t;
            edge_t e = add_edge(s, t, _g).first;

            // put properties
            for (auto& iter : _stack.back().second)
            {
                if (iter.first == "id" || iter.first == "source" || iter.first == "target")
                    continue;
                if (_ignore_ep.find(iter.first) != _ignore_ep.end())
                    continue;
                boost::apply_visitor(prop_val_visitor<edge_t>(iter.first, _dp, e),
                                     iter.second);
            }

        }
        else if (k == "graph")
        {
            // put properties
            for (auto& iter : _stack.back().second)
            {
                if (iter.first == "directed")
                    _directed = boost::get<double>(iter.second);
                if (_ignore_gp.find(iter.first) != _ignore_gp.end())
                    continue;
                boost::apply_visitor(prop_val_visitor<graph_property_tag>(iter.first, _dp,
                                                                          graph_property_tag()),
                                     iter.second);
            }

        }
        else
        {
            // Push nested lists down the stack
            if (_stack.size() < 2)
            {
                if (k != "comments")
                    throw gml_parse_error("invalid syntax: list '" + k + "' not within 'node', 'edge' or 'graph'");
            }
            else
            {
                _stack[_stack.size() - 2].second[k] = _stack.back().second;
            }
        }
        _stack.pop_back();
    }

    typename graph_traits<Graph>::vertex_descriptor get_vertex(size_t index)
    {
        if (_vmap.find(index) == _vmap.end())
            _vmap[index] = add_vertex(_g);
        return _vmap[index];
    }


    bool is_directed()
    {
        return _directed;
    }


private:
    Graph& _g;
    dynamic_properties& _dp;
    bool _directed;
    std::unordered_map<int, typename graph_traits<Graph>::vertex_descriptor> _vmap;

    // the stack holds the keys, and its properties
    typedef std::unordered_map<std::string, val_t> prop_list_t;
    vector<pair<std::string,  prop_list_t> > _stack;

    const std::unordered_set<std::string>& _ignore_vp;
    const std::unordered_set<std::string>& _ignore_ep;
    const std::unordered_set<std::string>& _ignore_gp;
};


template <class Iterator, class Graph, class Skipper>
struct gml : spirit::qi::grammar<Iterator, void(), Skipper>
{
    gml(Graph& g, dynamic_properties& dp,
        const std::unordered_set<std::string>& ignore_vp = std::unordered_set<std::string>(),
        const std::unordered_set<std::string>& ignore_ep = std::unordered_set<std::string>(),
        const std::unordered_set<std::string>& ignore_gp = std::unordered_set<std::string>())
        : gml::base_type(start), _state(g, dp, ignore_vp, ignore_ep, ignore_gp)
    {
        using namespace spirit;
        using spirit::unicode::char_;

        unesc_str = spirit::lexeme['"' >> *(unesc_char |  (unicode::graph - "\"") |
                                            unicode::space | "\\x" >> qi::hex) >> '"'];
        unesc_char.add("\\a", '\a')("\\b", '\b')("\\f", '\f')("\\n", '\n')
            ("\\r", '\r')("\\t", '\t')("\\v", '\v')("\\\\", '\\')
            ("\\\'", '\'')("\\\"", '\"');
        key_identifier %= spirit::lexeme[((+((spirit::qi::unicode::alnum | "_") | "-")) >> *spirit::qi::unicode::alnum)];
        auto key_action = [this](auto && attr)
                          {
                              _state.push_key(std::forward<decltype(attr)>(attr));
                          };
        key = key_identifier[key_action];

        value_identifier %= (spirit::lexeme[spirit::qi::double_] | unesc_str);
        auto value_action = [this](auto&& attr)
                            {
                                _state.push_value(std::forward<decltype(attr)>(attr));
                            };
        value %= value_identifier[value_action];

        list_identifier = *(key >> (value | "[" >> list >> "]"));
        auto list_action = [this] { _state.finish_list(); };
        list = list_identifier[list_action];

        start = list;
    }

    typedef boost::variant<std::string, std::wstring, double> val_t;

    spirit::qi::rule<Iterator, std::wstring(), Skipper> unesc_str;
    spirit::qi::symbols<char const, char const> unesc_char;
    spirit::qi::rule<Iterator, std::string(), Skipper> key, key_identifier;
    spirit::qi::rule<Iterator, val_t(), Skipper> value, value_identifier;
    spirit::qi::rule<Iterator, void(), Skipper> list, list_identifier;
    spirit::qi::rule<Iterator, void(), Skipper> start;

    gml_state<Graph> _state;
};

template <class Iterator, class Graph, class Skipper>
bool parse_grammar(Iterator begin, Iterator end, Graph& g,
                   dynamic_properties& dp, Skipper skip,
                   const std::unordered_set<std::string>& ignore_vp = std::unordered_set<std::string>(),
                   const std::unordered_set<std::string>& ignore_ep = std::unordered_set<std::string>(),
                   const std::unordered_set<std::string>& ignore_gp = std::unordered_set<std::string>())
{
    using namespace spirit;
    gml<Iterator, Graph, Skipper> parser(g, dp, ignore_vp, ignore_ep, ignore_gp);
    bool ok = qi::phrase_parse(begin, end, parser, skip);
    if (!ok)
        throw gml_parse_error("invalid syntax");
    return parser._state.is_directed();
}


template <class Graph>
bool read_gml(istream& in, Graph& g, dynamic_properties& dp,
              const std::unordered_set<std::string>& ignore_vp = std::unordered_set<std::string>(),
              const std::unordered_set<std::string>& ignore_ep = std::unordered_set<std::string>(),
              const std::unordered_set<std::string>& ignore_gp = std::unordered_set<std::string>())
{
    using namespace spirit;

    in >> std::noskipws;
    spirit::istream_iterator begin(in);
    spirit::istream_iterator end;

    u8_to_u32_iterator<spirit::istream_iterator> tbegin(begin), tend(end);

    bool directed =
        parse_grammar(tbegin, tend, g, dp,
                      (unicode::space | '#' >> *(unicode::char_ - qi::eol) >> qi::eol),
                      ignore_vp, ignore_ep, ignore_gp);

    return directed;
}

struct get_str
{
    template <typename ValueType>
    void operator()(const boost::any& val, std::string& sval, ValueType) const
    {
        const ValueType* v = any_cast<ValueType>(&val);
        if (v == nullptr)
            return;
        if constexpr (std::is_same<ValueType, python::object>::value)
        {
            sval = base64_encode(lexical_cast<string>(*v));
        }
        else
        {
            stringstream s;
            s << *v;
            sval = s.str();
        }

        if constexpr (!std::is_scalar<ValueType>::value)
        {
            replace_all(sval, "\"", "\\\"");
            sval = "\"" + sval + "\"";
        }
    }
};

template <typename ValueTypes, typename Descriptor>
std::string print_val(dynamic_property_map& pmap, const Descriptor& v)
{
    std::string val;
    boost::any oval = pmap.get(v);
    mpl::for_each<ValueTypes>([&oval, &val](auto t){ get_str()(oval, val, t); });
    return val;
}


template <typename Graph, typename VertexIndexMap>
void write_gml(std::ostream& out, const Graph& g, VertexIndexMap vertex_index,
               const dynamic_properties& dp)
{
    typedef typename graph_traits<Graph>::directed_category directed_category;
    typedef typename graph_traits<Graph>::edge_descriptor edge_descriptor;
    typedef typename graph_traits<Graph>::vertex_descriptor vertex_descriptor;

    typedef mpl::vector<bool, uint8_t, int8_t, uint32_t, int32_t,
                        uint64_t, int64_t, float, double, long double,
                        std::vector<uint8_t>, std::vector<int32_t>,
                        std::vector<int64_t>, std::vector<double>,
                        std::vector<long double>, std::vector<std::string>,
                        std::string, python::object> value_types;

    BOOST_STATIC_CONSTANT(bool, graph_is_directed =
                          (std::is_convertible<directed_category*,
                                               directed_tag*>::value));

    out << "graph [" << endl;

    if (graph_is_directed)
        out << "   directed " << 1 << endl;

    dynamic_properties vdp;
    dynamic_properties edp;

    for (auto& i : dp)
    {
        if (i.second->key() == typeid(graph_property_tag))
        {
            std::string val = print_val<value_types>(*i.second,
                                                     graph_property_tag());
            if (val.empty())
                continue;
            out << "   " << i.first << " " << val << endl;
        }
        else if (i.second->key() == typeid(vertex_descriptor))
        {
            vdp.insert(i.first, i.second);
        }
        else if (i.second->key() == typeid(edge_descriptor))
        {
            edp.insert(i.first, i.second);
        }
    }

    for (auto v : vertices_range(g))
    {
        out << "   node [" << endl;
        out << "      id " << get(vertex_index, v) << endl;

        for (auto& i : vdp)
        {
            std::string val = print_val<value_types>(*i.second, v);
            if (val.empty())
                continue;
            out << "      " << i.first << " " << val << endl;
        }
        out << "   ]" << endl;
    }

    typename graph_traits<Graph>::edges_size_type edge_count = 0;
    for (auto e : edges_range(g))
    {
        out << "   edge [" << endl;
        out << "      id " << edge_count++ << endl;
        out << "      source " << get(vertex_index, source(e, g)) << endl;
        out << "      target " << get(vertex_index, target(e, g)) << endl;

        for (auto& i : edp)
        {
            std::string val = print_val<value_types>(*i.second, e);
            if (val.empty())
                continue;
            out << "      " << i.first << " " << val << endl;
        }
        out << "   ]" << endl;
    }
    out << "]" << endl;
}


template <typename Graph>
void write_gml(std::ostream& out, const Graph& g, const dynamic_properties& dp)
{
    write_gml(out, g, get(vertex_index, g), dp);
}

} // namespace graph_tool

#endif // GML_HH
