"""Inter process communication with Minecraft server.

This file is part of MiSMaP.

MiSMaP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

MiSMaP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with MiSMaP.  If not, see <http://www.gnu.org/licenses/>.
"""
from json import load
from sys import stderr

from mismap.config import BASE_DIR
from mismap.eventapi import EventHandler
from mismap.exceptions import HandlerNotFound


__all__ = [
    'BASE_DIR',
    'NoSuchServer',
    'DoesNotImplementInterface',
    'Server']


class ServerError(Exception):
    """General server-related error."""

    def __init__(self, server):
        """Sets server name and base directory."""
        super().__init__(server)
        self.server = server


class NoSuchServer(ServerError):
    """Indicates that the respective server does not exist."""

    pass


class DoesNotImplementInterface(ServerError):
    """Indicates that the respective server
    does not implement the API interface.
    """

    pass


class Server:
    """Minecraft server IPC client."""

    def __init__(self, path):
        """Sets the path to the server."""
        self.path = path
        self.stdin = None
        self.stdout = None
        self.config = self.load_config()
        self.event_handlers = tuple(self.load_event_handlers())

    def __enter__(self):
        """Opens the pipes."""
        self.stdin = self.stdin_path.open('wb').__enter__()
        self.stdout = self.stdout_path.open('rb').__enter__()
        return self

    def __exit__(self, typ, value, traceback):
        """Closes the pipes."""
        self.stdin.__exit__(typ, value, traceback)
        self.stdin = None
        self.stdout.__exit__(typ, value, traceback)
        self.stdout = None

    @classmethod
    def by_name(cls, name, base_dir=BASE_DIR):
        """Creates a server by its name."""
        server = cls(base_dir.joinpath(name))

        if server.exists:
            if server.complete:
                return server

            raise DoesNotImplementInterface(server)

        raise NoSuchServer(server)

    @property
    def stdin_path(self):
        """Returns the path to the server's STDIN."""
        return self.path.joinpath('.stdin')

    @property
    def stdout_path(self):
        """Returns the path to the server's STDOUT."""
        return self.path.joinpath('.stdout')

    @property
    def fifos(self):
        """Returns all named pipes."""
        return (self.stdin_path, self.stdout_path)

    @property
    def exists(self):
        """Determines whether the server exists."""
        return self.path.is_dir()

    @property
    def complete(self):
        """Determines whether the server
        implementes the necessary interface.
        """
        if not self.config_path.is_file():
            return False

        return all(fifo.is_fifo() for fifo in self.fifos)

    @property
    def config_path(self):
        """Returns the path to the config file."""
        return self.path.joinpath('mismap.json')

    def load_config(self):
        """Returns the path to the MiSMaP configuration file."""
        with self.config_path.open('r') as file:
            return load(file)

    def load_event_handlers(self):
        """Yields the respective event handlers."""
        for event_handler_name in self.config.get('event_handlers', tuple()):
            try:
                event_handler = EventHandler.by_name(event_handler_name)
            except HandlerNotFound as handler_not_found:
                print(handler_not_found, file=stderr, flush=True)
            else:
                yield event_handler(self)

    def send(self, bytes_, flush=True):
        """Sends the string to the server's STDIN."""
        self.stdin.write(bytes_)

        if flush:
            self.stdin.flush()
