from __future__ import absolute_import, print_function

import unittest

import tempfile
import shutil
import os
import os.path

import jinja2

from bokeh.application.handlers import DirectoryHandler
from bokeh.document import Document
from bokeh.core.templates import FILE

class TmpDir(object):
    def __init__(self, prefix):
        self._dir = tempfile.mkdtemp(prefix=prefix)

    def __exit__(self, type, value, traceback):
        shutil.rmtree(path=self._dir)

    def __enter__(self):
        return self._dir

def _with_directory_contents(contents, func):
    with (TmpDir(prefix="bokeh-directory-handler-test")) as dirname:
        for filename, file_content in contents.items():
            filepath = os.path.join(dirname, filename)
            filedir = os.path.dirname(filepath)
            if not os.path.exists(filedir):
                os.makedirs(filedir)
            f = open(filepath, 'w')
            f.write(file_content)
            f.flush()
        func(dirname)

script_adds_two_roots_template = """
from bokeh.io import curdoc
from bokeh.model import Model
from bokeh.core.properties import Int, Instance

class %s(Model):
    bar = Int(1)

class %s(Model):
    foo = Int(2)
    child = Instance(Model)

curdoc().add_root(%s())
curdoc().add_root(%s())
"""

def script_adds_two_roots(some_model_name, another_model_name):
    return script_adds_two_roots_template % (another_model_name, some_model_name,
                                             another_model_name, some_model_name)

script_has_lifecycle_handlers = """
def on_server_loaded(server_context):
    return "on_server_loaded"
def on_server_unloaded(server_context):
    return "on_server_unloaded"
def on_session_created(session_context):
    return "on_session_created"
def on_session_destroyed(session_context):
    return "on_session_destroyed"
"""

class TestDirectoryHandler(unittest.TestCase):

    def test_directory_empty_mainpy(self):
        doc = Document()
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : "# This script does nothing"
        }, load)

        assert not doc.roots

    def test_directory_mainpy_adds_roots(self):
        doc = Document()
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : script_adds_two_roots('SomeModelInTestDirectory',
                                              'AnotherModelInTestDirectory')
        }, load)

        assert len(doc.roots) == 2

    def test_directory_has_theme_file(self):
        doc = Document()
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        custom_theme = """
attrs:
    AnotherModelInTestDirectoryTheme:
        bar: 42
    SomeModelInTestDirectoryTheme:
        foo: 14
"""

        _with_directory_contents({
            'main.py' : script_adds_two_roots('SomeModelInTestDirectoryTheme',
                                              'AnotherModelInTestDirectoryTheme') +
            """
# we're testing that the script can override the theme
some = next(m for m in curdoc().roots if isinstance(m, SomeModelInTestDirectoryTheme))
some.foo = 57
            """,
            'theme.yaml' : custom_theme
        }, load)

        self.assertEqual(2, len(doc.roots))
        some_model = next(m for m in doc.roots if m.__class__.__name__ == 'SomeModelInTestDirectoryTheme')
        another_model = next(m for m in doc.roots if m.__class__.__name__ == 'AnotherModelInTestDirectoryTheme')
        self.assertEqual(42, another_model.bar)
        self.assertEqual(57, some_model.foo)
        # test that we use the theme if we delete our explicit-set value
        del some_model.foo
        self.assertEqual(14, some_model.foo)
        # test that removing the theme gets us back to the base
        doc.theme = None
        self.assertEqual(2, some_model.foo)
        self.assertEqual(1, another_model.bar)

    def test_directory_with_server_lifecycle(self):
        doc = Document()
        result = {}
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            result['handler'] = handler
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : script_adds_two_roots('SomeModelInTestDirectoryWithLifecycle',
                                              'AnotherModelInTestDirectoryWithLifecycle'),
            'server_lifecycle.py' : script_has_lifecycle_handlers
        }, load)

        assert len(doc.roots) == 2

        handler = result['handler']

        assert "on_server_loaded" == handler.on_server_loaded(None)
        assert "on_server_unloaded" == handler.on_server_unloaded(None)
        assert "on_session_created" == handler.on_session_created(None)
        assert "on_session_destroyed" == handler.on_session_destroyed(None)

    def test_directory_with_static(self):
        doc = Document()
        result = {}
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            result['handler'] = handler
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : "# This script does nothing",
            'static/js/foo.js' : "# some JS"
        }, load)

        assert not doc.roots

        handler = result['handler']
        assert handler.static_path() is not None
        assert handler.static_path().endswith("static")

    def test_directory_without_static(self):
        doc = Document()
        result = {}
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            result['handler'] = handler
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : "# This script does nothing",
        }, load)

        assert not doc.roots

        handler = result['handler']
        assert handler.static_path() is None

    def test_directory_with_template(self):
        doc = Document()
        result = {}
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            result['handler'] = handler
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : "# This script does nothing",
            'templates/index.html' : "<div>some HTML</div>"
        }, load)

        assert not doc.roots

        handler = result['handler']
        assert isinstance(doc.template, jinja2.Template)

    def test_directory_without_template(self):
        doc = Document()
        result = {}
        def load(filename):
            handler = DirectoryHandler(filename=filename)
            result['handler'] = handler
            handler.modify_document(doc)
            if handler.failed:
                raise RuntimeError(handler.error)

        _with_directory_contents({
            'main.py' : "# This script does nothing",
        }, load)

        assert not doc.roots

        handler = result['handler']
        assert doc.template is FILE
