#! /usr/bin/env python
# vim:set sts=4 ts=8 sw=4:

"""Program to generate Xrl Interface Client 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, cpp_classname, caps_cpp_classname,	      \
     cpp_version, xorp_indent_string, xorp_indent

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

from Xif.parse import \
     XifParser

from Xif.thrifttypes import \
    wire_type, send_arg, recv_arg

# -----------------------------------------------------------------------------
# Client Interface file output related
# -----------------------------------------------------------------------------

def declare_send_xrl(method_no, method):
    rtypes = []
    for r in method.rargs():
        rtypes.append("const " + r.cpp_type() + "*")
    cb_name = "%sCB" % (caps_cpp_classname(method.name()))
    s = "    typedef XorpCallback%s<void, const XrlError&%s>::RefPtr %s;\n" \
        % (1 + len(rtypes), joining_csv(rtypes), cb_name)

    atypes = ["\n\tconst char*\tdst_xrl_target_name"]
    for a in method.args():
        atypes.append("\n\tconst %s&\t%s" % (a.cpp_type(), a.name()))
    atypes.append("\n\tconst %s&\tcb" % cb_name)
    s += "    bool send_%s(%s\n    );\n\n" \
        % (cpp_name(method.name()), csv(atypes))
    return s

def implement_send_argument(a, fid):
    lines = []
    lines.append("nout += outp->writeFieldBegin(\"%s\", %s, %d);" % \
	(a.name(), wire_type(a), fid))
    lines += send_arg(a)
    lines.append("nout += outp->writeFieldEnd();")
    return lines

def implement_send_xrl(cls, method_no, method, ifqname):
    cpp_method_name = cpp_name(method.name())
    cb_name = "%sCB" % (caps_cpp_classname(method.name()))
    atypes = ["\n\tconst char*\tdst_xrl_target_name"]
    for a in method.args():
        atypes.append("\n\tconst %s&\t%s" % (a.cpp_type(), a.name()))
    atypes.append("\n\tconst %s&\tcb" % cb_name)
    s = "\nbool\n%s::send_%s(%s\n)\n" \
        % (cls, cpp_method_name, csv(atypes))
    # TODO: call TMessenger interface which bumps cseqid for outgoing.
    s += "{\n"
    s += "    using namespace apache::thrift::protocol;\n"
    s += "\n"
    s += "    TProtocol *outp = 0;\n"
    s += "    uint32_t nout = 0;\n"
    s += "    int32_t cseqid = 0;\n"
    s += "\n"
    s += "    // Begin Thrift RPC message\n"
    s += "    nout += outp->writeMessageBegin(\"%s\", T_CALL, cseqid);\n" % \
	(cpp_method_name)
    #
    # Generate the marshal-out code for each argument as a list of strings;
    # collate them, and append to the output file with correct indentation.
    #
    # Stay compatible with Thrift generated stubs; always write
    # passed arguments out as a struct, even if we don't use them.
    # This exists mainly to allow us to later change the protocol
    # using field IDs.
    # XifMethod.args() are inputs, XifMethod.rargs() are outputs.
    #
    s += "    // Begin arguments\n"
    s += "    nout += outp->writeStructBegin(\"%s\");\n" % \
        (cpp_method_name + "_args")
    fid = 0
    for a in method.args():
	fid += 1
	lines = implement_send_argument(a, fid)
	for l in lines:
	    s += xorp_indent(1) + l + "\n"
    s += "    nout += outp->writeFieldStop();\n"
    s += "    nout += outp->writeStructEnd();\n"
    s += "    // End arguments\n"

    s += "    nout += outp->writeMessageEnd();\n"
    s += "    // End Thrift RPC message\n"
    s += "\n"
    s += "    // Flush transport and mark end of message\n"
    s += "    outp->getTransport()->flush();\n"
    s += "    outp->getTransport()->writeEnd();\n"
    s += "\n"
    # TODO: Mark as sent with TMessenger.
    s += "    return true;\n"
    # XXX
    s += "    UNUSED(dst_xrl_target_name);\n"
    s += "    UNUSED(cb);\n"
    # end function
    s += "}\n\n"
    # nout now contains # of bytes in transport buffer for this IPC
    return s

# -----------------------------------------------------------------------------
# Unmarshalling callbacks
# -----------------------------------------------------------------------------

# XXX XrlError is preserved and should be passed in from
# the libxipc thrifted transport to record any transport error
# whilst remaining compatible with legacy XORP code base
# XXX ctx will change to be TMessenger or similar.

def declare_unmarshall(method_name):
    s = xorp_indent_string(1, "static void ")
    s += "unmarshall_%s(" % method_name
    cb_name = "%sCB" % (caps_cpp_classname(method_name))
    args = [ "const XrlError&\te", "void\t*ctx", "%s\t\tcb" % cb_name]
    for i in range(0, len(args)):
        args[i] = "\n\t" + args[i]
    s += csv(args)
    s += "\n%s);\n\n" % xorp_indent(1)
    return s

# Generate code intended to:
#  Transform an incoming Thrift RPC T_REPLY message into an XRL method
#  return callback at the client.
#
# When this routine is called, the T_REPLY/T_EXCEPTION part of
# the message has already been parsed. We will only need to parse
# the return value if it's a T_REPLY. If it is a T_EXCEPTION, then
# this function will be called to ensure the client's callback is
# invoked with an appropriate XrlError.
#
# XRL callbacks must always be invoked, with XrlError set to an
# appropriate value, to indicate an error. Otherwise, they should
# eventually time out (we have yet to implement that part).
#
# Implementation note:
#
#  Thrift RPC methods always return their results in a struct, usually
#  named "success", although this is never exposed to the stubs --
#  except when declared 'oneway'.
#
#  Now, in XIF, we always expect a reply to an RPC; there is no
#  equivalent of Thrift's 'oneway' method. In this case, the
#  structure returned (according to the XIF) should be empty.
#  We can skip this, or attempt to parse it.
#
#  However, to facilitate interworking between XIF code and Thrift code,
#  the thrift-gen translator will define another struct to be returned
#  as the real return value; so we need to unmarshall 2 layers of struct.
#
# TODO: Handle T_EXCEPTION passed to this function further up.
#
def implement_unmarshall(cls, method_no, method):
    s = ""
    arg_checks_enabled = True

    nargs = []
    for r in method.rargs():
        nargs.append("0")
    fail_args = joining_csv(nargs)

    s += "void\n%s::unmarshall_%s(" % (cls, method.name())
    cb_name = "%sCB" % (caps_cpp_classname(method.name()))
    args = [ "const XrlError&\te", "void\t*ctx", "%s\t\tcb" % cb_name]
    for i in range(0, len(args)):
        args[i] = "\n\t" + args[i]
    s += csv(args)
    s += "\n)\n"
    s += "{\n"
    s += "    using namespace apache::thrift::protocol;\n"
    s += "\n"
    s += "    if (e != XrlError::OKAY()) {\n"
    s += "        cb->dispatch(e%s);\n" % fail_args
    s += "        return;\n"
    s += "    }\n"
    s += "\n"
    s += "    TProtocol *inp = 0;\n"
    s += "    uint32_t nin = 0;\n"
    s += "\n"

    if len(method.rargs()) == 0:
	s += "    /* Ignore void result for XIF method call.*/\n"
	s += "    nin += inp->skip(T_STRUCT);\n"
    else:
	if arg_checks_enabled:
	    s += "#ifndef XIF_DISABLE_CLIENT_INPUT_CHECKS\n"
	    s += "    bitset<%d> argf;\n" % len(method.rargs())
	    s += "#endif\n"
	s += "    /* Return value declarations */\n"
	for r in method.rargs():
	    s += "    %s %s;\n" % (r.cpp_type(), cpp_name(r.name()))
	s += "\n"
	s += "    /* Begin parsing outer \"success\" struct. */\n"
	s += "    string fname;\n"
	s += "    TType ftype;\n"
	s += "    int16_t fid;\n"
	s += "\n"
	s += "    nin += inp->readStructBegin(fname);\n"
	s += "    for (;;) {\n"
	s += "        nin += inp->readFieldBegin(fname, ftype, fid);\n"
	s += "        if (ftype == T_STOP) {\n"
	s += "            break;\n"
	s += "        } else if (fid == 0 && ftype == T_STRUCT) {\n"
	s += "            /* Begin parsing inner method_result struct. */\n"
	s += "            nin += inp->readStructBegin(fname);\n"
	s += "            for (;;) {\n"
	s += "                nin += inp->readFieldBegin(fname, ftype, fid);\n"
	s += "                if (ftype == T_STOP)\n"
	s += "                    break;\n"
	s += "                switch (fid) {\n"
	# Parse each field expected in this struct.
	rfid = 0
	for r in method.rargs():
	    rfid += 1
	    s += "                case %d:\n" % rfid
	    s += "                    if (ftype == %s) {\n" % wire_type(r)
	    for l in recv_arg(r):
	        s += xorp_indent(6) + l + "\n"
	    if arg_checks_enabled:
		s += "#ifndef XIF_DISABLE_CLIENT_INPUT_CHECKS\n"
		s += xorp_indent(6) + "argf.set(%d);\n" % (rfid - 1)
		s += "#endif\n"
	    s += "                    }\n"
	    s += "                    break;\n"
	s += "                default:\n"
	s += "                    nin += inp->skip(ftype);\n"
	s += "                    break;\n"
	s += "                }\n"
	s += "                nin += inp->readFieldEnd();\n"
	s += "            }\n"
	s += "            nin += inp->readStructEnd();\n"
	s += "            /* End parsing inner method_result struct. */\n"
	s += "        } else {\n"
	s += "            nin += inp->skip(ftype);\n"
	s += "        }\n"
	s += "    }\n"
	s += "    nin += inp->readStructEnd();\n"
	s += "    /* End parsing outer \"success\" struct. */\n"
    # end of return argument parsing
    s += "    nin += inp->readMessageEnd();\n"
    s += "\n"
    s += "    inp->getTransport()->readEnd();\n"
    s += "\n"

    # final argument count check.
    if len(method.rargs()) > 0 and arg_checks_enabled:
	s += "#ifndef XIF_DISABLE_CLIENT_INPUT_CHECKS\n"
	s += "    if (argf.count() != %d) {\n" % len(method.rargs())
	s += "#if 0\n"
	s += "        XLOG_ERROR(\"Wrong number of arguments (%%u != %%u)\",\n"
	s += "            XORP_UINT_CAST(argf.count()),\n"
	s += "            XORP_UINT_CAST(%d));\n" % len(method.rargs())
	s += "#endif // no XLOG in here just yet.\n"
	s += "        cb->dispatch(XrlError::BAD_ARGS()%s);\n" % fail_args
	s += "    }\n"
	s += "#endif\n"
	s += "\n"

    # now dispatch a successful XRL return.
    v = []
    for r in method.rargs():
        v.append("&%s" % cpp_name(r.name()))
    s += "    cb->dispatch(e%s);\n" % (joining_csv(v))

    s += "    UNUSED(ctx);\n"	     # XXX
    s += "}\n"

    return s

# -----------------------------------------------------------------------------
# Boilerplate code
# -----------------------------------------------------------------------------

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

def prepare_client_if_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 "libxorp/callback.hh"

#include "libxipc/xrl_error.hh"
#include "libxipc/xrl_atom.hh"
#include "libxipc/xrl_atom_list.hh"
#include "libxipc/xrl_sender.hh"

#include <boost/scoped_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/static_assert.hpp>

#include <Thrift.h>

#include <transport/TTransport.h>

#include <protocol/TProtocol.h>
#include <protocol/TBinaryProtocol.h>

#include "libxipc/xif_thrift.hh"    // XXX for xif_read_*()

#include <bitset>		// for argument checks

""" % (protect(hh_file), protect(hh_file), modulename)
    return s

def client_if_hh(cls, methods):
    s = """
class %s {
public:
    %s(XrlSender* s) : _sender(s) {}
    virtual ~%s() {}

""" % (cls, cls, cls)
    for i in range(0, len(methods)):
        s += declare_send_xrl(i, methods[i])

    s += """protected:
    XrlSender* _sender;

private:
"""
    for i in range(0, len(methods)):
        s += declare_unmarshall(methods[i].name())
    s += "};\n"
    return s

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

def prepare_client_if_cc(hh_file, cc_file):
    s = Xif.util.standard_preamble(0, cc_file)
    s += "#include \"%s\"\n" % hh_file
    return s

def client_if_cc(cls, ifname, ifversion, methods):
    s = ""
    for i in range(0, len(methods)):
        # Interface qualified name
        ifqname = "%s/%s/%s" % (ifname, ifversion, methods[i].name())
        s += implement_send_xrl(cls, i, methods[i], ifqname)
        s += implement_unmarshall(cls, i, methods[i])
    return s

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)

    if len(xp.targets()):
        print "Found targets (used a .ent rather than .xif input?)"
        sys.exit(1)

    xifs = xp.interfaces()
    if len(xifs) == 0:
        print "No interface definitions provided"
        sys.exit(1)

    # Check all interface definitions come from same source file.
    # Although we've done the hard part (parsing), generating from
    # here is still painful if we have to output multiple interface files.
    sourcefile = xifs[0].sourcefile()
    for xif in xifs:
        if (xif.sourcefile() != sourcefile):
            print "Multiple .xif files presented, expected one."
            sys.exit(1)

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

    basename = sourcefile[:-4]
    basename = basename[basename.rfind("/") + 1:]

    modulename = "Xif%s" % cpp_classname(basename)
    hh_file = "%s_xif.hh" % basename
    cc_file = "%s_xif.cc" % 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)

    # Generate header file
    hh_txt = prepare_client_if_hh(modulename, hh_file)
    for xif in xifs:
        cls = "Xrl%s%sClient" % (cpp_classname(xif.name()), \
                                  cpp_version(xif.version()))
        hh_txt += client_if_hh(cls, xif.methods())
    hh_txt += finish_client_if_hh(hh_file)
    Xif.util.file_write_string(hh_file, hh_txt)

    # Generate implementation file
    cc_txt = prepare_client_if_cc(hh_file[hh_file.rfind("/") + 1 : ], cc_file)
    for xif in xifs:
        cls = "Xrl%s%sClient" % (cpp_classname(xif.name()), \
                                  cpp_version(xif.version()))
        cc_txt += client_if_cc(cls, xif.name(), xif.version(), xif.methods())
    Xif.util.file_write_string(cc_file, cc_txt)

if __name__ == '__main__':
    main()
