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

from knowledge_core import __version__

import logging

logger = logging.getLogger("KnowledgeCore")


import sys
import asyncore, asynchat
import socket

import json

from knowledge_core.exceptions import KbServerError

PORT = 6969


class KnowledgeCoreROS:
    def __init__(self, kb):
        self.kb = kb

        try:
            import rospy
            from knowledge_core.srv import Manage, Revise, Query, Sparql
        except ImportError:
            print(
                "Unable to load the ROS support. You might want to run with --no-ros"
                " to use KnowledgeCore without ROS support enabled."
            )
            sys.exit(1)

        rospy.init_node("knowledge_core", disable_signals=True)
        self.services = {
            "manage": rospy.Service("kb/manage", Manage, self.handle_manage),
            "revise": rospy.Service("kb/revise", Revise, self.handle_revise),
            "query": rospy.Service("kb/query", Query, self.handle_query),
            "sparql": rospy.Service("kb/sparql", Sparql, self.handle_sparql),
        }

    def handle_manage(self, req):

        from knowledge_core.srv import ManageResponse, ManageRequest

        try:
            if req.action == ManageRequest.CLEAR:
                kb.clear()
                return ManageResponse(success=True, error_msg="")

            elif req.action == ManageRequest.LOAD:
                if len(req.parameters) != 1:
                    return ManageResponse(
                        success=False,
                        error_msg="'load' expects 'parameters' to contain exactly one URI pointing to the ontology to load",
                    )
                kb.load(req.parameters[0], models=req.models)
                return ManageResponse(success=True, error_msg="")

            elif req.action == ManageRequest.STATUS:
                status = {
                    "name": kb.hello(),
                    "version": kb.version(),
                    "reasoning_enabled": kb.reasoner_enabled,
                }
                return ManageResponse(
                    success=True, json=json.dumps(status), error_msg=""
                )
            else:
                return ManageResponse(
                    success=False, error_msg="Unknown 'manage' action: %s" % req.action
                )
        except KbServerError as kbe:
            return ManageResponse(success=False, error_msg=str(kbe))

    def handle_revise(self, req):

        from knowledge_core.srv import ReviseResponse

        policy = {"method": req.method, "models": req.models}
        try:
            kb.revise(req.statements, policy)

            return ReviseResponse(success=True, error_msg="")
        except KbServerError as kbe:
            return ReviseResponse(success=False, error_msg=str(kbe))

    def handle_query(self, req):

        from knowledge_core.srv import QueryResponse

        try:
            res = kb.find(req.patterns, req.vars, req.models)
            return QueryResponse(success=True, error_msg="", json=json.dumps(res))
        except KbServerError as kbe:
            return QueryResponse(success=False, error_msg=str(kbe))

    def handle_sparql(self, req):

        from knowledge_core.srv import SparqlResponse

        try:
            res = kb.sparql(req.query, req.models)
            return SparqlResponse(success=True, error_msg="", json=json.dumps(res))
        except KbServerError as kbe:
            return SparqlResponse(success=False, error_msg=str(kbe))

    def shutdown(self):
        import rospy

        rospy.signal_shutdown("knowledge_core closing")


class KnowledgeCoreChannel(asynchat.async_chat):
    def __init__(self, server, sock, addr, kb):
        asynchat.async_chat.__init__(self, sock)
        self.set_terminator("#end#".encode("utf-8"))
        self.request = None
        self.data = ""

        self.kb = kb

    def parse_request(self, req):
        tokens = [a.strip() for a in req.strip().split("\n")]
        if len(tokens) == 1:
            return tokens[0], [], {}
        else:
            args = []
            for t in tokens[1:]:
                try:
                    args.append(json.loads(t))
                except ValueError:
                    logger.error(
                        "Invalid arguments for <%s>: %s. Valid JSON was expected."
                        % (tokens[0], t)
                    )

            if isinstance(args[-1], dict) and "kwargs" in args[-1]:
                kwargs = args[-1]["kwargs"]
                args = args[:-1]
            else:
                kwargs = {}
            return tokens[0], args, kwargs

    def collect_incoming_data(self, data):
        self.data = self.data + data.decode()

    def found_terminator(self):

        logger.debug("Got request:" + self.data)
        request, args, kwargs = self.parse_request(self.data)
        self.data = ""
        self.kb.submitrequest(self, request, *args, **kwargs)

        if request == "close":
            self.close_when_done()

    def sendmsg(self, msg):
        status, res = msg

        raw = ""
        if status == "ok":
            raw = (
                "ok\n%s\n" % json.dumps(res, ensure_ascii=False)
                if res is not None
                else "ok\n"
            )
        elif status == "error":
            raw = "error\nkberror\n%s\n" % str(res)
        elif status == "event":
            raw = "event\n%s\n%s\n" % (
                res.id,
                json.dumps(res.content, ensure_ascii=False),
            )
        else:
            raise RuntimeError("Unexpected message status: %s" % status)

        logger.debug("Sent message:" + raw)
        self.push((raw + "#end#\n").encode("utf-8"))


class KnowledgeCoreServer(asyncore.dispatcher):
    def __init__(self, port, kb):
        asyncore.dispatcher.__init__(self)

        self.kb = kb

        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(("", port))
        self.listen(5)

    def handle_accept(self):
        conn, addr = self.accept()
        KnowledgeCoreChannel(self, conn, addr, kb)


def version():
    print("KnowledgeCore %s" % __version__)


if __name__ == "__main__":

    from knowledge_core.ansistrm import ColorizingStreamHandler
    import argparse

    parser = argparse.ArgumentParser(description="A minimal knowledge base for robots.")
    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version=__version__,
        help="returns KnowledgeCore version",
    )
    parser.add_argument(
        "-q", "--quiet", action="store_true", help="be quiet (only errors are reported)"
    )
    parser.add_argument(
        "-d", "--debug", action="store_true", help="enables verbose output"
    )
    parser.add_argument(
        "--no-reasoner", action="store_false", help="do not use the reasoner"
    )
    parser.add_argument(
        "--no-ros", action="store_false", help="do not enable ROS support"
    )
    parser.add_argument(
        "-p",
        "--port",
        default=PORT,
        type=int,
        nargs="?",
        help="port the server listen to.",
    )
    parser.add_argument(
        "ontology",
        nargs="*",
        help="Files (OWL, RDF or raw triples) with initial ontologies to load",
    )

    # filter out possible ROS remapping args
    argv = [a for a in sys.argv if not a.startswith("__")]
    args = parser.parse_args(argv[1:])

    console = ColorizingStreamHandler()

    if args.debug:
        logger.setLevel(logging.DEBUG)
    elif args.quiet:
        logger.setLevel(logging.ERROR)
    else:
        logger.setLevel(logging.INFO)

    formatter = logging.Formatter("%(asctime)-15s: %(message)s")
    console.setFormatter(formatter)
    logger.addHandler(console)

    # The logging handlers are now setup, import the other knowledge_core components
    from knowledge_core.kb import KnowledgeCore, KbServerError

    kb = KnowledgeCore(args.ontology, enable_reasoner=args.no_reasoner)

    s = KnowledgeCoreServer(args.port, kb)
    logger.info("Starting to serve at port %d..." % args.port)

    if args.no_ros:
        logger.info("Initializing the ROS node...")
        ros = KnowledgeCoreROS(kb)

    try:
        while True:
            asyncore.loop(count=1)
            kb.process()
    except KeyboardInterrupt:
        if args.no_ros:
            ros.shutdown()
        kb.stop_services()
        logger.info("Bye bye")
