# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS",  WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os.path
import time

from .lib.torrentparser import TorrentParser
from .lib.xmlrpc.basic_auth import BasicAuthTransport
from .lib.xmlrpc.http import HTTPServerProxy
from .lib.xmlrpc.scgi import SCGIServerProxy

from . import rpc
from .common import convert_version_tuple_to_str, find_torrent, is_valid_port, join_uri, update_uri
from .compat import urlparse, xmlrpclib
from .file import File, methods as file_methods
from .group import Group
from .peer import Peer, methods as peer_methods
from .rpc import Method
from .torrent import Torrent, methods as torrent_methods
from .tracker import Tracker, methods as tracker_methods


__version__ = '0.2.10'
__author__ = 'Chris Lucas'
__maintainer__ = 'SickGear'
__contact__ = 'chris@chrisjlucas.com'
__license__ = 'MIT'

MIN_RTORRENT_VERSION = (0, 8, 1)
MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION)


class RTorrent(object):
    """ Create a new rTorrent connection """
    rpc_prefix = None

    def __init__(self, uri, username=None, password=None, check_connection=True, sp=None, sp_kwargs=None):

        self.username = username
        self.password = password

        self.scheme, self.uri = self._transform_uri(uri)
        if sp:
            self.sp = sp
        elif self.scheme in ['http', 'https']:
            self.sp = HTTPServerProxy
        elif 'scgi' == self.scheme:
            self.sp = SCGIServerProxy
        else:
            raise NotImplementedError()

        self.sp_kwargs = sp_kwargs or {}

        self.torrents = []  # : List of L{Torrent} instances
        self._rpc_methods = []  # : List of rTorrent RPC methods
        self._torrent_cache = []
        self._client_version_tuple = ()
        self.use_target = False
        self.dht_port = None
        self.request_interval = 3

        if check_connection:
            self._check_connection()

    @staticmethod
    def _transform_uri(uri):
        scheme = urlparse(uri).scheme

        if 'httprpc' == scheme or scheme.startswith('httprpc+'):
            # Transform URI with new path and scheme
            uri = join_uri(uri, 'plugins/httprpc/action.php', construct=False)

            # Try find HTTPRPC transport (token after '+' in 'httprpc+https'), otherwise assume HTTP
            transport = 'http' if '+' not in scheme else scheme[scheme.index('+') + 1:]

            uri = update_uri(uri, scheme=transport)

        return urlparse(uri).scheme, uri

    def get_connection(self):
        """Get ServerProxy instance"""

        if not (self.username and self.password):
            return self.sp(self.uri, **self.sp_kwargs)

        if 'scgi' == self.scheme:
            raise NotImplementedError()

        return self.sp(self.uri,
                       transport=BasicAuthTransport('https' == self.scheme, self.username, self.password),
                       **self.sp_kwargs)

    def test_connection(self):
        """
        Test connection to rT
        :return: True of connection successful, otherwise False
        :rtype: Boolean
        """
        try:
            self._check_connection()
            return True
        except (BaseException, Exception):
            return False

    def _check_connection(self):
        # check for rpc methods that should be available
        assert self.method_exists('system.client_version'), \
            'Required RPC method (client_version) not available. Check connection settings in client.'
        assert self.method_exists('system.library_version'), \
            'Required RPC method (system.library_version) not available. Check connection settings in client.'

        # minimum rTorrent version check
        assert self._meets_version_requirement() is True, \
            'Error: Minimum rTorrent version required is %s' % MIN_RTORRENT_VERSION_STR

    def method_exists(self, name):
        """
        Check if rpc method exists
        :param name: Name of method
        :type name: str
        :return: True if method is found
        :rtype: Boolean
        """
        return name in self.get_rpc_methods()

    def get_rpc_methods(self):
        """ Get list of raw RPC commands

        @return: raw RPC commands
        @rtype: list
        """

        return self._rpc_methods or self._fetch_rpc_methods()

    def _fetch_rpc_methods(self):

        self._rpc_methods = self.get_connection().system.listMethods()

        return self._rpc_methods

    def _meets_version_requirement(self):
        return self.get_client_version_tuple() >= MIN_RTORRENT_VERSION

    def get_client_version_tuple(self):
        conn = self.get_connection()

        if not self._client_version_tuple:
            if not hasattr(self, 'client_version'):
                setattr(self, 'client_version',
                        conn.system.client_version())

            rtver = getattr(self, 'client_version')
            self._client_version_tuple = tuple([int(i) for i in
                                                rtver.split('.')])

        return self._client_version_tuple

    def get_torrents(self, view='main'):
        """Get list of all torrents in specified view

        @return: list of L{Torrent} instances

        @rtype: list

        @todo: add validity check for specified view
        """
        self.torrents = []
        retriever_methods = list(filter(lambda m: m.is_retriever() and m.is_available(self), torrent_methods))
        mc = rpc.Multicall(self)

        if self.method_exists('d.multicall2'):
            mc.add('d.multicall2', '', view, 'd.hash=',
                   *list(map(lambda m2: ((getattr(m2, 'aliases') or [''])[-1] or m2.rpc_call) + '=',
                             retriever_methods)))
        else:
            mc.add('d.multicall', view, 'd.get_hash=',
                   *list(map(lambda m1: m1.rpc_call + '=', retriever_methods)))

        results = mc.call()[0]  # only sent one call, only need first result

        for result in results:
            self.torrents.append(
                Torrent(self, info_hash=result[0],
                        **dict((mc.varname, rpc.process_result(mc, r))
                               for (mc, r) in list(zip(retriever_methods, result[1:])))))  # result[0]=info_hash

        self._manage_torrent_cache()
        return self.torrents

    def _manage_torrent_cache(self):
        """Carry tracker/peer/file lists over to new torrent list"""
        for t in self._torrent_cache:
            new_torrent = common.find_torrent(t.info_hash, self.torrents)
            if new_torrent is not None:
                new_torrent.files = t.files
                new_torrent.peers = t.peers
                new_torrent.trackers = t.trackers

        self._torrent_cache = self.torrents

    def _get_load_function(self, file_type, start, verbose):
        """Determine correct "load torrent" RPC method"""
        func_name = file_type in ('url', 'file', 'raw') and (
            dict(sv='load_raw_start_verbose', s='load_raw_start', v='load_raw_verbose', ld='load_raw'),
            dict(sv='load_start_verbose', s='load_start', v='load_verbose', ld='load')
        )['url' == file_type][
            ((('ld', 'v')[verbose], 's')[start], 'sv')[start and verbose]] or None

        if not self.method_exists(func_name):
            if 'load' == func_name:
                func_name = 'load.normal'
            func_name = func_name.replace('load_', 'load.')
            if not self.method_exists(func_name):
                func_name = None

        return func_name

    def execute_func(self, func_name, param=None, extra=None):
        
        param = ([param], param)[isinstance(param, list)]
        for x in (extra or []):
            try:
                call, arg = x.split('=')
                method = rpc.find_method(call)
                method_name = next(filter(lambda m: self.method_exists(m), (method.rpc_call,) + method.aliases))
                param += ['%s=%s' % (method_name, arg)]
            except (BaseException, Exception):
                pass

        method = getattr(self.get_connection(), func_name)

        if not self.use_target:
            try:
                method(*param)
            except (BaseException, Exception):
                self.use_target = True
        if self.use_target:
            method('', *param)

    def load_magnet(self, magneturl, info_hash, extra=None, start=False, verbose=False, verify_load=True):

        func_name = self._get_load_function('url', start, verbose)
        # load magnet
        self.execute_func(func_name, magneturl, extra)

        t = None
        if verify_load:
            info_hash = info_hash.upper()
            max_retries = 10
            while max_retries:
                try:
                    t = next(filter(lambda td: td.info_hash.upper() == info_hash, self.get_torrents()))
                    break
                except (BaseException, Exception):
                    time.sleep(self.request_interval)
                    max_retries -= 1

        return t

    def load_torrent(self, data, extra=None, start=False, verbose=False, verify_load=True, verify_retries=3):
        """
        Loads torrent into rTorrent (with various enhancements)

        @param data: can be a url, a path to a local file, or the raw data
        of a torrent file
        @type data: str

        @param extra: extra commands to send
        @type extra: array

        @param start: start torrent when loaded
        @type start: bool

        @param verbose: print error messages to rTorrent log
        @type verbose: bool

        @param verify_load: verify that torrent was added to rTorrent successfully
        @type verify_load: bool

        @param verify_retries: number of times to attempt verification
        @type verify_load: int

        @return: Depends on verify_load:
                 - if verify_load is True, (and the torrent was
                 loaded successfully), it'll return a L{Torrent} instance
                 - if verify_load is False, it'll return None

        @rtype: L{Torrent} instance or None

        @raise AssertionError: If the torrent wasn't successfully added to rTorrent
                               - Check L{TorrentParser} for the AssertionError's
                               it raises


        @note: Because this function includes url verification (if a url was input)
        as well as verification as to whether the torrent was successfully added,
        this function doesn't execute instantaneously. If that's what you're
        looking for, use load_torrent_simple() instead.
        """
        tp = TorrentParser(data)
        info_hash = tp.info_hash

        # load torrent
        self.execute_func(self._get_load_function('url', False, False), '')
        self.execute_func(self._get_load_function('raw', start, verbose), xmlrpclib.Binary(tp.raw_torrent), extra)

        t = None
        if verify_load:
            while verify_retries:
                try:
                    t = next(filter(lambda td: td.info_hash == info_hash, self.get_torrents()))
                    break
                except (BaseException, Exception):
                    time.sleep(self.request_interval)
                    verify_retries -= 1

            assert None is not t, 'Adding torrent was unsuccessful.'

        return t

    def load_torrent_simple(self, data, file_type, extra=None, start=False, verbose=False):
        """Loads torrent into rTorrent

        @param data: can be a url, a path to a local file, or the raw data
        of a torrent file
        @type data: str

        @param file_type: valid options: "url", "file", or "raw"
        @type file_type: str

        @param extra: extra commands to send
        @type extra: array

        @param start: start torrent when loaded
        @type start: bool

        @param verbose: print error messages to rTorrent log
        @type verbose: bool

        @return: None

        @raise AssertionError: if incorrect file_type is specified

        @note: This function was written for speed, it includes no enhancements.
        If you input a url, it won't check if it's valid. You also can't get
        verification that the torrent was successfully added to rTorrent.
        Use load_torrent() if you would like these features.
        """
        assert file_type in ['raw', 'file', 'url'], \
            'Invalid file_type, options are: \'url\', \'file\', \'raw\'.'
        func_name = self._get_load_function(file_type, start, verbose)

        if 'file' == file_type:
            # since we have to assume we're connected to a remote rTorrent
            # client, we have to read the file and send it to rT as raw
            assert os.path.isfile(data), \
                'Invalid path: "{0}"'.format(data)
            data = open(data, 'rb').read()
        elif 'raw' == file_type:
            self.execute_func(self._get_load_function('url', False, False), '')

        finput = None
        if file_type in ['raw', 'file']:
            finput = xmlrpclib.Binary(data)
        elif 'url' == file_type:
            finput = data

        self.execute_func(func_name, finput, extra)

    def get_views(self):
        p = self.get_connection()
        return p.view_list()

    def create_group(self, name, persistent=True, view=None):
        p = self.get_connection()

        if persistent is True:
            p.group.insert_persistent_view('', name)
        else:
            assert view is not None, 'view parameter required on non-persistent groups'
            p.group.insert('', name, view)

        self._fetch_rpc_methods()

    def get_group(self, name):
        assert name is not None, 'group name required'

        grp = Group(self, name)
        grp.update()
        return grp

    def set_dht_port(self, port):
        """Set DHT port

        @param port: port
        @type port: int

        @raise AssertionError: if invalid port is given
        """
        assert is_valid_port(port), 'Valid port range is 0-65535'
        # noinspection PyUnresolvedReferences
        self.dht_port = self._p.set_dht_port(port)

    def enable_check_hash(self):
        """Alias for set_check_hash(True)"""
        # noinspection PyUnresolvedReferences
        self.set_check_hash(True)

    def disable_check_hash(self):
        """Alias for set_check_hash(False)"""
        # noinspection PyUnresolvedReferences
        self.set_check_hash(False)

    def find_torrent(self, info_hash):
        """Frontend for common.find_torrent"""
        return common.find_torrent(info_hash, self.get_torrents())

    def has_local_id(self, info_hash):
        method = rpc.find_method('d.get_local_id')
        result = True
        try:
            func = next(filter(lambda m: self.method_exists(m), (method.rpc_call,) + method.aliases))
            getattr(self.get_connection(), func)(info_hash)
        except (BaseException, Exception):
            result = False
        return result

    def poll(self):
        """ poll rTorrent to get latest torrent/peer/tracker/file information

        @note: This essentially refreshes every aspect of the rTorrent
        connection, so it can be very slow if working with a remote
        connection that has a lot of torrents loaded.

        @return: None
        """
        self.update()
        torrents = self.get_torrents()
        for t in torrents:
            t.poll()

    def update(self):
        """Refresh rTorrent client info

        @note: All fields are stored as attributes to self.

        @return: None
        """
        mc = rpc.Multicall(self)

        for method in filter(lambda m: m.is_retriever() and m.is_available(self), methods):
            mc.add(method)

        mc.call()


def _build_class_methods(class_obj):
    # multicall add class
    caller = (lambda self, mc, method, *args: mc.add(method, self.rpc_id, *args))

    caller.__doc__ = """Same as Multicall.add(), but with automatic inclusion
                        of the rpc_id

                        @param multicall: A L{Multicall} instance
                        @type: multicall: Multicall

                        @param method: L{Method} instance or raw rpc method
                        @type: Method or str

                        @param args: optional arguments to pass
                        """
    setattr(class_obj, 'multicall_add', caller)


def __compare_rpc_methods(rt_new, rt_old):
    from pprint import pprint
    rt_new_methods = set(rt_new.get_rpc_methods())
    rt_old_methods = set(rt_old.get_rpc_methods())
    print('New Methods:')
    pprint(rt_new_methods - rt_old_methods)
    print('Methods not in new rTorrent:')
    pprint(rt_old_methods - rt_new_methods)


def __check_supported_methods(rt):
    from pprint import pprint
    supported_methods = set(
        [m.rpc_call for m in methods + file_methods + torrent_methods + tracker_methods + peer_methods])
    all_methods = set(rt.get_rpc_methods())

    print('Methods NOT in supported methods')
    pprint(all_methods - supported_methods)
    print('Supported methods NOT in all methods')
    pprint(supported_methods - all_methods)


methods = [
    # RETRIEVERS
    Method(RTorrent, 'get_xmlrpc_size_limit', 'get_xmlrpc_size_limit',
           aliases=('network.xmlrpc.size_limit',)),
    Method(RTorrent, 'get_proxy_address', 'get_proxy_address',
           aliases=('network.proxy_address',)),
    Method(RTorrent, 'get_split_suffix', 'get_split_suffix',
           aliases=('system.file.split_suffix',)),
    Method(RTorrent, 'get_up_limit', 'get_upload_rate',
           aliases=('throttle.global_up.max_rate',)),
    Method(RTorrent, 'get_max_memory_usage', 'get_max_memory_usage',
           aliases=('pieces.memory.max',)),
    Method(RTorrent, 'get_max_open_files', 'get_max_open_files',
           aliases=('network.max_open_files',)),
    Method(RTorrent, 'get_min_peers_seed', 'get_min_peers_seed',
           aliases=('throttle.min_peers.seed',)),
    Method(RTorrent, 'get_use_udp_trackers', 'get_use_udp_trackers',
           aliases=('trackers.use_udp',)),
    Method(RTorrent, 'get_preload_min_size', 'get_preload_min_size',
           aliases=('pieces.preload.min_size',)),
    Method(RTorrent, 'get_max_uploads', 'get_max_uploads',
           aliases=('throttle.max_uploads',)),
    Method(RTorrent, 'get_max_peers', 'get_max_peers',
           aliases=('throttle.max_peers.normal',)),
    Method(RTorrent, 'get_timeout_sync', 'get_timeout_sync',
           aliases=('pieces.sync.timeout',)),
    Method(RTorrent, 'get_receive_buffer_size', 'get_receive_buffer_size',
           aliases=('network.receive_buffer.size',)),
    Method(RTorrent, 'get_split_file_size', 'get_split_file_size',
           aliases=('system.file.split_size',)),
    Method(RTorrent, 'get_dht_throttle', 'get_dht_throttle',
           aliases=('dht.throttle.name',)),
    Method(RTorrent, 'get_max_peers_seed', 'get_max_peers_seed',
           aliases=('throttle.max_peers.seed',)),
    Method(RTorrent, 'get_min_peers', 'get_min_peers',
           aliases=('throttle.min_peers.normal',)),
    Method(RTorrent, 'get_tracker_numwant', 'get_tracker_numwant',
           aliases=('trackers.numwant',)),
    Method(RTorrent, 'get_max_open_sockets', 'get_max_open_sockets',
           aliases=('network.max_open_sockets',)),
    Method(RTorrent, 'get_session', 'get_session',
           aliases=('session.path',)),
    Method(RTorrent, 'get_ip', 'get_ip',
           aliases=('network.local_address',)),
    Method(RTorrent, 'get_scgi_dont_route', 'get_scgi_dont_route',
           aliases=('network.scgi.dont_route',)),
    Method(RTorrent, 'get_hash_read_ahead', 'get_hash_read_ahead'),
    Method(RTorrent, 'get_http_cacert', 'get_http_cacert',
           aliases=('network.http.cacert',)),
    Method(RTorrent, 'get_dht_port', 'get_dht_port',
           aliases=('dht.port',)),
    Method(RTorrent, 'get_handshake_log', 'get_handshake_log'),
    Method(RTorrent, 'get_preload_type', 'get_preload_type',
           aliases=('pieces.preload.type',)),
    Method(RTorrent, 'get_max_open_http', 'get_max_open_http',
           aliases=('network.http.max_open',)),
    Method(RTorrent, 'get_http_capath', 'get_http_capath',
           aliases=('network.http.capath',)),
    Method(RTorrent, 'get_max_downloads_global', 'get_max_downloads_global',
           aliases=('throttle.max_downloads.global',)),
    Method(RTorrent, 'get_name', 'get_name',
           aliases=('session.name',)),
    Method(RTorrent, 'get_session_on_completion', 'get_session_on_completion',
           aliases=('session.on_completion',)),
    Method(RTorrent, 'get_down_limit', 'get_download_rate',
           aliases=('throttle.global_down.max_rate',)),
    Method(RTorrent, 'get_down_total', 'get_down_total',
           aliases=('throttle.global_down.total',)),
    Method(RTorrent, 'get_up_rate', 'get_up_rate',
           aliases=('throttle.global_up.rate',)),
    Method(RTorrent, 'get_hash_max_tries', 'get_hash_max_tries'),
    Method(RTorrent, 'get_peer_exchange', 'get_peer_exchange',
           aliases=('protocol.pex',)),
    Method(RTorrent, 'get_down_rate', 'get_down_rate',
           aliases=('throttle.global_down.rate',)),
    Method(RTorrent, 'get_connection_seed', 'get_connection_seed',
           aliases=('protocol.connection.seed',)),
    Method(RTorrent, 'get_http_proxy', 'get_http_proxy',
           aliases=('network.http.proxy_address',)),
    Method(RTorrent, 'get_stats_preloaded', 'get_stats_preloaded',
           aliases=('pieces.stats_preloaded',)),
    Method(RTorrent, 'get_timeout_safe_sync', 'get_timeout_safe_sync',
           aliases=('pieces.sync.timeout_safe',)),
    Method(RTorrent, 'get_hash_interval', 'get_hash_interval'),
    Method(RTorrent, 'get_port_random', 'get_port_random',
           aliases=('network.port_random',)),
    Method(RTorrent, 'get_directory', 'get_directory',
           aliases=('directory.default',)),
    Method(RTorrent, 'get_port_open', 'get_port_open',
           aliases=('network.port_open',)),
    Method(RTorrent, 'get_max_file_size', 'get_max_file_size',
           aliases=('system.file.max_size',)),
    Method(RTorrent, 'get_stats_not_preloaded', 'get_stats_not_preloaded',
           aliases=('pieces.stats_not_preloaded',)),
    Method(RTorrent, 'get_memory_usage', 'get_memory_usage',
           aliases=('pieces.memory.current',)),
    Method(RTorrent, 'get_connection_leech', 'get_connection_leech',
           aliases=('protocol.connection.leech',)),
    Method(RTorrent, 'get_check_hash', 'get_check_hash',
           boolean=True,
           aliases=('pieces.hash.on_completion',)
           ),
    Method(RTorrent, 'get_session_lock', 'get_session_lock',
           aliases=('session.use_lock',)),
    Method(RTorrent, 'get_preload_required_rate', 'get_preload_required_rate',
           aliases=('pieces.preload.min_rate',)),
    Method(RTorrent, 'get_max_uploads_global', 'get_max_uploads_global',
           aliases=('throttle.max_uploads.global',)),
    Method(RTorrent, 'get_send_buffer_size', 'get_send_buffer_size',
           aliases=('network.send_buffer.size',)),
    Method(RTorrent, 'get_port_range', 'get_port_range',
           aliases=('network.port_range',)),
    Method(RTorrent, 'get_max_downloads_div', 'get_max_downloads_div',
           aliases=('throttle.max_downloads.div',)),
    Method(RTorrent, 'get_max_uploads_div', 'get_max_uploads_div',
           aliases=('throttle.max_uploads.div',)),
    Method(RTorrent, 'get_safe_sync', 'get_safe_sync',
           aliases=('pieces.sync.always_safe',)),
    Method(RTorrent, 'get_bind', 'get_bind',
           aliases=('network.bind_address',)),
    Method(RTorrent, 'get_up_total', 'get_up_total',
           aliases=('throttle.global_up.total',)),
    Method(RTorrent, 'get_client_version', 'system.client_version'),
    Method(RTorrent, 'get_library_version', 'system.library_version'),
    Method(RTorrent, 'get_api_version', 'system.api_version',
           min_version=(0, 9, 1)
           ),
    Method(RTorrent, 'get_system_time', 'system.time',
           docstring="""Get the current time of the system rTorrent is running on

           @return: time (posix)
           @rtype: int""",
           ),

    # MODIFIERS
    Method(RTorrent, 'set_http_proxy', 'set_http_proxy',
           aliases=('network.http.proxy_address.set',)),
    Method(RTorrent, 'set_max_memory_usage', 'set_max_memory_usage',
           aliases=('pieces.memory.max.set',)),
    Method(RTorrent, 'set_max_file_size', 'set_max_file_size',
           aliases=('system.file.max_size.set',)),
    Method(RTorrent, 'set_bind', 'set_bind',
           docstring="""Set address bind

           @param arg: ip address
           @type arg: str
           """,
           aliases=('network.bind_address.set',)
           ),
    Method(RTorrent, 'set_up_limit', 'set_upload_rate',
           docstring="""Set global upload limit (in bytes)

           @param arg: speed limit
           @type arg: int
           """,
           aliases=('throttle.global_up.max_rate.set',)
           ),
    Method(RTorrent, 'set_port_random', 'set_port_random',
           aliases=('network.port_random.set',)),
    Method(RTorrent, 'set_connection_leech', 'set_connection_leech',
           aliases=('protocol.connection.leech.set',)),
    Method(RTorrent, 'set_tracker_numwant', 'set_tracker_numwant',
           aliases=('trackers.numwant.set',)),
    Method(RTorrent, 'set_max_peers', 'set_max_peers',
           aliases=('throttle.max_peers.normal.set',)),
    Method(RTorrent, 'set_min_peers', 'set_min_peers',
           aliases=('throttle.min_peers.normal.set',)),
    Method(RTorrent, 'set_max_uploads_div', 'set_max_uploads_div',
           aliases=('throttle.max_uploads.div.set',)),
    Method(RTorrent, 'set_max_open_files', 'set_max_open_files',
           aliases=('network.max_open_files.set',)),
    Method(RTorrent, 'set_max_downloads_global', 'set_max_downloads_global',
           aliases=('throttle.max_downloads.global.set',)),
    Method(RTorrent, 'set_session_lock', 'set_session_lock',
           aliases=('session.use_lock.set',)),
    Method(RTorrent, 'set_session', 'set_session',
           aliases=('session.path.set',)),
    Method(RTorrent, 'set_split_suffix', 'set_split_suffix',
           aliases=('system.file.split_suffix.set',)),
    Method(RTorrent, 'set_hash_interval', 'set_hash_interval'),
    Method(RTorrent, 'set_handshake_log', 'set_handshake_log'),
    Method(RTorrent, 'set_port_range', 'set_port_range',
           aliases=('network.port_range.set',)),
    Method(RTorrent, 'set_min_peers_seed', 'set_min_peers_seed',
           aliases=('throttle.min_peers.seed.set',)),
    Method(RTorrent, 'set_scgi_dont_route', 'set_scgi_dont_route',
           aliases=('network.scgi.dont_route.set',)),
    Method(RTorrent, 'set_preload_min_size', 'set_preload_min_size',
           aliases=('pieces.preload.min_size.set',)),
    Method(RTorrent, 'set_log.tracker', 'set_log.tracker'),
    Method(RTorrent, 'set_max_uploads_global', 'set_max_uploads_global',
           aliases=('throttle.max_uploads.global.set',)),
    Method(RTorrent, 'set_down_limit', 'set_download_rate',
           docstring="""Set global download limit (in bytes)

           @param arg: speed limit
           @type arg: int
           """,
           aliases=('throttle.global_down.max_rate.set',)
           ),
    Method(RTorrent, 'set_preload_required_rate', 'set_preload_required_rate',
           aliases=('pieces.preload.min_rate.set',)),
    Method(RTorrent, 'set_hash_read_ahead', 'set_hash_read_ahead'),
    Method(RTorrent, 'set_max_peers_seed', 'set_max_peers_seed',
           aliases=('throttle.max_peers.seed.set',)),
    Method(RTorrent, 'set_max_uploads', 'set_max_uploads',
           aliases=('throttle.max_uploads.set',)),
    Method(RTorrent, 'set_session_on_completion', 'set_session_on_completion',
           aliases=('session.on_completion.set',)),
    Method(RTorrent, 'set_max_open_http', 'set_max_open_http',
           aliases=('network.http.max_open.set',)),
    Method(RTorrent, 'set_directory', 'set_directory',
           aliases=('directory.default.set',)),
    Method(RTorrent, 'set_http_cacert', 'set_http_cacert',
           aliases=('network.http.cacert.set',)),
    Method(RTorrent, 'set_dht_throttle', 'set_dht_throttle',
           aliases=('dht.throttle.name.set',)),
    Method(RTorrent, 'set_hash_max_tries', 'set_hash_max_tries'),
    Method(RTorrent, 'set_proxy_address', 'set_proxy_address',
           aliases=('network.proxy_address.set',)),
    Method(RTorrent, 'set_split_file_size', 'set_split_file_size',
           aliases=('system.file.split_size.set',)),
    Method(RTorrent, 'set_receive_buffer_size', 'set_receive_buffer_size',
           aliases=('network.receive_buffer.size.set',)),
    Method(RTorrent, 'set_use_udp_trackers', 'set_use_udp_trackers',
           aliases=('trackers.use_udp.set',)),
    Method(RTorrent, 'set_connection_seed', 'set_connection_seed',
           aliases=('protocol.connection.seed.set',)),
    Method(RTorrent, 'set_xmlrpc_size_limit', 'set_xmlrpc_size_limit',
           aliases=('network.xmlrpc.size_limit.set',)),
    Method(RTorrent, 'set_xmlrpc_dialect', 'set_xmlrpc_dialect',
           aliases=('network.xmlrpc.dialect.set',)),
    Method(RTorrent, 'set_safe_sync', 'set_safe_sync',
           aliases=('pieces.sync.always_safe.set',)),
    Method(RTorrent, 'set_http_capath', 'set_http_capath',
           aliases=('network.http.capath.set',)),
    Method(RTorrent, 'set_send_buffer_size', 'set_send_buffer_size',
           aliases=('network.send_buffer.size.set',)),
    Method(RTorrent, 'set_max_downloads_div', 'set_max_downloads_div',
           aliases=('throttle.max_downloads.div.set',)),
    Method(RTorrent, 'set_name', 'set_name',
           aliases=('session.name.set',)),
    Method(RTorrent, 'set_port_open', 'set_port_open',
           aliases=('network.port_open.set',)),
    Method(RTorrent, 'set_timeout_sync', 'set_timeout_sync',
           aliases=('pieces.sync.timeout.set',)),
    Method(RTorrent, 'set_peer_exchange', 'set_peer_exchange',
           aliases=('protocol.pex.set',)),
    Method(RTorrent, 'set_ip', 'set_ip',
           docstring="""Set IP

           @param arg: ip address
           @type arg: str
           """,
           aliases=('network.local_address.set',)
           ),
    Method(RTorrent, 'set_timeout_safe_sync', 'set_timeout_safe_sync',
           aliases=('pieces.sync.timeout_safe.set',)),
    Method(RTorrent, 'set_preload_type', 'set_preload_type',
           aliases=('pieces.preload.type.set',)),
    Method(RTorrent, 'set_check_hash', 'set_check_hash',
           docstring="""Enable/Disable hash checking on finished torrents

            @param arg: True to enable, False to disable
            @type arg: bool
            """,
           boolean=True,
           aliases=('pieces.hash.on_completion.set',)
           )
]

class_methods = [
    (RTorrent, methods),
    (File, file_methods),
    (Torrent, torrent_methods),
    (Tracker, tracker_methods),
    (Peer, peer_methods)]

for c, methods in class_methods:
    # noinspection PyProtectedMember
    rpc._build_rpc_methods(c, methods)
    _build_class_methods(c)