# Standard Library
import logging
import os

# Pyramid
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPInternalServerError
from pyramid.util import DottedNameResolver

# Pyramid Notebook
from pyramid_notebook.notebookmanager import NotebookManager
from pyramid_notebook.proxy import WSGIProxyApplication
from pyramid_notebook.utils import make_dict_hash
from pyramid_notebook.utils import route_to_alt_domain


logger = logging.getLogger(__name__)


def get_notebook_manager(request):
    settings = request.registry.settings
    notebook_folder = settings["pyramid_notebook.notebook_folder"]
    manager = NotebookManager(notebook_folder)
    return manager


def proxy_it(request, port):
    """Proxy HTTP request to upstream IPython Notebook Tornado server."""

    # Check if we have websocket proxy configured
    websocket_proxy = request.registry.settings.get("pyramid_notebook.websocket_proxy", "")
    if websocket_proxy.strip():
        r = DottedNameResolver()
        websocket_proxy = r.maybe_resolve(websocket_proxy)

    if "upgrade" in request.headers.get("connection", "").lower():
        if websocket_proxy:
            return websocket_proxy(request, port)
        else:
            # If we run on localhost on pserve, we should never hit here as requests go directly to IPython Notebook kernel, not us
            raise RuntimeError("Websocket proxy support is not configured.")

    proxy_app = WSGIProxyApplication(port)

    return request.get_response(proxy_app)


def security_check(request, username):
    """Check for basic misconfiguration errors."""
    assert username, "You must give an username whose IPython Notebook session we open"


def prepare_notebook_context(request, notebook_context):
    """Fill in notebook context with default values."""

    if not notebook_context:
        notebook_context = {}

    # Override notebook Jinja templates
    if "extra_template_paths" not in notebook_context:
        notebook_context["extra_template_paths"] = [os.path.join(os.path.dirname(__file__), "server", "templates")]

    # Furious invalid state follows if we let this slip through
    assert type(notebook_context["extra_template_paths"]) == list, "Got bad extra_template_paths {}".format(notebook_context["extra_template_paths"])

    # Jinja variables
    notebook_context["jinja_environment_options"] = notebook_context.get("jinja_environment_options", {})

    assert type(notebook_context["jinja_environment_options"]) == dict

    # XXX: Following passing of global variables to Jinja templates requires Jinja 2.8.0dev+ version and is not yet supported
    # http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment.globals

    # notebook_context["jinja_environment_options"]["globals"] = notebook_context["jinja_environment_options"].get("globals", {})
    # globals_ = notebook_context["jinja_environment_options"]["globals"]
    #
    # assert type(globals_) == dict
    #
    # if not "home_url" in globals_:
    #     globals_["home_url"] = request.host_url
    #
    # if not "home_title" in globals_:
    #     globals_["home_title"] = "Back to site"

    # Tell notebook to correctly address WebSockets allow origin policy
    notebook_context["allow_origin"] = route_to_alt_domain(request, request.host_url)
    notebook_context["notebook_path"] = request.route_path("notebook_proxy", remainder="")

    # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context
    if "context_hash" not in notebook_context:
        notebook_context["context_hash"] = make_dict_hash(notebook_context)

    print(notebook_context)


def launch_on_demand(request, username, notebook_context):
    """See if we have notebook already running this context and if not then launch new one."""
    security_check(request, username)

    settings = request.registry.settings

    notebook_folder = settings.get("pyramid_notebook.notebook_folder", None)
    if not notebook_folder:
        raise RuntimeError("Setting missing: pyramid_notebook.notebook_folder")

    kill_timeout = settings.get("pyramid_notebook.kill_timeout", None)
    if not kill_timeout:
        raise RuntimeError("Setting missing: pyramid_notebook.kill_timeout")

    kill_timeout = int(kill_timeout)

    if not notebook_context:
        notebook_context = {}

    # Override notebook Jinja templates
    if "extra_template_paths" not in notebook_context:
        notebook_context["extra_template_paths"] = [os.path.join(os.path.dirname(__file__), "server", "templates")]

    # Furious invalid state follows if we let this slip through
    assert type(notebook_context["extra_template_paths"]) == list, "Got bad extra_template_paths {}".format(notebook_context["extra_template_paths"])
    notebook_folder = settings.get("pyramid_notebook.notebook_folder", None)
    if not notebook_folder:
        raise RuntimeError("Setting missing: pyramid_notebook.notebook_folder")

    kill_timeout = settings.get("pyramid_notebook.kill_timeout", None)
    if not kill_timeout:
        raise RuntimeError("Setting missing: pyramid_notebook.kill_timeout")

    kill_timeout = int(kill_timeout)

    prepare_notebook_context(request, notebook_context)

    # Configure websockets
    # websocket_url = settings.get("pyramid_notebook.websocket_url")
    # assert websocket_url, "pyramid_notebook.websocket_url setting missing"
    # assert websocket_url.startswith("ws:/") or websocket_url.startswith("wss:/")

    if request.registry.settings.get("pyramid_notebook.websocket_proxy", ""):
        websocket_url = route_to_alt_domain(request, request.host_url)
        websocket_url = websocket_url.replace("http://", "ws://").replace("https://", "wss://")
        notebook_context["websocket_url"] = websocket_url
    else:
        # Connect websockets directly to localhost notebook server, do not try to proxy them
        websocket_url = "ws://localhost:{port}/notebook/"

    # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context
    if "context_hash" not in notebook_context:
        notebook_context["context_hash"] = make_dict_hash(notebook_context)

    manager = NotebookManager(notebook_folder, kill_timeout=kill_timeout)
    notebook_info, creates = manager.start_notebook_on_demand(username, notebook_context)
    return notebook_info


def notebook_proxy(request, username):
    """Renders a IPython Notebook frame wrapper.

    Starts or reattachs ot an existing Notebook session.
    """
    security_check(request, username)

    manager = get_notebook_manager(request)
    notebook_info = manager.get_context(username)

    if not notebook_info:
        raise HTTPInternalServerError("Apparently IPython Notebook daemon process is not running for {}".format(username))

    if 'http_port' not in notebook_info:
        raise RuntimeError("Notebook terminated prematurely before managed to tell us its HTTP port")

    return proxy_it(request, notebook_info["http_port"])


def launch_notebook(request, username, notebook_context):
    """Renders a IPython Notebook frame wrapper.

    Starts or reattachs ot an existing Notebook session.
    """
    # The notebook manage now tries too hard to get the port allocated for the notebook user, making it slow
    # TODO: Manage a proper state e.g. using Redis
    notebook_info = launch_on_demand(request, username, notebook_context)

    # Jump to the detault notebook
    proxy_route = request.route_url("notebook_proxy", remainder="notebooks/{}".format(notebook_info["notebook_name"]))
    proxy_route = route_to_alt_domain(request, proxy_route)

    return HTTPFound(proxy_route)


def shutdown_notebook(request, username):
    """Stop any running notebook for a user."""

    manager = get_notebook_manager(request)
    if manager.is_running(username):
        manager.stop_notebook(username)

# Standard Library
import logging
import os

# Pyramid
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPInternalServerError
from pyramid.util import DottedNameResolver

# Pyramid Notebook
from pyramid_notebook.notebookmanager import NotebookManager
from pyramid_notebook.proxy import WSGIProxyApplication
from pyramid_notebook.utils import make_dict_hash
from pyramid_notebook.utils import route_to_alt_domain


logger = logging.getLogger(__name__)


def get_notebook_manager(request):
    settings = request.registry.settings
    notebook_folder = settings["pyramid_notebook.notebook_folder"]
    manager = NotebookManager(notebook_folder)
    return manager


def proxy_it(request, port):
    """Proxy HTTP request to upstream IPython Notebook Tornado server."""

    # Check if we have websocket proxy configured
    websocket_proxy = request.registry.settings.get("pyramid_notebook.websocket_proxy", "")
    if websocket_proxy.strip():
        r = DottedNameResolver()
        websocket_proxy = r.maybe_resolve(websocket_proxy)

    if "upgrade" in request.headers.get("connection", "").lower():
        if websocket_proxy:
            return websocket_proxy(request, port)
        else:
            # If we run on localhost on pserve, we should never hit here as requests go directly to IPython Notebook kernel, not us
            raise RuntimeError("Websocket proxy support is not configured.")

    proxy_app = WSGIProxyApplication(port)

    return request.get_response(proxy_app)


def security_check(request, username):
    """Check for basic misconfiguration errors."""
    assert username, "You must give an username whose IPython Notebook session we open"


def prepare_notebook_context(request, notebook_context):
    """Fill in notebook context with default values."""

    if not notebook_context:
        notebook_context = {}

    # Override notebook Jinja templates
    if "extra_template_paths" not in notebook_context:
        notebook_context["extra_template_paths"] = [os.path.join(os.path.dirname(__file__), "server", "templates")]

    # Furious invalid state follows if we let this slip through
    assert type(notebook_context["extra_template_paths"]) == list, "Got bad extra_template_paths {}".format(notebook_context["extra_template_paths"])

    # Jinja variables
    notebook_context["jinja_environment_options"] = notebook_context.get("jinja_environment_options", {})

    assert type(notebook_context["jinja_environment_options"]) == dict

    # XXX: Following passing of global variables to Jinja templates requires Jinja 2.8.0dev+ version and is not yet supported
    # http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment.globals

    # notebook_context["jinja_environment_options"]["globals"] = notebook_context["jinja_environment_options"].get("globals", {})
    # globals_ = notebook_context["jinja_environment_options"]["globals"]
    #
    # assert type(globals_) == dict
    #
    # if not "home_url" in globals_:
    #     globals_["home_url"] = request.host_url
    #
    # if not "home_title" in globals_:
    #     globals_["home_title"] = "Back to site"

    # Tell notebook to correctly address WebSockets allow origin policy
    notebook_context["allow_origin"] = route_to_alt_domain(request, request.host_url)
    notebook_context["notebook_path"] = request.route_path("notebook_proxy", remainder="")

    # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context
    if "context_hash" not in notebook_context:
        notebook_context["context_hash"] = make_dict_hash(notebook_context)

    print(notebook_context)


def launch_on_demand(request, username, notebook_context):
    """See if we have notebook already running this context and if not then launch new one."""
    security_check(request, username)

    settings = request.registry.settings

    notebook_folder = settings.get("pyramid_notebook.notebook_folder", None)
    if not notebook_folder:
        raise RuntimeError("Setting missing: pyramid_notebook.notebook_folder")

    kill_timeout = settings.get("pyramid_notebook.kill_timeout", None)
    if not kill_timeout:
        raise RuntimeError("Setting missing: pyramid_notebook.kill_timeout")

    kill_timeout = int(kill_timeout)

    if not notebook_context:
        notebook_context = {}

    # Override notebook Jinja templates
    if "extra_template_paths" not in notebook_context:
        notebook_context["extra_template_paths"] = [os.path.join(os.path.dirname(__file__), "server", "templates")]

    # Furious invalid state follows if we let this slip through
    assert type(notebook_context["extra_template_paths"]) == list, "Got bad extra_template_paths {}".format(notebook_context["extra_template_paths"])
    notebook_folder = settings.get("pyramid_notebook.notebook_folder", None)
    if not notebook_folder:
        raise RuntimeError("Setting missing: pyramid_notebook.notebook_folder")

    kill_timeout = settings.get("pyramid_notebook.kill_timeout", None)
    if not kill_timeout:
        raise RuntimeError("Setting missing: pyramid_notebook.kill_timeout")

    kill_timeout = int(kill_timeout)

    prepare_notebook_context(request, notebook_context)

    # Configure websockets
    # websocket_url = settings.get("pyramid_notebook.websocket_url")
    # assert websocket_url, "pyramid_notebook.websocket_url setting missing"
    # assert websocket_url.startswith("ws:/") or websocket_url.startswith("wss:/")

    if request.registry.settings.get("pyramid_notebook.websocket_proxy", ""):
        websocket_url = route_to_alt_domain(request, request.host_url)
        websocket_url = websocket_url.replace("http://", "ws://").replace("https://", "wss://")
        notebook_context["websocket_url"] = websocket_url
    else:
        # Connect websockets directly to localhost notebook server, do not try to proxy them
        websocket_url = "ws://localhost:{port}/notebook/"

    # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context
    if "context_hash" not in notebook_context:
        notebook_context["context_hash"] = make_dict_hash(notebook_context)

    manager = NotebookManager(notebook_folder, kill_timeout=kill_timeout)
    notebook_info, creates = manager.start_notebook_on_demand(username, notebook_context)
    return notebook_info


def notebook_proxy(request, username):
    """Renders a IPython Notebook frame wrapper.

    Starts or reattachs ot an existing Notebook session.
    """
    security_check(request, username)

    manager = get_notebook_manager(request)
    notebook_info = manager.get_context(username)

    if not notebook_info:
        raise HTTPInternalServerError("Apparently IPython Notebook daemon process is not running for {}".format(username))

    if 'http_port' not in notebook_info:
        raise RuntimeError("Notebook terminated prematurely before managed to tell us its HTTP port")

    return proxy_it(request, notebook_info["http_port"])


def launch_notebook(request, username, notebook_context):
    """Renders a IPython Notebook frame wrapper.

    Starts or reattachs ot an existing Notebook session.
    """
    # The notebook manage now tries too hard to get the port allocated for the notebook user, making it slow
    # TODO: Manage a proper state e.g. using Redis
    notebook_info = launch_on_demand(request, username, notebook_context)

    # Jump to the detault notebook
    proxy_route = request.route_url("notebook_proxy", remainder="notebooks/{}".format(notebook_info["notebook_name"]))
    proxy_route = route_to_alt_domain(request, proxy_route)

    return HTTPFound(proxy_route)


def shutdown_notebook(request, username):
    """Stop any running notebook for a user."""

    manager = get_notebook_manager(request)
    if manager.is_running(username):
        manager.stop_notebook(username)

# Standard Library # Pyramid # Pyramid Notebook Proxy HTTP request to upstream IPython Notebook Tornado server. # Check if we have websocket proxy configured # If we run on localhost on pserve, we should never hit here as requests go directly to IPython Notebook kernel, not us Check for basic misconfiguration errors. Fill in notebook context with default values. # Override notebook Jinja templates # Furious invalid state follows if we let this slip through # Jinja variables # XXX: Following passing of global variables to Jinja templates requires Jinja 2.8.0dev+ version and is not yet supported # http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment.globals # notebook_context["jinja_environment_options"]["globals"] = notebook_context["jinja_environment_options"].get("globals", {}) # globals_ = notebook_context["jinja_environment_options"]["globals"] # # assert type(globals_) == dict # # if not "home_url" in globals_: #     globals_["home_url"] = request.host_url # # if not "home_title" in globals_: #     globals_["home_title"] = "Back to site" # Tell notebook to correctly address WebSockets allow origin policy # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context See if we have notebook already running this context and if not then launch new one. # Override notebook Jinja templates # Furious invalid state follows if we let this slip through # Configure websockets # websocket_url = settings.get("pyramid_notebook.websocket_url") # assert websocket_url, "pyramid_notebook.websocket_url setting missing" # assert websocket_url.startswith("ws:/") or websocket_url.startswith("wss:/") # Connect websockets directly to localhost notebook server, do not try to proxy them # Record the hash of the current parameters, so we know if this user accesses the notebook in this or different context Renders a IPython Notebook frame wrapper.      Starts or reattachs ot an existing Notebook session. Renders a IPython Notebook frame wrapper.      Starts or reattachs ot an existing Notebook session. # The notebook manage now tries too hard to get the port allocated for the notebook user, making it slow # TODO: Manage a proper state e.g. using Redis # Jump to the detault notebook Stop any running notebook for a user.