#! /usr/bin/python3
#
# Copyright (c) 2014,2017 - Adjacent Link LLC, Bridgewater, New Jersey
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in
#   the documentation and/or other materials provided with the
#   distribution.
# * Neither the name of Adjacent Link LLC nor the names of its
#   contributors may be used to endorse or promote products derived
#   from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

from __future__ import absolute_import, division, print_function
from lxml import etree
from optparse import OptionParser
import sys
import os.path

try:
    from urlparse import urljoin
except:
    from urllib.parse import urljoin

usage = "%prog [OPTION].. PLATFORMXML..."

optionParser =  OptionParser(usage=usage)

optionParser.add_option("-v",
                        "--verbose",
                        action="store_true",
                        dest="verbose",
                        help="Verbose output mode")

optionParser.add_option("",
                        "--dtd",
                        action="store",
                        type="string",
                        dest="dtd",
                        default='file:///usr/share/emane/dtd/transportdaemon.dtd',
                        help="Transport Daemon XML DTD [default: %default]")

optionParser.add_option("-o",
                        "--outpath",
                        action="store",
                        type="string",
                        dest="outpath",
                        default=".",
                        help="Transport Daemon XML output directory [default: %default]")

(options, args) = optionParser.parse_args()

if options.outpath:
    if not os.path.isdir(options.outpath):
        print("error output directory does not exist:",options.outpath, file=sys.stderr)
        exit(1)


metas = {}
instances = {}
groups = {}

try:
    # process all platform XML files present on the command line
    for platform in args:
        if options.verbose:
            print("processing XML:",platform)

        parser = etree.XMLParser(dtd_validation=True)

        # parse and validate
        try:
            tree = etree.parse(platform, parser)
        except etree.XMLSyntaxError as exp:
            print("error %s:" %platform,exp, file=sys.stderr)
            exit(1)

        root = tree.getroot()

        # verify the XML is platform file
        if root.tag != 'platform':
            print("error %s:" % platform,"wrong document type", file=sys.stderr)

        for nem in root.xpath('nem'):
            nemId = int(nem.get('id'))
            nemDefinition = nem.get('definition')
            nemTransportLocation = nem.get('transport')

            if nemTransportLocation == "internal":
                continue

            if nemId not in instances:
                instances[nemId] = {}

            platformendpoint = nem.find("param/[@name='platformendpoint']")
            transportendpoint = nem.find("param/[@name='transportendpoint']")
            protocol = nem.find("param/[@name='protocol']")

            if platformendpoint is None:
                print("error %s:" % platform," missing 'platformendpoint' for NEM",nemId, file=sys.stderr)
                exit(1)

            if transportendpoint is None:
                print("error %s:" % platform," missing 'transportendpoint' for NEM",nemId, file=sys.stderr)
                exit(1)


            # store platform and transport endpoints
            metas[nemId] = {
                'platformendpoint' : platformendpoint.get('value'),
                'transportendpoint' : transportendpoint.get('value')
            }

            if protocol is not None:
                metas[nemId]['protocol'] = protocol.get('value')

            # load nem XML
            try:
                if options.verbose:
                    print("processing XML:",nemDefinition)

                nemTree = etree.parse(urljoin(platform,nemDefinition),parser)

                for transport in nemTree.xpath('/nem/transport'):
                    transportDefinition = transport.get('definition')

                    if 'definition' not in metas[nemId]:
                        metas[nemId]['definition'] = transportDefinition
                    elif metas[nemId]['definition'] != transportDefinition:
                        print("error %s:" % nemDefinition," inconsistent transport selected for NEM",nemId, file=sys.stderr)
                        exit(1)

                    # load transport XML
                    try:
                        if options.verbose:
                            print("processing XML:",transportDefinition)

                        transportTree = etree.parse(urljoin(platform,transportDefinition),parser)

                        # process transport XML parameters
                        for param in transportTree.xpath('/transport/param'):
                            instances[nemId][param.get('name')] = [param.get('value')]

                        # process transport XML parameter lists
                        for paramlist in transportTree.xpath('/transport/paramlist'):

                            instances[nemId][paramlist.get('name')] = []

                            for item in paramlist.xpath('item'):
                                instances[nemId][paramlist.get('name')].append(item.get('value'))

                    except etree.XMLSyntaxError as exp:
                        print("error %s:" % transportDefinition,exp, file=sys.stderr)
                        exit(1)

                    # process nem XML parameters
                    for param in nemTree.xpath('/nem/transport/param'):
                        instances[nemId][param.get('name')] = [param.get('value')]

                    # process nem XML parameter lists
                    for paramlist in nemTree.xpath('/nem/transport/paramlist'):

                        instances[nemId][paramlist.get('name')] = []

                        for item in paramlist.xpath('item'):
                            instances[nemId][paramlist.get('name')].append(item.get('value'))

            except etree.XMLSyntaxError as exp:
                print("error %s:" % nemDefinition,exp, file=sys.stderr)
                exit(1)

            transport = nem.find('transport')

            # unless specified use the nemId as the group tag
            group = str(nemId)

            if transport is not None:
                transportDefinition = transport.get('definition')

                group = transport.get("group")

                if group is None:
                    group = str(nemId)

                if 'definition' not in metas[nemId]:
                    metas[nemId]['definition'] = transportDefinition
                elif metas[nemId]['definition'] != transportDefinition:
                    print("error %s:" % platform," inconsistent transport selected for NEM",nemId, file=sys.stderr)
                    exit(1)

                # process platform XML nem element transport parameters
                for param in transport.xpath('param'):
                    instances[nemId][param.get('name')] = [param.get('value')]

                # process nem XML parameter lists
                for paramlist in transport.xpath('paramlist'):

                    instances[nemId][paramlist.get('name')] = []

                    for item in paramlist.xpath('item'):
                        instances[nemId][paramlist.get('name')].append(item.get('value'))

            if group not in groups:
                groups[group] = []

            # add the nem to a group
            groups[group].append(nemId)


except IOError as exp:
    print("error:",exp, file=sys.stderr)
    exit(1)

# create the transport daemon files
for members in sorted(groups.values()):
    output="transportdaemon%d.xml" % min(members)

    root = etree.Element('transportdaemon')

    for nemId in members:
        params = instances[nemId]

        instanceElement = etree.SubElement(root,'instance', nemid=str(nemId))

        etree.SubElement(instanceElement,
                         'param',
                         name='platformendpoint',
                         value=metas[nemId]['platformendpoint'])

        etree.SubElement(instanceElement,
                         'param',
                         name='transportendpoint',
                         value=metas[nemId]['transportendpoint'])

        if 'protocol' in metas[nemId]:
            etree.SubElement(instanceElement,
                             'param',
                             name='protocol',
                             value=metas[nemId]['protocol'])

        transportElement = etree.SubElement(instanceElement,
                                            'transport',
                                            definition=metas[nemId]['definition'])


        for name,value in list(params.items()):
            if len(value) == 1:
                etree.SubElement(transportElement,'param', name=name,value=value[0])
            else:
                paramlist = etree.SubElement(transportElement,'paramlist', name=name)

                for item in value:
                    etree.SubElement(paramlist,'item', value=item)


    target = "transportdaemon%d.xml" % min(members)

    try:
        xml = etree.tostring(root,
                             pretty_print=True,
                            encoding="UTF-8",
                             xml_declaration=True,
                             doctype="<!DOCTYPE transportdaemon SYSTEM \"%s\">" % options.dtd)

        # kludge DTD validation with external entity loading
        etree.XML(xml,parser)

        if options.verbose:
            print("generating XML:",target)

        output=open(os.path.join(options.outpath,target),'w')

        print(xml.decode('UTF-8'), file=output)

        output.close()

    except etree.XMLSyntaxError as exp:
        print("error %s:" % target, "unable to validate target xml", file=sys.stderr)
        print(exp, file=sys.stderr)
        exit(1)
