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

"""Program to generate compatible Thrift service defintions from a Xif spec"""

#
# XIF to Thrift Translator
#
# The XRL wrapper generators have been refactored to generate
# Thrift transport/protocol calls. They try to use the equivalent
# wire protocol generated by native Thrift stubs wherever possible.
#
# E.g. Thrift will always try to marshal an empty T_STRUCT even for
# methods declared void. It's up to the TProtocol implementation to
# marshal these more efficiently, or not. This exists so wire-level
# protocols which use textual marshaling (e.g. non-binary JSON, etc)
# can be supported using the same API layer cake.
#
# However, in order to call XORP processes natively from Thrift,
# we need to generate wrappers which match these semantics. This means
# bundling XIF return types (after the -> symbol) for each method into
# a Thrift struct {} declaration, and using that as the return type.
#
# As XORP code is refactored, or new XORP code is written to use emerging
# native Thrift async APIs, we can use these directly. Existing code which
# is synchronous can use the Thrift declarations produced by this generator.
#

from optparse import OptionParser
import os, sys

import Xif.util
from Xif.util import csv
from Xif.parse import XifParser

# -----------------------------------------------------------------------------
# Thrift IDL file output related
# -----------------------------------------------------------------------------

def prepare_thrift_file(thrift_file):
    s = Xif.util.standard_preamble(1, thrift_file)
    s += "namespace cpp xorp.ipc\n\n"
    s += "include \"xorp.thrift\"\n\n"
    return s

def finish_thrift_file(thrift_file):
    s = ""
    return s

def generate_result_structs(service_name, methods):
    s = ""
    for m in methods:
	if len(m.rargs()) == 0:
	    continue
	s += "struct %s_result {\n" % m.name()
	i = 1
	m_rargs_list = []
	for a in m.rargs():
	    t_idl_type = a.thrift_idl_type()
	    if a.is_list():
		t_idl_type += "<%s>" % a.member().thrift_idl_type()
	    m_rargs_list.append(" %d: %s %s" % (i, t_idl_type, a.name()))
	    i += 1
	s += csv(m_rargs_list, comma=',\n')
	s += "\n}\n\n"
    return s

def generate_service(service_name, service_version, methods):
    s = "/*\n * Based on Xif: %s/%s\n */\n" % (service_name, service_version)
    s += "service %s {\n" % service_name
    for m in methods:
	m_ret = "void"
	m_args = ""
	if len(m.rargs()) > 0:
	    m_ret = "%s_result" % m.name()
	if len(m.args()) > 0:
	    i = 1
	    m_args_list = []
	    for a in m.args():
		t_idl_type = a.thrift_idl_type()
		if a.is_list():
		    t_idl_type += "<%s>" % a.member().thrift_idl_type()
		m_args_list.append("%d: %s %s" % (i, t_idl_type, a.name()))
		i += 1
	    m_args = csv(m_args_list, comma=', ')
	s += " %s %s(%s),\n" % (m_ret, m.name(), m_args)
    s += "}\n"
    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:]

    thrift_file = "%s.thrift" % basename

    if options.output_dir:
	thrift_file = os.path.join(options.output_dir, thrift_file)

    thrift_txt = prepare_thrift_file(thrift_file)
    for xif in xifs:
	thrift_txt += generate_result_structs(xif.name(), xif.methods())
    for xif in xifs:
	thrift_txt += generate_service(xif.name(), xif.version(), xif.methods())

    thrift_txt += finish_thrift_file(thrift_file)
    Xif.util.file_write_string(thrift_file, thrift_txt)

if __name__ == '__main__':
    main()
