#!/usr/bin/env python
from __future__ import print_function

'''Turn a UPnP service definition XML file into a C++ device-side
service skeleton based on libupnpp'''

import sys
import os
import xml.sax.handler

cprolog_text = \
'''/* Copyright (C) 2016 J.F.Dockes
 *	 This program is free software; you can redistribute it and/or modify
 *	 it under the terms of the GNU General Public License as published by
 *	 the Free Software Foundation; either version 2 of the License, or
 *	 (at your option) any later version.
 *
 *	 This program is distributed in the hope that it will be useful,
 *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	 GNU General Public License for more details.
 *
 *	 You should have received a copy of the GNU General Public License
 *	 along with this program; if not, write to the
 *	 Free Software Foundation, Inc.,
 *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "%s"

#include <upnp/upnp.h>

#include <functional>
#include <iostream>
#include <map>
#include <utility>

#include "libupnpp/log.hxx"
#include "libupnpp/soaphelp.hxx"
#include "libupnpp/device/device.hxx"

using namespace std;
using namespace std::placeholders;

'''

hprolog_text = \
'''/* Copyright (C) 2016 J.F.Dockes
 *	 This program is free software; you can redistribute it and/or modify
 *	 it under the terms of the GNU General Public License as published by
 *	 the Free Software Foundation; either version 2 of the License, or
 *	 (at your option) any later version.
 *
 *	 This program is distributed in the hope that it will be useful,
 *	 but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	 GNU General Public License for more details.
 *
 *	 You should have received a copy of the GNU General Public License
 *	 along with this program; if not, write to the
 *	 Free Software Foundation, Inc.,
 *	 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#ifndef _%s_H_INCLUDED_
#define _%s_H_INCLUDED_

#include <string>
#include <vector>

#include "libupnpp/device/device.hxx"
#include "libupnpp/soaphelp.hxx"

using namespace UPnPP;

'''

def back(l):
    return l[len(l)-1]

def stripsuff(s):
    dotpos = s.rfind('.')
    if dotpos != -1:
        s = s[:dotpos]
    return s
    


class MyHandler(xml.sax.handler.ContentHandler):
    def __init__(self, classname, cfn, hfn):
        self.classname = classname
        self.cfn = cfn
        self.hfn = hfn
        self.elstack = []
        self.chardata = u""
        self.attrs = {}
        self.inargs = []
        self.outargs = []
        self.actions = []
        self.statevars = {}
        self.cprolog = cprolog_text % os.path.basename(hfn)
        self.cprolog += 'static const string sTp%s("urn:XXX:1");\n' % classname
        self.cprolog += 'static const string sId%s("urn:XXX");\n\n' % classname
        self.cprolog += '%s::%s(UPnPProvider::UpnpDevice *dev)\n' % \
                        (classname, classname)
        self.cprolog += '    : UpnpService(sTp%s, sId%s, dev)\n'  % \
                        (classname, classname)
        self.cprolog += '{\n'
        uh = stripsuff(os.path.basename(hfn)).upper()
        self.hprolog = hprolog_text % (uh, uh)
        self.hprolog += 'class %s : public UPnPProvider::UpnpService {\n' % classname
        self.hprolog += 'public:\n'
        self.hprolog += '    %s(UPnPProvider::UpnpDevice *dev);\n\n' % classname
        self.hprolog += 'private:\n'

    def setDocumentLocator(self, locator):
        self.locator = locator
        
    def startElement(self, name, attrs):
        self.attrs = attrs
        if name == 'stateVariable':
            self.stvarev = False
            self.stvarallow = []
            if 'sendEvents' in attrs and attrs['sendEvents'] == 'yes':
                self.stvarev = True
        self.elstack.append(name)

    def endElement(self, name):
        self.elstack.pop()
        chardata = self.chardata.strip()
        self.chardata = u""

        if name == 'action':
            self.actions.append([self.actionname, self.inargs, self.outargs])
            self.inargs = []
            self.outargs = []
        elif name == 'stateVariable':
            self.statevars[self.stvarname] = [self.stvartype, self.stvarev, \
                                              self.stvarallow]
            self.stvartype = u''
            self.stvarev = False
            self.stvarallow = []
        elif name == 'name':
            if back(self.elstack) == 'action':
                self.actionname = chardata
            elif back(self.elstack) == 'argument':
                self.argname = chardata
            elif back(self.elstack) == 'stateVariable':
                self.stvarname = chardata
        elif name == 'dataType':
            self.stvartype = chardata
        elif name == 'allowedValue':
            self.stvarallow.append(chardata)
        elif name == 'sendEventsAttribute' and chardata == 'yes':
            self.stvarev = True
        elif name == 'direction':
            self.argdir = chardata
        elif name == 'relatedStateVariable':
            self.relvar = chardata
        elif name == 'argument':
            if self.argdir == 'in':
                self.inargs.append([self.argname, self.relvar])
            else:
                self.outargs.append([self.argname, self.relvar])
            self.argname = u''
            self.argdir = u''
            self.relvar = u''
            
    def characters(self, chars):
        self.chardata += chars

    def actionMethodText(self, actname, inargs, outargs):
        methname = "%s::act%s" % (self.classname, actname)
        txt = u''
        txt += "int %s(const SoapIncoming& sc, SoapOutgoing& data)\n"\
               % methname
        txt += "{\n"
        if len(inargs) != 0:
            txt += "    bool ok = false;\n"
        for arg in inargs:
            relvar = arg[1]
            if relvar in self.statevars:
                atype = self.statevars[relvar][0]
            else:
                atype = 'string'
            if atype == 'string':
                txt += '    std::string in_%s;\n' % arg[0]
            elif atype == 'boolean':
                txt += '    bool in_%s;\n' % arg[0]
            else:
                txt += '    int in_%s;\n' % arg[0]
            txt += '    ok = sc.get("%s", &in_%s);\n' % (arg[0], arg[0])
            txt += '    if (!ok) {\n'
            txt += '        LOGERR("%s: no %s in params\\n");\n' %\
                   (methname, arg[0])
            txt += '        return UPNP_E_INVALID_PARAM;\n'
            txt += '    }\n'
                
        txt += "\n"

        txt += '    LOGDEB("%s: "' % methname
        for arg in inargs:
            txt += ' << " %s " << in_%s' % (arg[0], arg[0])
        txt += ' << endl);\n\n'
        
        for arg in outargs:
            txt += '    std::string out_%s;\n' % arg[0]
        for arg in outargs:
            txt += '    data.addarg("%s", out_%s);\n' % (arg[0], arg[0])
                    
        txt += "    return UPNP_E_SUCCESS;\n"
        txt += "}\n\n"
        return txt

    def endDocument(self):
        act_text = u''
        for act in self.actions:
            self.cprolog += '    dev->addActionMapping(\n'
            self.cprolog += '        this, "%s",\n'% act[0]
            self.cprolog += '        bind(&%s::act%s, this, _1, _2));\n' % \
                          (self.classname, act[0])
            act_text += self.actionMethodText(act[0], act[1], act[2])

        self.cprolog += '}\n\n'

        f = open(self.cfn, "w")
        print(self.cprolog, file = f)
        print(act_text, file = f)
        f.close()
        
        f = open(self.hfn, "w")
        for nm in self.actions:
            self.hprolog += '    int act%s(const SoapIncoming& sc, SoapOutgoing& data);\n' % nm[0]
        self.hprolog += '};\n'
        self.hprolog += '#endif\n'
        print(self.hprolog, file = f)
        f.close
        
def Usage():
    print("Usage: %s xmlfilename cfn hfn" % os.path.basename(sys.argv[0]), file=sys.stderr)
    sys.exit(1)
    
if len(sys.argv) != 4:
    Usage()
    
inputname = sys.argv[1]
cfn = sys.argv[2]
hfn = sys.argv[3]

classname = stripsuff(os.path.basename(inputname))

handler = MyHandler(classname, cfn, hfn)
parser = xml.sax.make_parser()

xml.sax.parse(inputname, handler)

