from tornado.ioloop import IOLoop
from tornado.options import options
from tornado.platform.asyncio import AnyThreadEventLoopPolicy
from concurrent.futures import ThreadPoolExecutor
from tornado import autoreload
from ..web.settings import settings
from ..utils.rd_session import connect
from ..utils.json_utils import dumps, loads
from ..utils.config import extend
from . import perform
import importlib
import threading
import asyncio
import logging

LOGGER = logging.getLogger(__name__)

"""
    Main worker file. It subscribes to the
    redis queue and broadcasts activity
"""


class Worker:
    def __init__(self, ioloop):
        self.ioloop = ioloop
        self.ioloop.set_default_executor(ThreadPoolExecutor())
        self.procedures = importlib.import_module(options.procedures)

    def get_params(self, content):
        if isinstance(content["params"], dict):
            return [], content["params"]
        elif isinstance(content["params"], list):
            return content["params"], {}
        else:
            raise Exception("Params neither list or dict")

    async def perform(self, content):
        actor = content.get("current_user")
        args, kwargs = self.get_params(content)
        method = content["method"]
        content["thread"] = threading.get_ident()
        if ":" in method:
            mod_name, func_name = method.split(":")
            procedure = getattr(importlib.import_module(mod_name), func_name)
        else:
            procedure = getattr(self.procedures, method)
        try:
            LOGGER.info("%r %s %s", method, args, kwargs)
            result = await perform.perform(actor, procedure, args, kwargs)
            content["result"] = result
            if isinstance(result, perform.Context):
                content["is_context"] = True
        except Exception as ex:
            content["error"] = {"code": -32000, "message": str(ex)}
        return content

    async def _broadcast_(self, signal, message, filter_clients=None):
        msg = {"signal": signal, "message": message, "filter_clients": filter_clients}
        await self.broadcast_redis.publish("broadcast", dumps(msg))

    def broadcast(self, signal, message, filter_clients=None):
        """
            a thread safe way to broadcast
        """
        self.ioloop.spawn_callback(self._broadcast_, signal, message, filter_clients)

    async def do_work(self, val):
        content = loads(val[1].decode("utf-8"))
        if content.get("id"):
            await self._broadcast_("broadcast", {"working": content["id"]})
        content = await self.perform(content)
        if content.get("reply"):
            await self.broadcast_redis.publish(content["reply"], dumps(content))
            LOGGER.info("published reply %s", content)

    async def work(self):
        """ Connects to redis a awaits messages from queue """
        self.broadcast_redis = await connect()
        self.work_q = await connect()
        LOGGER.info("ready for work")
        while True:
            val = await self.work_q.blpop(options.redis_work_q)
            if val:
                await self.do_work(val)
        self.work_q.close()
        await self.work_q.wait_closed()
        self.broadcast_redis.close()
        await self.broadcast_redis.wait_closed()

    async def put(self, content):
        await self.broadcast_redis.execute(
            "rpush", options.redis_work_q, dumps(content)
        )


def main():
    """ Creates Worker and starts tornado """
    asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
    values = settings()
    if options.settings_extend:
        values = extend(options.settings_extend, values)

    assert options.redis_url
    autoreload.start()
    ioloop = IOLoop.current()
    perform._manager_ = Worker(ioloop=ioloop)
    for _ in range(options.workers):
        ioloop.spawn_callback(perform._manager_.work)
    try:
        ioloop.start()
    except KeyboardInterrupt:
        logging.info("stopping")
