#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

import os
import sys

try:
    import tornado
except ImportError:
    raise RuntimeError("You need tornado installed to use this worker.")
import tornado.web
import tornado.httpserver
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.wsgi import WSGIContainer
from gunicorn.workers.base import Worker
from gunicorn import __version__ as gversion
from gunicorn.sock import ssl_context


class TornadoWorker(Worker):

    @classmethod
    def setup(cls):
        web = sys.modules.pop("tornado.web")
        old_clear = web.RequestHandler.clear

        def clear(self):
            old_clear(self)
            if "Gunicorn" not in self._headers["Server"]:
                self._headers["Server"] += " (Gunicorn/%s)" % gversion
        web.RequestHandler.clear = clear
        sys.modules["tornado.web"] = web

    def handle_exit(self, sig, frame):
        if self.alive:
            super().handle_exit(sig, frame)

    def handle_request(self):
        self.nr += 1
        if self.alive and self.nr >= self.max_requests:
            self.log.info("Autorestarting worker after current request.")
            self.alive = False

    def watchdog(self):
        if self.alive:
            self.notify()

        if self.ppid != os.getppid():
            self.log.info("Parent changed, shutting down: %s", self)
            self.alive = False

    def heartbeat(self):
        if not self.alive:
            if self.server_alive:
                if hasattr(self, 'server'):
                    try:
                        self.server.stop()
                    except Exception:
                        pass
                self.server_alive = False
            else:
                for callback in self.callbacks:
                    callback.stop()
                self.ioloop.stop()

    def init_process(self):
        # IOLoop cannot survive a fork or be shared across processes
        # in any way. When multiple processes are being used, each process
        # should create its own IOLoop. We should clear current IOLoop
        # if exists before os.fork.
        IOLoop.clear_current()
        super().init_process()

    def run(self):
        self.ioloop = IOLoop.instance()
        self.alive = True
        self.server_alive = False

        # Warn if HTTP/2 is requested - tornado worker doesn't support it
        if 'h2' in self.cfg.http_protocols:
            self.log.warning(
                "HTTP/2 is not supported by the tornado worker. "
                "Use gthread, gevent, or asgi workers for HTTP/2 support. "
                "Falling back to HTTP/1.1 only."
            )

        self.callbacks = []
        self.callbacks.append(PeriodicCallback(self.watchdog, 1000))
        self.callbacks.append(PeriodicCallback(self.heartbeat, 1000))
        for callback in self.callbacks:
            callback.start()

        # Assume the app is a WSGI callable if its not an
        # instance of tornado.web.Application or WSGIContainer
        app = self.wsgi
        if not isinstance(app, WSGIContainer) and \
                not isinstance(app, tornado.web.Application):
            app = WSGIContainer(app)

        worker = self

        class _HTTPServer(tornado.httpserver.HTTPServer):

            def on_close(self, server_conn):
                worker.handle_request()
                super().on_close(server_conn)

        if self.cfg.is_ssl:
            server = _HTTPServer(app, ssl_options=ssl_context(self.cfg))
        else:
            server = _HTTPServer(app)

        self.server = server
        self.server_alive = True

        for s in self.sockets:
            s.setblocking(0)
            server.add_socket(s)

        server.no_keep_alive = self.cfg.keepalive <= 0
        server.start(num_processes=1)

        self.ioloop.start()
