##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Code to initialize the application server

"""
from __future__ import print_function
__docformat__ = 'restructuredtext'

import base64
import time
import sys

from pdb import Pdb
from io import BytesIO

from zope.publisher.publish import publish as _publish, debug_call
from zope.publisher.browser import TestRequest, setDefaultSkin
from zope.app.publication.browser import BrowserPublication
from zope.app.appsetup import config, database

try:
    from time import process_time as time_process_time  # pragma: PY3
except ImportError:
    from time import clock as time_process_time  # pragma: PY2

try:
    import urllib.parse as urllib  # pragma: PY3
except ImportError:
    import urllib  # pragma: PY2

try:
    text_type = unicode  # pragma: PY2
except NameError:
    text_type = str  # pragma: PY3


class Debugger(object):

    pdb = Pdb

    def __init__(self, db=None, config_file=None, stdout=None):
        if db is None and config_file is None:
            db = 'Data.fs'
            config_file = 'site.zcml'

        if config_file is not None:
            config(config_file)
        self.db = database(db)
        self.stdout = stdout

    @classmethod
    def fromDatabase(cls, db):
        inst = cls.__new__(cls)
        inst.db = db
        return inst

    def root(self):
        """Get the top-level application object

        The object returned is connected to an open database connection.
        """

        from zope.app.publication.zopepublication import ZopePublication
        return self.db.open().root()[ZopePublication.root_name]

    def _request(self,
                 path='/', stdin='', basic=None,
                 environment=None, form=None,
                 request=None, publication=BrowserPublication):
        """Create a request
        """

        env = {}

        if isinstance(stdin, text_type):
            stdin = stdin.encode("utf-8")

        if isinstance(stdin, bytes):
            stdin = BytesIO(stdin)

        p = path.split('?')
        if len(p) == 1:
            env['PATH_INFO'] = p[0]
        elif len(p) == 2:
            env['PATH_INFO'], env['QUERY_STRING'] = p
        else:
            raise ValueError("Too many ?s in path", path)

        env['PATH_INFO'] = urllib.unquote(env['PATH_INFO'])

        if environment is not None:
            env.update(environment)

        if basic:
            basic_bytes = basic.encode('ascii') if not isinstance(
                basic, bytes) else basic
            basic64_bytes = base64.b64encode(basic_bytes)
            basic64 = basic64_bytes.decode('ascii').strip()
            env['HTTP_AUTHORIZATION'] = "Basic %s" % basic64

        pub = publication(self.db)

        if request is not None:
            request = request(stdin, env)
        else:
            request = TestRequest(stdin, env)
            setDefaultSkin(request)
        request.setPublication(pub)
        if form:
            request.form.update(form)

        return request

    def publish(self, path='/', stdin='', *args, **kw):
        t, pt = time.time(), time_process_time()

        request = self._request(path, stdin, *args, **kw)

        # agroszer: 2008.feb.1.: if a retry occurs in the publisher,
        # the response will be LOST, so we must accept the returned request
        request = _publish(request)
        getStatus = getattr(request.response, 'getStatus', lambda: None)

        headers = sorted(request.response.getHeaders())

        print(
            'Status %s\r\n%s\r\n\r\n%s' % (
                request.response.getStatusString(),
                '\r\n'.join([("%s: %s" % h) for h in headers]),
                request.response.consumeBody(),
            ), file=self.stdout or sys.stdout)
        return time.time() - t, time_process_time() - pt, getStatus()

    def run(self, *args, **kw):
        t, pt = time.time(), time_process_time()
        request = self._request(*args, **kw)
        # agroszer: 2008.feb.1.: if a retry occurs in the publisher,
        # the response will be LOST, so we must accept the returned request
        request = _publish(request, handle_errors=False)
        getStatus = getattr(request.response, 'getStatus', lambda: None)

        return time.time() - t, time_process_time() - pt, getStatus()

    def debug(self, *args, **kw):
        out = self.stdout or sys.stdout

        class ZopePdb(self.Pdb):
            done_pub = False
            done_ob = False

            def do_pub(self, arg):
                if self.done_pub:
                    print('pub already done.', file=out)
                    return

                self.do_s('')
                self.do_s('')
                self.do_c('')
                self.done_pub = True

            def do_ob(self, arg):
                if self.done_ob:
                    print('ob already done.', file=out)
                    return

                self.do_pub('')
                self.do_c('')
                self.done_ob = True

        dbg = ZopePdb()

        request = self._request(*args, **kw)
        fbreak(dbg, _publish)
        fbreak(dbg, debug_call)

        print('* Type c<cr> to jump to published object call.',
              file=out)
        dbg.runcall(_publish, request)
        return dbg


def getlineno(code):
    return code.co_firstlineno


def fbreak(db, meth):
    try:
        meth = meth.__func__
    except AttributeError:
        pass
    code = meth.__code__
    lineno = getlineno(code)
    filename = code.co_filename
    db.set_break(filename, lineno)

[((59, 18, 59, 30), 'zope.app.appsetup.database', 'database', ({(59, 27, 59, 29): 'db'}, {}), '(db)', False, 'from zope.app.appsetup import config, database\n'), ((100, 27, 100, 59), 'urllib.unquote', 'urllib.unquote', ({(100, 42, 100, 58): "env['PATH_INFO']"}, {}), "(env['PATH_INFO'])", False, 'import urllib\n'), ((132, 18, 132, 35), 'zope.publisher.publish.publish', '_publish', ({(132, 27, 132, 34): 'request'}, {}), '(request)', True, 'from zope.publisher.publish import publish as _publish, debug_call\n'), ((150, 18, 150, 56), 'zope.publisher.publish.publish', '_publish', (), '', True, 'from zope.publisher.publish import publish as _publish, debug_call\n'), ((58, 12, 58, 31), 'zope.app.appsetup.config', 'config', ({(58, 19, 58, 30): 'config_file'}, {}), '(config_file)', False, 'from zope.app.appsetup import config, database\n'), ((90, 20, 90, 34), 'io.BytesIO', 'BytesIO', ({(90, 28, 90, 33): 'stdin'}, {}), '(stdin)', False, 'from io import BytesIO\n'), ((108, 28, 108, 57), 'base64.b64encode', 'base64.b64encode', ({(108, 45, 108, 56): 'basic_bytes'}, {}), '(basic_bytes)', False, 'import base64\n'), ((117, 22, 117, 45), 'zope.publisher.browser.TestRequest', 'TestRequest', ({(117, 34, 117, 39): 'stdin', (117, 41, 117, 44): 'env'}, {}), '(stdin, env)', False, 'from zope.publisher.browser import TestRequest, setDefaultSkin\n'), ((118, 12, 118, 35), 'zope.publisher.browser.setDefaultSkin', 'setDefaultSkin', ({(118, 27, 118, 34): 'request'}, {}), '(request)', False, 'from zope.publisher.browser import TestRequest, setDefaultSkin\n'), ((126, 16, 126, 27), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((126, 29, 126, 48), 'time.clock', 'time_process_time', ({}, {}), '()', True, 'from time import clock as time_process_time\n'), ((146, 16, 146, 27), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((146, 29, 146, 48), 'time.clock', 'time_process_time', ({}, {}), '()', True, 'from time import clock as time_process_time\n'), ((143, 15, 143, 26), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((143, 32, 143, 51), 'time.clock', 'time_process_time', ({}, {}), '()', True, 'from time import clock as time_process_time\n'), ((153, 15, 153, 26), 'time.time', 'time.time', ({}, {}), '()', False, 'import time\n'), ((153, 32, 153, 51), 'time.clock', 'time_process_time', ({}, {}), '()', True, 'from time import clock as time_process_time\n')]