#!/usr/bin/env python
# -*- coding: utf-8 -*-

from datetime import datetime
from copy import deepcopy
import asyncio
import logging
import os

from aiohttp.web import Application, run_app
from aiohttp_json_rpc import JsonRpc

from flamingo.core.utils.cli import gen_default_parser, parse_args
from flamingo.core.utils.aionotify import RecursiveWatcher
from flamingo.core.data_model import ContentSet, Q
from flamingo.core.utils.aiohttp import Exporter
from flamingo.core.context import Context, ln_s
from flamingo import SERVER_STATIC_ROOT


class IncrementalContext(Context):
    def run_plugin_hook(self, name, *args, **kwargs):
        if name in ('parser_setup'):
            super().run_plugin_hook(name, *args, **kwargs)

    def build(self, clean=True):
        super().run_plugin_hook('contents_parsed')
        super().run_plugin_hook('templating_engine_setup',
                                self.templating_engine)
        super().run_plugin_hook('context_setup')
        super().run_plugin_hook('pre_build')
        super().build(clean=clean, cp=ln_s)
        super().run_plugin_hook('post_build')


class IncrementalBuildEnv:
    def __init__(self, settings):
        self.settings = settings
        self.raw_contents = ContentSet()

    def build(self, paths=[]):
        if paths and not isinstance(paths, list):
            paths = [paths]

        if paths:
            self.settings.CONTENT_PATHS = paths

            context = IncrementalContext(
                self.settings,
                contents=self.raw_contents.exclude(path__in=paths),
            )

        else:
            self.settings.CONTENT_PATHS = []
            context = IncrementalContext(self.settings)

        self.raw_contents = deepcopy(context.contents)
        context.build(clean=not bool(paths))

        return context


class RPCHandler(logging.Handler):
    def handle(self, record):
        if record.name.startswith('flamingo'):
            self.rpc.worker_pool.run_sync(
                self.rpc.notify,
                'log',
                {
                    'time': str(datetime.fromtimestamp(record.created)),
                    'name': record.name,
                    'level': record.levelname,
                    'message': record.getMessage(),
                },
                wait=False,
            )


# parse command line
parser = gen_default_parser()

parser.add_argument('--port', type=int, default=8080)
parser.add_argument('--host', type=str, default='localhost')
parser.add_argument('--shell', action='store_true')

namespace, settings = parse_args(parser)

# setup rpc
loop = asyncio.get_event_loop()
rpc = JsonRpc(loop=loop, max_workers=4)
worker_pool = rpc.worker_pool

rpc.add_topics(
    ('status',),
    ('log',),
    ('messages',),
)

handler = RPCHandler()
handler.rpc = rpc

logging.getLogger().addHandler(handler)

# build
build_env = IncrementalBuildEnv(settings)
context = build_env.build()


# rpc methods
async def toggle_index(request):
    global content_exporter

    content_exporter.show_index = not content_exporter.show_index

    return content_exporter.show_index


async def rebuild(request):
    global build_env, context

    context = build_env.build()

    await request.rpc.notify('status', {
        'changed_paths': ['*'],
    })

rpc.add_methods(
    ('', toggle_index),
    ('', rebuild),
)


# setup watcher
watcher = RecursiveWatcher(settings.CONTENT_ROOT, loop=loop)
loop.run_until_complete(watcher.setup())


async def watcher_task():
    global build_env, context

    while True:
        try:
            path = await watcher.get_file()
            path = os.path.relpath(path, settings.CONTENT_ROOT)

            logging.info('%s changed', path)

            await rpc.notify(
                'messages',
                '<span class="important">{}</span> changed. Rebuilding...'.format(path),  # NOQA
            )

            context = build_env.build(path)

            await rpc.notify(
                'messages',
                '<span class="important">{}</span> rebuild successful'.format(path),  # NOQA
            )

            changed_paths = [
                '/' + i for i in context.contents.filter(
                    Q(path=path) | Q(i18n_path=path)
                ).values('output')
            ]

            for path in changed_paths[::]:
                if path.endswith('index.html'):
                    changed_paths.append(os.path.dirname(changed_paths[0]))
                    changed_paths.append(changed_paths[-1] + '/')

            await rpc.notify('status', {
                'changed_paths': changed_paths,
            })

        except Exception as e:
            logging.error(e, exc_info=True)


loop.create_task(watcher_task())

# setup server
app = Application(loop=loop)
content_exporter = Exporter(settings.OUTPUT_ROOT)

app.router.add_route('*', '/live-server/rpc/', rpc)

app.router.add_route('*', '/live-server/{path_info:.*}',
                          Exporter(SERVER_STATIC_ROOT, prefix='/live-server'))

app.router.add_route('*', '/{path_info:.*}', content_exporter)

# run
print('starting server on http://{}:{}/live-server/'.format(namespace.host,
                                                            namespace.port))

if namespace.shell:
    print('starting shell')

    def start_shell():
        try:
            import IPython

            IPython.embed()

            return

        except ImportError:
            pass

        import code

        code.interact(local=globals())

    loop.run_until_complete(
        loop.create_server(
            app.make_handler(), namespace.host, namespace.port
        )
    )

    loop.run_until_complete(worker_pool.run(start_shell))

else:
    run_app(app=app, host=namespace.host, port=namespace.port,
            print=lambda *args, **kwargs: None)
