//  Copyright 2016 Klemens Morgenstern
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_DLL_DETAIL_DEMANGLING_ITANIUM_HPP_
#define BOOST_DLL_DETAIL_DEMANGLING_ITANIUM_HPP_

#include <boost/dll/detail/demangling/mangled_storage_base.hpp>
#include <iterator>
#include <algorithm>
#include <boost/type_traits/is_const.hpp>
#include <boost/type_traits/is_volatile.hpp>
#include <boost/type_traits/is_rvalue_reference.hpp>
#include <boost/type_traits/is_lvalue_reference.hpp>
#include <boost/type_traits/function_traits.hpp>


namespace boost { namespace dll { namespace detail {



class mangled_storage_impl : public mangled_storage_base
{
    template<typename T>
    struct dummy {};

    template<typename Return, typename ...Args>
    std::vector<std::string> get_func_params(dummy<Return(Args...)>)  const
    {
        return {get_name<Args>()...};
    }
    template<typename Return, typename ...Args>
    std::string get_return_type(dummy<Return(Args...)>)  const
    {
        return get_name<Return>();
    }
public:
    using mangled_storage_base::mangled_storage_base;
    struct ctor_sym
    {
        std::string C1;
        std::string C2;
        std::string C3;

        bool empty() const
        {
            return C1.empty() && C2.empty() && C3.empty();
        }
    };

    struct dtor_sym
    {
        std::string D0;
        std::string D1;
        std::string D2;
        bool empty() const
        {
            return D0.empty() && D1.empty() && D2.empty();
        }
    };

    template<typename T>
    std::string get_variable(const std::string &name) const;

    template<typename Func>
    std::string get_function(const std::string &name) const;

    template<typename Class, typename Func>
    std::string get_mem_fn(const std::string &name) const;

    template<typename Signature>
    ctor_sym get_constructor() const;

    template<typename Class>
    dtor_sym get_destructor() const;

    template<typename T>
    std::string get_type_info() const;

    template<typename T>
    std::vector<std::string> get_related() const;

};



namespace parser
{
    //! declare
    template <typename... T>
    struct dummy;

    template <typename T>
    std::string parse_type_helper(const mangled_storage_impl & ms, dummy<T>*);

    template <typename... T, template <typename...> class Tn>
    std::string parse_type_helper(const mangled_storage_impl & ms, dummy<Tn<T...>>*);

    template <typename... T, template <typename...> class Tn>
    std::string parse_type(const mangled_storage_impl & ms, dummy<Tn<T...>>*);

    template <typename T>
    std::string parse_type(const mangled_storage_impl & ms, dummy<T>*);

    template <typename T1, typename T2, typename... T3>
    std::string parse_type(const mangled_storage_impl & ms, dummy<T1, T2, T3...>*);

    template <typename R, typename... Args>
    std::string parse_type(const mangled_storage_impl & ms, dummy<R(Args...)>*);

    std::string parse_type(const mangled_storage_impl & ms, dummy<>*);

    template<typename T>
    std::string
    type_name(const mangled_storage_impl &);


    //The purpose of this class template is to separate the pure type from the rule name from the target type
    template<typename T>
    struct pure_type
    {
        typedef T type;
        inline static std::string type_rule() { return ""; }
    };

    template<typename T>
    struct pure_type<T*>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + "*";
        }
    };

    template<typename T>
    struct pure_type<T const>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + " const";
        }
    };

    template<typename T>
    struct pure_type<T volatile>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + " volatile";
        }
    };

    template<typename T>
    struct pure_type<T const volatile>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + " const volatile";
        }
    };

    template<typename T>
    struct pure_type<T&>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + "&";
        }
    };

    template<typename T>
    struct pure_type<T&&>
    {
        typedef typename pure_type<T>::type type;
        inline static std::string type_rule()
        {
            return pure_type<T>::type_rule() + "&&";
        }
    };

    inline std::string const_rule_impl(true_type )  {return " const";}
    inline std::string const_rule_impl(false_type)  {return "";}
    template<typename T>
    std::string const_rule() {using t = is_const<typename remove_reference<T>::type>; return const_rule_impl(t());}

    inline std::string volatile_rule_impl(true_type )  {return " volatile";}
    inline std::string volatile_rule_impl(false_type)  {return "";}
    template<typename T>
    std::string volatile_rule() {using t = is_volatile<typename remove_reference<T>::type>; return volatile_rule_impl(t());}

    inline std::string reference_rule_impl(false_type, false_type) {return "";}
    inline std::string reference_rule_impl(true_type,  false_type) {return "&" ;}
    inline std::string reference_rule_impl(false_type, true_type ) {return "&&";}

    template<typename T>
    std::string reference_rule() {using t_l = is_lvalue_reference<T>; using t_r = is_rvalue_reference<T>; return reference_rule_impl(t_l(), t_r());}

    //it takes a string, because it may be overloaded.
    template<typename Return, typename Arg>
    std::string arg_list(const mangled_storage_impl & ms, Return (*)(Arg))
    {
        using namespace std;
        return type_name<Arg>(ms);
    }

    template<typename Return, typename First, typename Second, typename ...Args>
    std::string arg_list(const mangled_storage_impl & ms, Return (*)(First, Second, Args...))
    {
        using next_type = Return (*)(Second, Args...);
        return type_name<First>(ms) + ", " + arg_list(ms, next_type());
    }

    template<typename Return>
    std::string arg_list(const mangled_storage_impl &, Return (*)())
    {
        return "";
    }

    //! implement
    template <typename T>
    inline std::string parse_type_helper(const mangled_storage_impl & ms, dummy<T>*) {
        return  ms.get_name<T>();
    }

    template <typename... T, template <typename...> class Tn>
    inline std::string parse_type_helper(const mangled_storage_impl & ms, dummy<Tn<T...>>*) {
        using type = dummy<Tn<T...>>*;
        return parse_type(ms, type());
    }

    template <typename R, typename... Args>
    inline std::string parse_type(const mangled_storage_impl & ms, dummy<R(*)(Args...)>*) {
        using args_type = dummy<Args...>*;
        using return_type = dummy<R>*;
        return parse_type(ms, return_type()) + " (*)(" + parse_type(ms, args_type()) + ")";
    }

    template <typename R, typename... Args>
    inline std::string parse_type(const mangled_storage_impl & ms, dummy<R(Args...)>*) {
        using args_type = dummy<Args...>*;
        using return_type = dummy<R>*;
        return parse_type(ms, return_type()) + " (" + parse_type(ms, args_type()) + ")";
    }

    template <typename T>
    inline std::string parse_type(const mangled_storage_impl & ms, dummy<T>*) {
        using type = dummy<typename pure_type<T>::type>*;
        auto str = parse_type_helper(ms, type());
        return str + pure_type<T>::type_rule();
    }

    template <typename T1, typename T2, typename... T3>
    inline std::string parse_type(const mangled_storage_impl & ms, dummy<T1, T2, T3...>*) {
        using first_type = dummy<T1>*;
        using next_type = dummy<T2, T3...>*;
        return parse_type(ms, first_type()) + ", " + parse_type(ms, next_type());
    }

    template <typename... T, template <typename...> class Tn>
    inline std::string parse_type(const mangled_storage_impl & ms, dummy<Tn<T...>>*) {
        using next_type = dummy<T...>*;
        std::string str = ms.get_name<Tn<T...>>();
        auto frist = str.find_first_of("<");
        std::string template_name = str.substr(0, frist);
        std::string args_name = parse_type(ms, next_type());
        char last_ch = args_name[args_name.size() - 1];
        return template_name + "<" + args_name + (last_ch == '>' ? " >" : ">");
    }

    inline std::string parse_type(const mangled_storage_impl &, dummy<>*) {
        return "";
    }

    template<typename T>
    inline  std::string
    type_name(const mangled_storage_impl &ms)
    {
        using namespace parser;
        using type = dummy<T>*;
        return  parse_type(ms, type());
    }
}



template<typename T> std::string mangled_storage_impl::get_variable(const std::string &name) const
{
    auto found = std::find_if(storage_.begin(), storage_.end(),
            [&](const entry& e) {return e.demangled == name;});

    if (found != storage_.end())
        return found->mangled;
    else
        return "";
}

template<typename Func> std::string mangled_storage_impl::get_function(const std::string &name) const
{
    using func_type = Func*;

    auto matcher = name + '(' + parser::arg_list(*this, func_type()) + ')';

    auto found = std::find_if(storage_.begin(), storage_.end(), [&](const entry& e) {return e.demangled == matcher;});
    if (found != storage_.end())
        return found->mangled;
    else
        return "";

}

template<typename Class, typename Func>
std::string mangled_storage_impl::get_mem_fn(const std::string &name) const
{
    using namespace parser;

    using func_type = Func*;

    std::string cname = get_name<Class>();

    const auto matcher = cname + "::" + name +
             '(' + parser::arg_list(*this, func_type()) + ')'
             + const_rule<Class>() + volatile_rule<Class>();

    // Linux export table contains int MyClass::Func<float>(), but expected in import_mangled MyClass::Func<float>() without returned type.
    auto found = std::find_if(storage_.begin(), storage_.end(), [&matcher](const entry& e) {
        if (e.demangled == matcher) {
          return true;
        }

        const auto pos = e.demangled.rfind(matcher);
        if (pos == std::string::npos) {
          // Not found.
          return false;
        }

        if (pos + matcher.size() != e.demangled.size()) {
          // There are some characters after the `matcher` string.
          return false;
        }

        // Double checking that we matched a full function name
        return e.demangled[pos - 1] == ' '; // `if (e.demangled == matcher)` makes sure that `pos > 0`
    });

    if (found != storage_.end())
        return found->mangled;
    else
        return "";

}


template<typename Signature>
auto mangled_storage_impl::get_constructor() const -> ctor_sym
{
    using namespace parser;

    using func_type = Signature*;

    std::string ctor_name; // = class_name + "::" + name;
    std::string unscoped_cname; //the unscoped class-name
    {
        auto class_name = get_return_type(dummy<Signature>());
        auto pos = class_name.rfind("::");
        if (pos == std::string::npos)
        {
            ctor_name = class_name+ "::" +class_name ;
            unscoped_cname = class_name;
        }
        else
        {
            unscoped_cname = class_name.substr(pos+2) ;
            ctor_name = class_name+ "::" + unscoped_cname;
        }
    }

    auto matcher =
                ctor_name + '(' + parser::arg_list(*this, func_type()) + ')';


    std::vector<entry> findings;
    std::copy_if(storage_.begin(), storage_.end(),
            std::back_inserter(findings), [&](const entry& e) {return e.demangled == matcher;});

    ctor_sym ct;

    for (auto & e : findings)
    {

        if (e.mangled.find(unscoped_cname +"C1E") != std::string::npos)
            ct.C1 = e.mangled;
        else if (e.mangled.find(unscoped_cname +"C2E") != std::string::npos)
            ct.C2 = e.mangled;
        else if (e.mangled.find(unscoped_cname +"C3E") != std::string::npos)
            ct.C3 = e.mangled;
    }
    return ct;
}

template<typename Class>
auto mangled_storage_impl::get_destructor() const -> dtor_sym
{
    std::string dtor_name; // = class_name + "::" + name;
    std::string unscoped_cname; //the unscoped class-name
    {
        auto class_name = get_name<Class>();
        auto pos = class_name.rfind("::");
        if (pos == std::string::npos)
        {
            dtor_name = class_name+ "::~" + class_name  + "()";
            unscoped_cname = class_name;
        }
        else
        {
            unscoped_cname = class_name.substr(pos+2) ;
            dtor_name = class_name+ "::~" + unscoped_cname + "()";
        }
    }

    auto d0 = unscoped_cname + "D0Ev";
    auto d1 = unscoped_cname + "D1Ev";
    auto d2 = unscoped_cname + "D2Ev";

    dtor_sym dt;
    //this is so simple, i don#t need a predicate
    for (auto & s : storage_)
    {
        //alright, name fits
        if (s.demangled == dtor_name)
        {
            if (s.mangled.find(d0) != std::string::npos)
                dt.D0 = s.mangled;
            else if (s.mangled.find(d1) != std::string::npos)
                dt.D1 = s.mangled;
            else if (s.mangled.find(d2) != std::string::npos)
                dt.D2 = s.mangled;

        }
    }
    return dt;

}

template<typename T>
std::string mangled_storage_impl::get_type_info() const
{
    std::string id = "typeinfo for " + get_name<T>();


    auto predicate = [&](const mangled_storage_base::entry & e)
                {
                    return e.demangled == id;
                };

    auto found = std::find_if(storage_.begin(), storage_.end(), predicate);


    if (found != storage_.end())
        return found->mangled;
    else
        return "";
}

template<typename T>
std::vector<std::string> mangled_storage_impl::get_related()  const
{
    std::vector<std::string> ret;
    auto name = get_name<T>();

    for (auto & c : storage_)
    {
        if (c.demangled.find(name) != std::string::npos)
            ret.push_back(c.demangled);
    }

    return ret;
}

}}}


#endif /* BOOST_DLL_DETAIL_DEMANGLING_ITANIUM_HPP_ */