from trac.core import *
from trac.web.api import IRequestHandler
from trac.web.chrome import ITemplateProvider, add_stylesheet
from tracrpc.api import IXMLRPCHandler, XMLRPCSystem
from trac.wiki.formatter import wiki_to_oneliner
import pkg_resources
from StringIO import StringIO
import traceback

import json
import re
import xmlrpclib


class XMLRPCWeb(Component):
    """ Handle XML-RPC calls from HTTP clients, as well as presenting a list of
        methods available to the currently logged in user. Browsing to
        <trac>/xmlrpc will display this list. """

    implements(IRequestHandler, ITemplateProvider)

    # IRequestHandler methods
    def match_request(self, req):
        if req.path_info in ('/login/xmlrpc', '/xmlrpc', '/login/json', '/json'):
            self.log.debug("%s matches %s" % (req.path_info, True))
            return True
        if re.match(r'/login/json/[\w+\.]$', req.path_info):
            self.log.debug("%s matches %s" % (req.path_info, True))
            return True
        if re.match(r'/json/[\w\.]+$', req.path_info):
            self.log.debug("%s matches %s" % (req.path_info, True))
            return True
        self.log.debug("%s matches %s" % (req.path_info, False))
        return False

    def _send_response(self, req, response, is_json):
        req.send_response(200)
        self.log.debug("(%s ,%s)" % (len(response.encode("utf-8")), response))
        if is_json:
            req.send_header('Content-Type', 'application/json; charset=utf-8')
        else:
            req.send_header('Content-Type', 'text/xml')
        req.send_header('Content-Length', len(response.encode("utf-8")))
        req.end_headers()
        req.write(response)

    def process_request(self, req):
        # Need at least XML_RPC
        req.perm.assert_permission('XML_RPC')

        # Dump RPC functions
        content_type = req.get_header('Content-Type')
        if content_type is None or re.match('(text/html|application/json|text/xml)', content_type) is None:
            namespaces = {}
            for method in XMLRPCSystem(self.env).all_methods(req):
                namespace = method.namespace.replace('.', '_')
                if namespace not in namespaces:
                    namespaces[namespace] = {
                        'description' : wiki_to_oneliner(method.namespace_description, self.env),
                        'methods' : [],
                        'namespace' : method.namespace,
                        }
                try:
                    namespaces[namespace]['methods'].append((method.signature, wiki_to_oneliner(method.description, self.env), method.permission))
                except Exception, e:
                    out = StringIO()
                    traceback.print_exc(file=out)
                    raise Exception('%s: %s\n%s' % (method.name, str(e), out.getvalue()))
            add_stylesheet(req, 'common/css/wiki.css')
            req.hdf['xmlrpc.functions'] = namespaces
            return 'xmlrpclist.cs', None

        args = None
        method = None

        # Handle XML-RPC call
        is_json = content_type.startswith("application/json")
        if is_json:
            method = re.match(".*/json/(.*)$", req.path_info).group(1)
            args = json.read(req.read(int(req.get_header('Content-Length'))))
        else:
            args, method = xmlrpclib.loads(req.read(int(req.get_header('Content-Length'))))
        try:
            result = XMLRPCSystem(self.env).get_method(method)(req, args)
            if is_json:
#                req.send_header('X-JSON', '(' + json.write(result[0]).encode('utf-8') + ')')
#                self._send_response(req, "", True)
                self._send_response(req, json.write(result[0]).encode('utf-8'), True)
            else:
                self._send_response(req, xmlrpclib.dumps(result, methodresponse=True), False)
        except xmlrpclib.Fault, e:
            self.log.error(e)
            self._send_response(req, xmlrpclib.dumps(e), False)
        except Exception, e:
            self.log.error(e)
            out = StringIO()
            traceback.print_exc(file = out)
            self.log.error(out.getvalue())
            self._send_response(req, xmlrpclib.dumps(xmlrpclib.Fault(2, "'%s' while executing '%s()'" % (str(e), method))), False)

    # ITemplateProvider
    def get_htdocs_dirs(self):
        return []

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename(__name__, 'templates')]
