from __future__ import print_function

import threading

from . import lsp_structs



class LspEndpoint(threading.Thread):
    def __init__(self, json_rpc_endpoint, method_callbacks = {}, notify_callbacks = {}, timeout = 2):
        threading.Thread.__init__(self)
        self.json_rpc_endpoint = json_rpc_endpoint
        self.notify_callbacks  = notify_callbacks
        self.method_callbacks  = method_callbacks
        self.event_dict        = {}
        self.response_dict     = {}
        self.next_id           = 0
        self._timeout          = timeout
        self.shutdown_flag     = False

    def handle_result(self, rpc_id, result, error):
        self.response_dict[rpc_id] = (result, error)
        cond = self.event_dict[rpc_id]
        cond.acquire()
        cond.notify()
        cond.release()

    def stop(self):
        self.shutdown_flag = True

    def run(self):
        while not self.shutdown_flag:
            try:
                jsonrpc_message = self.json_rpc_endpoint.recv_response()
                if jsonrpc_message is None: break

                method = jsonrpc_message.get("method")
                result = jsonrpc_message.get("result")
                error  = jsonrpc_message.get("error")
                rpc_id = jsonrpc_message.get("id")
                params = jsonrpc_message.get("params")

                if method:
                    if rpc_id is not None:
                        if method not in self.method_callbacks:
                            raise lsp_structs.ResponseError(
                                lsp_structs.ErrorCodes.MethodNotFound,
                                "Method not found: {method}"
                                .format(method=method))
                        result = self.method_callbacks[method](params)
                        self.send_response(rpc_id, result, None)
                    else:
                        if method not in self.notify_callbacks:
                            print("Notify method not found: {method}.".format(method=method))
                        else:
                            self.notify_callbacks[method](params)
                else:
                    self.handle_result(rpc_id, result, error)
            except lsp_structs.ResponseError as e:
                self.send_response(rpc_id, None, e)

    def send_response(self, id, result, error):
        message_dict            = {}
        message_dict["jsonrpc"] = "2.0"
        message_dict["id"]      = id

        if result:
            message_dict["result"] = result
        if error:
            message_dict["error"] = error

        self.json_rpc_endpoint.send_request(message_dict)

    def send_message(self, method_name, params, id=None):
        message_dict = {}
        message_dict["jsonrpc"] = "2.0"

        if id is not None:
            message_dict["id"] = id

        message_dict["method"] = method_name
        message_dict["params"] = params

        self.json_rpc_endpoint.send_request(message_dict)

    def call_method(self, method_name, **kwargs):
        current_id    = self.next_id
        self.next_id += 1
        cond         = threading.Condition()
        self.event_dict[current_id] = cond

        cond.acquire()
        self.send_message(method_name, kwargs, current_id)
        if self.shutdown_flag:
            return None

        if not cond.wait(timeout=self._timeout):
            raise TimeoutError()
        cond.release()

        self.event_dict.pop(current_id)
        result, error = self.response_dict.pop(current_id)
        if error:
            raise lsp_structs.ResponseError(error.get("code"),
                                            error.get("message"),
                                            error.get("data"))

        return result

    def send_notification(self, method_name, **kwargs):
        self.send_message(method_name, kwargs)