// Copyright Nikolay Mladenov 2007.
// 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)

// boost::python::make_tuple below are for gcc 4.4 -std=c++0x compatibility
// (Intel C++ 10 and 11 with -std=c++0x don't need the full qualification).

#include <boost/python/converter/registrations.hpp>
#include <boost/python/object/function_doc_signature.hpp>
#include <boost/python/errors.hpp>
#include <boost/python/str.hpp>
#include <boost/python/args.hpp>
#include <boost/python/tuple.hpp>

#include <boost/python/detail/signature.hpp>

#include <vector>

namespace boost { namespace python { namespace objects {

    bool function_doc_signature_generator::arity_cmp( function const *f1, function const *f2 )
    {
        return f1->m_fn.max_arity() < f2->m_fn.max_arity();
    }

    bool function_doc_signature_generator::are_seq_overloads( function const *f1, function const *f2 , bool check_docs)
    {
        py_function const & impl1 = f1->m_fn;
        py_function const & impl2  = f2->m_fn;

        //the number of parameters differs by 1
        if (impl2.max_arity()-impl1.max_arity() != 1)
            return false;

        // if check docs then f1 shold not have docstring or have the same docstring as f2
        if (check_docs && f2->doc() != f1->doc() && f1->doc())
            return false;

        python::detail::signature_element const* s1 = impl1.signature();
        python::detail::signature_element const* s2 = impl2.signature();

        unsigned size = impl1.max_arity()+1;

        for (unsigned i = 0; i != size; ++i)
        {
            //check if the argument types are the same
            if (s1[i].basename != s2[i].basename)
                return false;

            //return type
            if (!i) continue;

            //check if the argument default values are the same
            bool f1_has_names = bool(f1->m_arg_names);
            bool f2_has_names = bool(f2->m_arg_names);
            if ( (f1_has_names && f2_has_names && f2->m_arg_names[i-1]!=f1->m_arg_names[i-1])
                 || (f1_has_names && !f2_has_names)
                 || (!f1_has_names && f2_has_names && f2->m_arg_names[i-1]!=python::object())
                )
                return false;
        }
        return true;
    }

    std::vector<function const*> function_doc_signature_generator::flatten(function const *f)
    {
        object name = f->name();

        std::vector<function const*> res;

        while (f) {

            //this if takes out the not_implemented_function
            if (f->name() == name)
                res.push_back(f);

            f=f->m_overloads.get();
        }

        //std::sort(res.begin(),res.end(), &arity_cmp);

        return res;
    }
    std::vector<function const*> function_doc_signature_generator::split_seq_overloads( const std::vector<function const *> &funcs, bool split_on_doc_change)
    {
        std::vector<function const*> res;

        std::vector<function const*>::const_iterator fi = funcs.begin();

        function const * last = *fi;

        while (++fi != funcs.end()){

            //check if fi starts a new chain of overloads
            if (!are_seq_overloads( last, *fi, split_on_doc_change ))
                res.push_back(last);

            last = *fi;
        }

        if (last)
            res.push_back(last);

        return res;
    }

    str function_doc_signature_generator::raw_function_pretty_signature(function const *f, size_t n_overloads,  bool cpp_types )
    {
        str res("object");

        res = str("%s %s(%s)" % make_tuple( res, f->m_name, str("tuple args, dict kwds")) );

        return res;
    }

    static str get_qualname(const PyTypeObject *py_type)
    {
# if PY_VERSION_HEX >= 0x03030000
        if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE )
            return str(handle<>(borrowed(((PyHeapTypeObject*)(py_type))->ht_qualname)));
# endif
        return str(py_type->tp_name);
    }

    str function_doc_signature_generator::py_type_str(const python::detail::signature_element &s, const object &current_module_name)
    {
        if (s.basename==std::string("void")){
            static const char * none = "None";
            return str(none);
        }

        PyTypeObject const * py_type = s.pytype_f?s.pytype_f():0;
        if ( py_type ) {
            str name(get_qualname(py_type));
            if ( py_type->tp_flags & Py_TPFLAGS_HEAPTYPE ) {
                // Qualify the type name if it is defined in a different module.
                PyObject *type_module_name = PyDict_GetItemString(py_type->tp_dict, "__module__");
                if (
                    type_module_name
                    && PyObject_RichCompareBool(
                        type_module_name,
                        current_module_name.ptr(),
                        Py_NE
                    ) != 0
                ) {
                    return str("%s.%s" % make_tuple(handle<>(borrowed(type_module_name)), name));
                }
            }
            return name;
        } else {
            static const char * object = "object";
            return str(object);
        }
    }

    str function_doc_signature_generator::parameter_string(py_function const &f, size_t n, object arg_names, const object& current_module_name, bool cpp_types)
    {
        str param;

        python::detail::signature_element  const * s = f.signature();
        if (cpp_types)
        {
            if(!n)
                s = &f.get_return_type();
            if (s[n].basename == 0)
            {
                return str("...");
            }

            param = str(s[n].basename);

            if (s[n].lvalue)
                 param += " {lvalue}";

        }
        else
        {
            if (n) //we are processing an argument and trying to come up with a name for it
            {
                object kv;
                if ( arg_names && (kv = arg_names[n-1]) )
                    param = str( " (%s)%s" % make_tuple(py_type_str(s[n], current_module_name),kv[0]) );
                else
                    param = str(" (%s)%s%d" % make_tuple(py_type_str(s[n], current_module_name),"arg", n) );
            }
            else //we are processing the return type
                param = py_type_str(f.get_return_type(), current_module_name);
        }

        //an argument - check for default value and append it
        if(n && arg_names)
        {
            object kv(arg_names[n-1]);
            if (kv && len(kv) == 2)
            {
                param = str("%s=%r" % make_tuple(param, kv[1]));
            }
        }
        return param;
    }

    str function_doc_signature_generator::pretty_signature(function const *f, size_t n_overloads,  bool cpp_types )
    {
        py_function
            const& impl = f->m_fn;
            ;


        unsigned arity = impl.max_arity();

        if(arity == unsigned(-1))// is this the proper raw function test?
        {
            return raw_function_pretty_signature(f,n_overloads,cpp_types);
        }

        list formal_params;

        size_t n_extra_default_args=0;

        for (unsigned n = 0; n <= arity; ++n)
        {
            str param;

            formal_params.append(
                parameter_string(impl, n, f->m_arg_names, f->get_module(), cpp_types)
                );

            // find all the arguments with default values preceeding the arity-n_overloads
            if (n && f->m_arg_names)
            {
                object kv(f->m_arg_names[n-1]);

                if (kv && len(kv) == 2)
                {
                    //default argument preceeding the arity-n_overloads
                    if( n <= arity-n_overloads)
                        ++n_extra_default_args;
                }
                else
                    //argument without default, preceeding the arity-n_overloads
                    if( n <= arity-n_overloads)
                        n_extra_default_args = 0;
            }
        }

        n_overloads+=n_extra_default_args;

        if (!arity && cpp_types)
            formal_params.append("void");

        str ret_type (formal_params.pop(0));
        if (cpp_types )
        {
            return str(
                "%s %s(%s%s%s%s)"
                % boost::python::make_tuple // workaround, see top
                ( ret_type
                    , f->m_name
                    , str(",").join(formal_params.slice(0,arity-n_overloads))
                    , n_overloads ? (n_overloads!=arity?str(" [,"):str("[ ")) : str()
                    , str(" [,").join(formal_params.slice(arity-n_overloads,arity))
                    , std::string(n_overloads,']')
                    ));
        }else{
            return str(
                "%s(%s%s%s%s) -> %s"
                % boost::python::make_tuple // workaround, see top
                ( f->m_name
                    , str(",").join(formal_params.slice(0,arity-n_overloads))
                    , n_overloads ? (n_overloads!=arity?str(" [,"):str("[ ")) : str()
                    , str(" [,").join(formal_params.slice(arity-n_overloads,arity))
                    , std::string(n_overloads,']')
                    , ret_type
                    ));
        }

        return str(
            "%s %s(%s%s%s%s) %s"
            % boost::python::make_tuple // workaround, see top
            ( cpp_types?ret_type:str("")
                , f->m_name
                , str(",").join(formal_params.slice(0,arity-n_overloads))
                , n_overloads ? (n_overloads!=arity?str(" [,"):str("[ ")) : str()
                , str(" [,").join(formal_params.slice(arity-n_overloads,arity))
                , std::string(n_overloads,']')
                , cpp_types?str(""):ret_type
                ));

    }

    namespace detail {    
        char py_signature_tag[] = "PY signature :";
        char cpp_signature_tag[] = "C++ signature :";
    }

    list function_doc_signature_generator::function_doc_signatures( function const * f)
    {
        list signatures;
        std::vector<function const*> funcs = flatten( f);
        std::vector<function const*> split_funcs = split_seq_overloads( funcs, true);
        std::vector<function const*>::const_iterator sfi=split_funcs.begin(), fi;
        size_t n_overloads=0;
        for (fi=funcs.begin(); fi!=funcs.end(); ++fi)
        {
            if(*sfi == *fi){
                if((*fi)->doc())
                {
                    str func_doc = str((*fi)->doc());
                    
                    int doc_len = len(func_doc);

                    bool show_py_signature = doc_len >= int(sizeof(detail::py_signature_tag)/sizeof(char)-1)
                                            && str(detail::py_signature_tag) == func_doc.slice(0, int(sizeof(detail::py_signature_tag)/sizeof(char))-1);
                    if(show_py_signature)
                    {
                        func_doc = str(func_doc.slice(int(sizeof(detail::py_signature_tag)/sizeof(char))-1, _));
                        doc_len = len(func_doc);
                    }
                    
                    bool show_cpp_signature = doc_len >= int(sizeof(detail::cpp_signature_tag)/sizeof(char)-1)
                                            && str(detail::cpp_signature_tag) == func_doc.slice( 1-int(sizeof(detail::cpp_signature_tag)/sizeof(char)), _);
                    
                    if(show_cpp_signature)
                    {
                        func_doc = str(func_doc.slice(_, 1-int(sizeof(detail::cpp_signature_tag)/sizeof(char))));
                        doc_len = len(func_doc);
                    }
                    
                    str res="\n";
                    str pad = "\n";

                    if(show_py_signature)
                    { 
                        str sig = pretty_signature(*fi, n_overloads,false);
                        res+=sig;
                        if(doc_len || show_cpp_signature )res+=" :";
                        pad+= str("    ");
                    }
                    
                    if(doc_len)
                    {
                        if(show_py_signature)
                            res+=pad;
                         res+= pad.join(func_doc.split("\n"));
                    }

                    if( show_cpp_signature)
                    {
                        if(len(res)>1)
                            res+="\n"+pad;
                        res+=detail::cpp_signature_tag+pad+"    "+pretty_signature(*fi, n_overloads,true);
                    }
                    
                    signatures.append(res);
                }
                ++sfi;
                n_overloads = 0;
            }else
                ++n_overloads ;
        }

        return signatures;
    }


}}}

