# Author: echel0n <echel0n@sickrage.ca>
# URL: https://sickrage.ca
#
# This file is part of SickRage.
#
# SickRage 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.
#
# SickRage 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 SickRage.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import unicode_literals

import io
import os
import platform
from collections import defaultdict
from itertools import imap

from .exceptions import DelugeRPCError
from .protocol import DelugeRPCRequest, DelugeRPCResponse
from .transfer import DelugeTransfer

__all__ = ["DelugeClient"]


RPC_RESPONSE = 1
RPC_ERROR = 2
RPC_EVENT = 3


class DelugeClient(object):
    def __init__(self):
        """A deluge client session."""
        self.transfer = DelugeTransfer()
        self.modules = []
        self._request_counter = 0

    def _get_local_auth(self):
        auth_file = ""
        username = password = ""
        if platform.system() in ('Windows', 'Microsoft'):
            appDataPath = os.environ.get("APPDATA")
            if not appDataPath:
                # noinspection PyUnresolvedReferences
                import _winreg
                hkey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders")
                appDataReg = _winreg.QueryValueEx(hkey, "AppData")
                appDataPath = appDataReg[0]
                _winreg.CloseKey(hkey)

            auth_file = os.path.join(appDataPath, "deluge", "auth")
        else:
            from xdg.BaseDirectory import save_config_path
            try:
                auth_file = os.path.join(save_config_path("deluge"), "auth")
            except OSError, e:
                return username, password


        if os.path.exists(auth_file):
            with io.open(auth_file) as fp:
                for line in fp.readlines():
                    if line.startswith("#"):
                        # This is a comment line
                        continue
                    line = line.strip()
                    try:
                        lsplit = line.split(":")
                    except Exception, e:
                        continue

                    if len(lsplit) == 2:
                        username, password = lsplit
                    elif len(lsplit) == 3:
                        username, password, level = lsplit
                    else:
                        continue

                    if username == "localclient":
                        return username, password

        return ("", "")

    def _create_module_method(self, module, method):
        fullname = "{0}.{1}".format(module, method)

        def func(obj, *args, **kwargs):
            return self.remote_call(fullname, *args, **kwargs)

        func.__name__ = method

        return func

    def _introspect(self):
        self.modules = []

        methods = self.remote_call("daemon.get_method_list").get()
        methodmap = defaultdict(dict)
        splitter = lambda v: v.split(".")

        for module, method in imap(splitter, methods):
            methodmap[module][method] = self._create_module_method(module, method)

        for module, methods in methodmap.items():
            clsname = str("DelugeModule{}".format(module.capitalize()))
            cls = type(clsname, (), methods)
            setattr(self, module, cls())
            self.modules.append(module)

    def remote_call(self, method, *args, **kwargs):
        req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs)
        message = next(self.transfer.send_request(req))

        response = DelugeRPCResponse()

        if not isinstance(message, tuple):
            return

        if len(message) < 3:
            return

        message_type = message[0]

#        if message_type == RPC_EVENT:
#            event = message[1]
#            values = message[2]
#
#            if event in self._event_handlers:
#                for handler in self._event_handlers[event]:
#                    gevent.spawn(handler, *values)
#
#        elif message_type in (RPC_RESPONSE, RPC_ERROR):
        if message_type in (RPC_RESPONSE, RPC_ERROR):
            request_id = message[1]
            value = message[2]

            if request_id == self._request_counter :
                if message_type == RPC_RESPONSE:
                    response.set(value)
                elif message_type == RPC_ERROR:
                    err = DelugeRPCError(*value)
                    response.set_exception(err)

        self._request_counter += 1
        return response

    def connect(self, host="127.0.0.1", port=58846, username="", password=""):
        """Connects to a daemon process.

        :param host: str, the hostname of the daemon
        :param port: int, the port of the daemon
        :param username: str, the username to login with
        :param password: str, the password to login with
        """

        # Connect transport
        self.transfer.connect((host, port))

        # Attempt to fetch local auth info if needed
        if not username and host in ("127.0.0.1", "localhost"):
            username, password = self._get_local_auth()

        # Authenticate
        self.remote_call("daemon.login", username, password).get()

        # Introspect available methods
        self._introspect()

    @property
    def connected(self):
        return self.transfer.connected

    def disconnect(self):
        """Disconnects from the daemon."""
        self.transfer.disconnect()

