#! /usr/bin/env python
# vin: sw=3 et:
'''
Copyright (C) 2012, Digium, Inc.
Matt Jordan <mjordan@digium.com>

This program is free software, distributed under the terms of
the GNU General Public License Version 2.
'''

import sys
import os
import xml.dom.minidom

from xml.dom.minidom import Element


def get_manager_event_method_type(candidate_string):
    if "ast_manager_event_multichan" in candidate_string:
        return "multichan"
    elif "ast_manager_event" in candidate_string:
        return "ast_manager_event"
    elif "manager_event" in candidate_string:
        return "manager_event"
    return ""


def parse_manager_event_instance(xml_fragment):
    ''' Parse the information for a manager event

    Keyword Arguments:
    xml_fragment    The XML fragment comment

    Returns:
    A well-formed XML fragment containing the comments passed in, as well as
    information obtained from the manager_event macro calls
    '''

    def __node_contains_parameter(node, parameter):
        ''' Return whether or not a node contains a given parameter name '''
        return any([n for n in node.getElementsByTagName("parameter")
                    if __node_contains_attribute(n, parameter)])

    def __node_contains_attribute(node, attribute_name):
        ''' Return whether or not a node contains a given attribute name '''
        return any([attr for attr in node.attributes.items()
                    if attr[1] == attribute_name])

    candidate_lines = []
    type = ""

    # Read the manager_event method call, which should occur after
    # the documentation block
    for line in sys.stdin:
        if len(line):
            candidate_lines.append(line)
        if ");" in line:
            break

    candidate_string = ''.join(candidate_lines)
    type = get_manager_event_method_type(candidate_string)
    if not type:
        # Unknown, return what we have
        return ''.join(xml_fragment)

    # strip off the macro name
    first_paren = candidate_string.index("(", 0)
    last_paren = candidate_string.rindex(");")
    candidate_string = candidate_string[first_paren + 1:last_paren]

    # split into parameter tokens
    func_parameter_tokens = candidate_string.split(',')

    if type == "manager_event" or type == "multichan":
        class_level = func_parameter_tokens[0].strip()
        event_type = func_parameter_tokens[1].strip()
    else:
        class_level = func_parameter_tokens[1].strip()
        event_type = func_parameter_tokens[2].strip()

    if type == "manager_event":
        event_parameters = func_parameter_tokens[2].strip()
    elif type == "ast_manager_event":
        event_parameters = func_parameter_tokens[3].strip()
    else:
        event_parameters = func_parameter_tokens[4].strip()

    parameter_tokens = event_parameters.replace("\"", "").split('\\r\\n')

    # Build the top level XML element information.  Note that we temporarily
    # add the xi namespace in case any includes are used
    node_text = '<managerEvent language=\"%s\" name=\"%s\" xmlns:xi=\"%s\">'
    xml_fragment.insert(0, node_text % ('en_US',
                                        event_type.strip().replace("\"", ""),
                                        'http://www.w3.org/2001/XInclude'))
    xml_fragment[1] = "<managerEventInstance class=\"%s\">" % (class_level)
    xml_fragment.insert(len(xml_fragment), "</managerEvent>")

    # Turn the XML into a DOM to manage the rest of the node manipulations
    dom = xml.dom.minidom.parseString(''.join(xml_fragment))

    # Get the syntax node if we have one; otherwise make one
    instance = dom.getElementsByTagName("managerEventInstance")[0]
    syntax = instance.getElementsByTagName("syntax")
    if not syntax:
        syntax = dom.createElement("syntax")
        instance.appendChild(syntax)
        # Move any existing parameter nodes over
        for node in instance.getElementsByTagName("parameter"):
            syntax.appendChild(node.cloneNode(True))
            instance.removeChild(node)
    else:
        syntax = syntax[0]

    # Add parameters found in the method invocation that were not previously
    # documented
    for parameter in parameter_tokens:
        if not len(parameter):
            continue
        index = parameter.find(':')
        if index < 0:
            index = len(parameter)
        parameter = (parameter[:index].strip().replace("\"", ""))
        if ('%s' not in parameter and
            not __node_contains_parameter(syntax, parameter)):
            e = dom.createElement("parameter")
            e.setAttribute('name', parameter)
            syntax.appendChild(e)

    return dom.toxml().replace("<?xml version=\"1.0\" ?>", "").replace(
               'xmlns:xi="http://www.w3.org/2001/XInclude"', '')


def main(argv=None):

    if argv is None:
        argv = sys.argv

    in_doc = False
    xml_fragment = []
    xml = []
    line_number = 0

    for line in sys.stdin:
        # Note: multiple places may have to read a line, so iterating over
        # readlines isn't possible.  Break when a null line is returned
        line_number += 1
        if not line:
            break

        line = line.strip()
        if ("/*** DOCUMENTATION" in line):
            in_doc = True
        elif ("***/" in line and in_doc):
            # Depending on what we're processing, determine if we need to do
            # any additional work
            in_doc = False
            if not xml_fragment:
                # Nothing read, move along
                continue

            if "<managerEventInstance>" in xml_fragment[0]:
                xml.append(parse_manager_event_instance(xml_fragment))
            else:
                xml.append(''.join(xml_fragment))

            xml_fragment = []
        elif (in_doc):
            xml_fragment.append("%s\n" % line)

    sys.stdout.write(''.join(xml))
    return 0

if __name__ == "__main__":
    sys.exit(main() or 0)
