#! /usr/bin/env python

"""Program to generate Xrl Target related files"""

from optparse import OptionParser
import os, sys

# This is a bit of mess as this code was split into separate files
import Xif.util

from Xif.util import \
     joining_csv, csv, cpp_name, caps_cpp_classname, \
     cpp_classname, xorp_indent_string, xorp_indent

from Xif.xiftypes import \
     XrlArg, XrlMethod, XrlInterface, XrlTarget

from Xif.parse import \
     XifParser

from Xif.kdoc import \
     XifKdocThing

# -----------------------------------------------------------------------------
# Xrl output list
# -----------------------------------------------------------------------------

def xrl_arg_str(a):
    return a.name() + ":" + a.type()

def output_xrls(tgt_name, methods):
    s = ""

    for m in methods:
        kdoc_note = kdoc_comment(m, "")
        if len(kdoc_note):
            s += kdoc_note + "\n"

        s += "finder://%s/%s" % (tgt_name, m.name())
        if len(m.args()):
            s += "?"
            s += csv(map(xrl_arg_str, m.args()), "&")
        if len(m.rargs()):
            s += "->"
            s += csv(map(xrl_arg_str, m.rargs()), "&")
        s += "\n\n"

    return s

# -----------------------------------------------------------------------------
# Target file output related
# -----------------------------------------------------------------------------

def kdoc_comment(method, indent_chars, preamble = ""):
    kdoc_source = method.annotation()
    if kdoc_source == "":
        return ""

    kdt = Xif.kdoc.parse_kdoc_comment(kdoc_source)

    params = []
    for a in method.args():
        params.append(a.name())
    for a in method.rargs():
        params.append(a.name())

    return kdt.output_kdoc(indent_chars, params, preamble)

def target_declare_handler_table(cls):
    s = """
    struct handler_table {
        const char *name;
        XrlCmdRT (%s::*method)(const XrlArgs&, XrlCmdOT);
    };

    static const struct handler_table handlers[];
    static const size_t num_handlers;
""" % cls
    return s


def target_define_handler_table(cls, methods):
    s = """
const struct %s::handler_table %s::handlers[] = {
""" % (cls, cls)

    for m in methods:
        s += "    { \"%s\",\n      &%s::handle_%s },\n" % (m.name(), cls, cpp_name(m.name()))

    s += """};

const size_t %s::num_handlers = (sizeof(%s::handlers) / sizeof(%s::handlers[0]));

""" % (cls, cls, cls)
    return s

def target_virtual_fns(methods):
    r = ""
    for x in methods:
        kdoc_note = kdoc_comment(x, "    ",
                                 "Pure-virtual function that needs to be implemented to:")
        if len(kdoc_note):
            r += kdoc_note + "\n"
        r += "    virtual XrlCmdError %s("% cpp_name(x.name())

        # input args
        args = []
        if len(x.args()):
            args.append("\n%s// Input values" % xorp_indent(2))

        for a in x.args():
            cpa = "\n%sconst %s&\t%s" % \
                  (xorp_indent(2), a.cpp_type(), cpp_name(a.name()))
            args.append(cpa)

        # output args

        if len(x.rargs()):
            args.append("\n%s// Output values" % xorp_indent(2))

        for a in x.rargs():
            cpa = "\n%s%s&\t%s" % \
                  (xorp_indent(2), a.cpp_type(), cpp_name(a.name()))
            args.append(cpa)
        r += csv(args)

        r += ") = 0;\n"



        r += "#ifdef XORP_ENABLE_ASYNC_SERVER\n"
        r += "    typedef\n"
        r += "    XorpCallback%s<void, const XrlCmdError &" % (len(x.rargs()) + 1)
        for a in x.rargs():
            r += ",\n%sconst %s*" % (xorp_indent(2), a.cpp_type())
        r += ">::RefPtr\n    %sRF;\n\n" % (caps_cpp_classname(x.name()))


        r += "    class %sCB {\n" % (caps_cpp_classname(x.name()))
        r += "        %sRF cb;\n\n" % (caps_cpp_classname(x.name()))
        r += "    public:\n"
        r += "        operator const %sRF& () const { return cb; }\n" \
            % (caps_cpp_classname(x.name()))
        r += "        const %sRF& operator ->() const { return cb; }\n" \
            % (caps_cpp_classname(x.name()))

        r += "        %sCB(const %sRF& cb)\n" \
            % (caps_cpp_classname(x.name()), caps_cpp_classname(x.name()))
        r += "          : cb(cb) { }\n\n"

        r += "        void fail(const XrlCmdError &e)"
        r += " const { cb->dispatch(e"
        for a in x.rargs():
            r += ", NULL"
        r += "); }\n\n"

        r += "        void respond("
        sep = ""
        for a in x.rargs():
            r += sep
            sep = ",\n                     "
            r += "const %s& arg_%s" \
                % (a.cpp_type(), cpp_name(a.name()))
        r += ") const {\n"
        r += "            cb->dispatch(XrlCmdError::OKAY()"
        for a in x.rargs():
            r += ",\n                         &arg_%s" \
                % (cpp_name(a.name()))
        r += ");\n"
        r += "        }\n"

        r += "    };\n\n"



        r += "    struct %sRsp :\n      public %sCB {\n" \
            % (caps_cpp_classname(x.name()), caps_cpp_classname(x.name()))
        if x.rargs():
            r += "        struct args_str {\n"
            for a in x.rargs():
                r += "            %s %s;\n" \
                    % (a.cpp_type(), cpp_name(a.name()))
            r += "        };\n"
            r += "\n    private:\n"
            r += "        args_str args;\n"
            r += "\n    public:\n"

        r += "        %sRsp(const %sRF& cb)\n" \
            % (caps_cpp_classname(x.name()), caps_cpp_classname(x.name()))
        r += "          : %sCB(cb)" % (caps_cpp_classname(x.name()))
        r += " { }\n\n"

        if x.rargs():
            r += "        void respond() const {\n"
            r += "            %sCB::\n" % (caps_cpp_classname(x.name()))
            r += "            respond("
            sep = ""
            for a in x.rargs():
                r += sep;
                sep = ",\n                        "
                r += "args.%s" % (cpp_name(a.name()))
            r += ");\n"
            r += "        }\n\n"
            r += "        args_str* operator ->() {\n"
            r += "            return &args;\n"
            r += "        }\n"

        r += "    };\n\n"


        r += "    virtual void async_%s\n       (" \
            % cpp_name(x.name())

        # input args
        for a in x.args():
            r += "\n%sconst %s&\t%s," % \
                  (xorp_indent(2), a.cpp_type(), cpp_name(a.name()))

        r += "\n%s%sCB);\n" % \
            (xorp_indent(2), caps_cpp_classname(x.name()))
        r += "#endif\n\n"

    return r

def target_declare_handlers(methods):
    s = ""
    for x in methods:
        s += "    XrlCmdRT handle_%s(const XrlArgs& in, XrlCmdOT out);\n" \
             % cpp_name(x.name())
        s += "#ifdef XORP_ENABLE_ASYNC_SERVER\n"

        s += "    void callback_%s\n       (const XrlCmdError &e" \
            % cpp_name(x.name())
        for a in x.rargs():
            s += ",\n%sconst %s* arg_%s" \
                % (xorp_indent(2), a.cpp_type(), cpp_name(a.name()))
        s += ",\n        XrlRespCallback);\n"
        s += "#endif\n\n"
    return s;

def target_declare_handler_hooks():
    s = "    void add_handlers(); \n    void remove_handlers();\n"
    return s

def target_handler_hooks(cls, name):
    s =  "void\n%s::add_handlers()\n{\n" % cls
    s += "    for (size_t i = 0; i < num_handlers; ++i) {\n"
    s += "        if (!_cmds->add_handler(handlers[i].name,\n"
    s += "                                callback(this, handlers[i].method))) {\n"
    s += "            XLOG_ERROR(\"Failed to register xrl handler finder://%%s/%%s\", \"%s\", handlers[i].name);\n" % name
    s += "        }\n"
    s += "    }\n"
    s += "    _cmds->finalize();\n"
    s += "}\n\n"
    
    s += "void\n%s::remove_handlers()\n{\n" %cls
    s += "     for (size_t i = 0; i < num_handlers; ++i) {\n"
    s += "         _cmds->remove_handler(handlers[i].name);\n"
    s += "     }\n"
    s += "}\n"

    return s;

def target_handler_methods(cls, name, methods):
    s = ""
    for m in methods:
        s += "\n#ifdef XORP_ENABLE_ASYNC_SERVER\n"
        s += "void\n"
        s += "%s::callback_%s\n    (const XrlCmdError &e" \
            % (cls, cpp_name(m.name()))
        for r in m.rargs():
            s += ",\n     "
            s += "const %s* rarg_%s" % (r.cpp_type(), cpp_name(r.name()))
        s += ",\n     XrlRespCallback c_b)\n"
        s += "{\n"

        s += \
"""    if (e != XrlCmdError::OKAY()) {
	XLOG_WARNING(\"Handling method for %%s failed: %%s\",
		     \"%s\", e.str().c_str());
	return c_b->dispatch(e, NULL);
    } else {
""" % m.name()

        s += xorp_indent(2) + "XrlArgs out;\n"
        if m.rargs():
            s += "\n        /* Marshall return values */\n        try {\n"
            for r in m.rargs():
                s += xorp_indent(3) + "out.add(\"%s\", *rarg_%s);\n" % \
                    (r.name(), cpp_name(r.name()))
            s += \
"""        } catch (const XrlArgs::XrlAtomFound& ) {
	    XLOG_FATAL("Duplicate atom name"); /* XXX Should never happen */
        }

"""
        s += "        return c_b->dispatch(e, &out);\n    }\n}\n\n"




        s += "\nvoid\n%s::async_%s(" \
            % (cls, cpp_name(m.name()))

        # input args
        for a in m.args():
            s += "\n%sconst %s&\targ_%s," % \
                (xorp_indent(2), a.cpp_type(), cpp_name(a.name()))

        s += "\n%s%sCB c_b)\n{\n" % \
            (xorp_indent(2), caps_cpp_classname(m.name()))

	s += "\n    /* Return value declarations */\n"
	for r in m.rargs():
            s += "    %s rarg_%s;\n" % (r.cpp_type(), cpp_name(r.name()))

        s += "    XrlCmdError e = %s(" % cpp_name(m.name())
        sep = ""
	for r in m.args():
            s += "%s\n        arg_%s" % (sep, cpp_name(r.name()))
            sep = ","
        for r in m.rargs():
            s += "%s\n        rarg_%s" % (sep, cpp_name(r.name()))
            sep = ","
        s += ");\n"
        s += "    return c_b->dispatch(e"
	for r in m.rargs():
            s += ",\n        &rarg_%s" % (cpp_name(r.name()))
            sep = ","
        s += ");\n"
        s += "}\n"
        s += "#endif\n\n"



        s += "XrlCmdRT\n"
        s += "%s::handle_%s(const XrlArgs& xa_inputs, XrlCmdOT pxa_outputs)\n" % \
             (cls, cpp_name(m.name()))
        s += "{"
        s += """
    if (xa_inputs.size() != %d) {
	XLOG_ERROR(\"Wrong number of arguments (%%u != %%u) handling %%s\",
            XORP_UINT_CAST(%d), XORP_UINT_CAST(xa_inputs.size()), \"%s\");
	XRL_CMD_RETURN_ERROR(pxa_outputs, XrlCmdError::BAD_ARGS());
    }
""" % (len(m.args()), len(m.args()), m.name())

        if len(m.rargs()):
            s += """
#ifndef XORP_ENABLE_ASYNC_SERVER
    if (pxa_outputs == 0) {
	XLOG_FATAL(\"Return list empty\");
	return XrlCmdError::BAD_ARGS();
    }
#endif

"""
        if len(m.rargs()) == 0:
            s += """
#ifndef XORP_ENABLE_ASYNC_SERVER
    UNUSED(pxa_outputs);
#endif

"""

        s += "#ifdef XORP_ENABLE_ASYNC_SERVER\n"


        s += xorp_indent(1) + "try {\n"
        s += xorp_indent(2) + \
            "%sRF mycb =\n%scallback(this, &%s::callback_%s, pxa_outputs);\n" \
            % (caps_cpp_classname(m.name()), xorp_indent(3), \
                   cls, cpp_name(m.name()))

        s += xorp_indent(2) + "async_%s(" % cpp_name(m.name())
	i = 0
        for a in m.args():
            s += "\n" + xorp_indent(3) + \
                "xa_inputs.get(%d, \"%s\").%s()," \
                % (i, a.name(), a.accessor())
            i += 1
        s += " mycb);\n"


	s += \
"""    } catch (const XrlArgs::BadArgs& e) {
	XLOG_ERROR(\"Error decoding the arguments: %s\", e.str().c_str());
	return pxa_outputs->dispatch(XrlCmdError::BAD_ARGS(e.str()), NULL);
    }
"""


        s += "#else\n"


    	s += "\n    /* Return value declarations */\n"
    	for r in m.rargs():
            s += "    %s r_%s;\n" % (r.cpp_type(), cpp_name(r.name()))


        s += xorp_indent(1) + "try {\n"
        s += xorp_indent(2) + "XrlCmdError e = %s(" % cpp_name(m.name())
        get_reqs = []
	i = 0
        for a in m.args():
            get_reqs.append("\n" + xorp_indent(3) + \
                            "xa_inputs.get(%d, \"%s\").%s()" \
                            % (i, a.name(), a.accessor()))
	    i += 1
        ret_vals = []
        for r in m.rargs():
            ret_vals.append("\n" + xorp_indent(3) + "r_%s" % cpp_name(r.name()))
        s += csv(get_reqs + ret_vals, ",") + ");\n"

        s += \
"""	if (e != XrlCmdError::OKAY()) {
	    XLOG_WARNING(\"Handling method for %%s failed: %%s\",
            		 \"%s\", e.str().c_str());
	    return e;
        }
""" % m.name()

    	s += \
"""    } catch (const XrlArgs::BadArgs& e) {
	XLOG_ERROR(\"Error decoding the arguments: %s\", e.str().c_str());
	return XrlCmdError::BAD_ARGS(e.str());
    }
"""

        if m.rargs():
            s += "\n    /* Marshall return values */\n    try {\n"
            for r in m.rargs():
                s += xorp_indent(2) + "pxa_outputs->add(\"%s\", r_%s);\n" % \
                    (r.name(), cpp_name(r.name()))
            s += \
"""    } catch (const XrlArgs::XrlAtomFound& ) {
	XLOG_FATAL("Duplicate atom name"); /* XXX Should never happen */
    }
"""

        s += "    return XrlCmdError::OKAY();\n"


        s += "#endif\n"
        s += "}\n\n"
    return s

def protect(file):
 # remove direcory component
    r = file.rfind("/") + 1
    return "__XRL_TARGETS_%s__" % file[r:].upper().replace(".", "_")

def prepare_target_hh(modulename, hh_file):
    s   = Xif.util.standard_preamble(1, hh_file)
    s  += \
"""
#ifndef %s
#define %s

#undef XORP_LIBRARY_NAME
#define XORP_LIBRARY_NAME "%s"

#include "libxorp/xlog.h"
#include "libxipc/xrl_cmd_map.hh"
""" % (protect(hh_file), protect(hh_file), modulename)
    return s

def output_target_hh(cls, tgt_name, tgt_version, methods):
    s = """
class %s {
protected:
    XrlCmdMap* _cmds;

public:
    /**
     * Constructor.
     *
     * @param cmds an XrlCmdMap that the commands associated with the target
     *		   should be added to.  This is typically the XrlRouter
     *		   associated with the target.
     */
    %s(XrlCmdMap* cmds = 0);

    /**
     * Destructor.
     *
     * Dissociates instance commands from command map.
     */
    virtual ~%s();

    /**
     * Set command map.
     *
     * @param cmds pointer to command map to associate commands with.  This
     * argument is typically a pointer to the XrlRouter associated with the
     * target.
     *
     * @return true on success, false if cmds is null or a command map has
     * already been supplied.
     */
    bool set_command_map(XrlCmdMap* cmds);

    /**
     * Get Xrl instance name associated with command map.
     */
    const string& get_name() const { return _cmds->name(); }

    /**
     * Get version string of instance.
     */
    const char* version() const { return "%s/%s"; }

protected:

""" % (cls, cls, cls, tgt_name, tgt_version)

    s += target_virtual_fns(methods)
    s += "private:\n"
    s += target_declare_handlers(methods)
    s += target_declare_handler_hooks()
    s += target_declare_handler_table(cls)

    s += "};\n"

    return s

def finish_target_hh(hh_file):
    return "\n#endif // %s\n" % protect(hh_file)

def prepare_target_cc(target_hh, target_cc):
    r = target_hh.rfind("/") + 1
    s = Xif.util.standard_preamble(0, target_cc)
    s += "\n#include \"%s\"\n\n" % target_hh[r:]
    return s

def output_target_cc(cls, tgt_name, methods):
    s = target_define_handler_table(cls, methods)
    s += """
%s::%s(XrlCmdMap* cmds)
    : _cmds(cmds)
{
    if (_cmds)
	add_handlers();
}

%s::~%s()
{
    if (_cmds)
	remove_handlers();
}

bool
%s::set_command_map(XrlCmdMap* cmds)
{
    if (_cmds == 0 && cmds) {
        _cmds = cmds;
        add_handlers();
        return true;
    }
    if (_cmds && cmds == 0) {
	remove_handlers();
        _cmds = cmds;
        return true;
    }
    return false;
}

""" % (cls, cls, cls, cls, cls)
    s += target_handler_methods(cls, tgt_name, methods)
    s += target_handler_hooks(cls, tgt_name)

    return s

def generate_target_methods(tgt, interfaces):
    methods = []
    # tgt.interfaces is a list of tuples ("interface_name", "interface_version")
    # Convert this to a list of methods
    for tif_info in tgt.interfaces():
        found = 0
        for tif in interfaces:
            if (tif.name() == tif_info[0]) & (tif.version() == tif_info[1]):
                found = 1
                break
        if found == 0:
            print "Error interface %s data not found" % tif_info[0]
            sys.exit(1)
        for m in tif.methods():
            full_name = Xif.util.xrl_method_name(tif.name(), tif.version(), m.name())
            fq_method = m
            fq_method.set_name(full_name)
            methods.append(fq_method)
    return methods


def main():
    usage = "usage: %prog [options] arg"
    parser = OptionParser(usage)
    parser.add_option("-o", "--output-dir",
		      action="store", 
		      type="string", 
		      dest="output_dir",
		      metavar="DIR")
    parser.add_option("-I",
                      action="append",
                      type="string",
                      dest="includes",
                      metavar="DIR")
    (options,args) = parser.parse_args()

    if len(args) != 1:
        parser_error("incorrect number of arguments")

    # Command line arguments passed on to cpp
    pipe_string = "cpp -C "
    if options.includes:
	for a in options.includes:
	    pipe_string += "-I%s " % a
    pipe_string += args[0] 

    cpp_pipe = os.popen(pipe_string, 'r')

    xp = XifParser(cpp_pipe)

    tgts = xp.targets()
    if len(tgts) == 0:
        print "No targets found in input files."
        sys.exit(1)

    sourcefile = tgts[0].sourcefile()
    for tgt in tgts:
        if (tgt.sourcefile() != sourcefile):
            print "Multiple .tgt files presented, expected just one."
            sys.exit(1)

    # basename transformation - this is a lame test
    if sourcefile[-4:] != ".tgt":
        print "Source file does not end in .tgt suffix - basename transform failure."
        sys.exit(1)
        
    basename = sourcefile[:-4]
    basename = basename[basename.rfind("/") + 1:]

    modulename = "Xrl%sTarget" % cpp_classname(basename)
    hh_file = "%s_base.hh" % basename
    cc_file = "%s_base.cc" % basename
    xrl_file = "%s.xrls" % basename

    if options.output_dir:
        hh_file = os.path.join(options.output_dir, hh_file)
        cc_file = os.path.join(options.output_dir, cc_file)
        xrl_file = os.path.join(options.output_dir, xrl_file)

    hh_txt  = prepare_target_hh(modulename, hh_file)
    cc_txt  = prepare_target_cc(hh_file, cc_file)
    xrl_txt = Xif.util.standard_preamble(1, xrl_file)

    for tgt in tgts:
        tgt_methods = generate_target_methods(tgt, xp.interfaces())
        cls = "Xrl%sTargetBase" % cpp_classname(tgt.name())
        hh_txt += output_target_hh(cls, tgt.name(), tgt.version(), tgt_methods)
        hh_txt += finish_target_hh(hh_file)
        cc_txt += output_target_cc(cls, tgt.name(), tgt_methods)
        xrl_txt += output_xrls(tgt.name(), tgt_methods)

    Xif.util.file_write_string(hh_file, hh_txt)
    Xif.util.file_write_string(cc_file, cc_txt)
    Xif.util.file_write_string(xrl_file, xrl_txt)

if __name__ == '__main__':
    main()
