# -*- coding: utf-8 -*-
import logging

from bs4 import BeautifulSoup, FeatureNotFound
from six.moves.xmlrpc_client import SafeTransport

from .. import __short_version__
from ..video import Episode, Movie

logger = logging.getLogger(__name__)


class TimeoutSafeTransport(SafeTransport):
    """Timeout support for ``xmlrpc.client.SafeTransport``."""
    def __init__(self, timeout, *args, **kwargs):
        SafeTransport.__init__(self, *args, **kwargs)
        self.timeout = timeout

    def make_connection(self, host):
        c = SafeTransport.make_connection(self, host)
        c.timeout = self.timeout

        return c


class ParserBeautifulSoup(BeautifulSoup):
    """A ``bs4.BeautifulSoup`` that picks the first parser available in `parsers`.

    :param markup: markup for the ``bs4.BeautifulSoup``.
    :param list parsers: parser names, in order of preference.

    """
    def __init__(self, markup, parsers, **kwargs):
        # reject features
        if set(parsers).intersection({'fast', 'permissive', 'strict', 'xml', 'html', 'html5'}):
            raise ValueError('Features not allowed, only parser names')

        # reject some kwargs
        if 'features' in kwargs:
            raise ValueError('Cannot use features kwarg')
        if 'builder' in kwargs:
            raise ValueError('Cannot use builder kwarg')

        # pick the first parser available
        for parser in parsers:
            try:
                super(ParserBeautifulSoup, self).__init__(markup, parser, **kwargs)
                return
            except FeatureNotFound:
                pass

        raise FeatureNotFound


class Provider(object):
    """Base class for providers.

    If any configuration is possible for the provider, like credentials, it must take place during instantiation.

    :raise: :class:`~subliminal.exceptions.ConfigurationError` if there is a configuration error

    """
    #: Supported set of :class:`~babelfish.language.Language`
    languages = set()

    #: Supported video types
    video_types = (Episode, Movie)

    #: Required hash, if any
    required_hash = None

    #: Subtitle class to use
    subtitle_class = None

    #: User Agent to use
    user_agent = 'Subliminal/%s' % __short_version__

    def __enter__(self):
        self.initialize()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.terminate()

    def initialize(self):
        """Initialize the provider.

        Must be called when starting to work with the provider. This is the place for network initialization
        or login operations.

        .. note::
            This is called automatically when entering the `with` statement

        """
        raise NotImplementedError

    def terminate(self):
        """Terminate the provider.

        Must be called when done with the provider. This is the place for network shutdown or logout operations.

        .. note::
            This is called automatically when exiting the `with` statement

        """
        raise NotImplementedError

    @classmethod
    def check(cls, video):
        """Check if the `video` can be processed.

        The `video` is considered invalid if not an instance of :attr:`video_types` or if the :attr:`required_hash` is
        not present in :attr:`~subliminal.video.Video.hashes` attribute of the `video`.

        :param video: the video to check.
        :type video: :class:`~subliminal.video.Video`
        :return: `True` if the `video` is valid, `False` otherwise.
        :rtype: bool

        """
        if not cls.check_types(video):
            return False
        if cls.required_hash is not None and cls.required_hash not in video.hashes:
            return False

        return True

    @classmethod
    def check_types(cls, video):
        """Check if the `video` type is supported by the provider.

        The `video` is considered invalid if not an instance of :attr:`video_types`.

        :param video: the video to check.
        :type video: :class:`~subliminal.video.Video`
        :return: `True` if the `video` is valid, `False` otherwise.
        :rtype: bool

        """
        return isinstance(video, cls.video_types)

    @classmethod
    def check_languages(cls, languages):
        """Check if the `languages` are supported by the provider.

        A subset of the supported languages is returned.

        :param languages: the languages to check.
        :type languages: set of :class:`~babelfish.language.Language`
        :return: subset of the supported languages.
        :rtype: set of :class:`~babelfish.language.Language`

        """
        return cls.languages & languages

    def query(self, *args, **kwargs):
        """Query the provider for subtitles.

        Arguments should match as much as possible the actual parameters for querying the provider

        :return: found subtitles.
        :rtype: list of :class:`~subliminal.subtitle.Subtitle`
        :raise: :class:`~subliminal.exceptions.ProviderError`

        """
        raise NotImplementedError

    def list_subtitles(self, video, languages):
        """List subtitles for the `video` with the given `languages`.

        This will call the :meth:`query` method internally. The parameters passed to the :meth:`query` method may
        vary depending on the amount of information available in the `video`.

        :param video: video to list subtitles for.
        :type video: :class:`~subliminal.video.Video`
        :param languages: languages to search for.
        :type languages: set of :class:`~babelfish.language.Language`
        :return: found subtitles.
        :rtype: list of :class:`~subliminal.subtitle.Subtitle`
        :raise: :class:`~subliminal.exceptions.ProviderError`

        """
        raise NotImplementedError

    def download_subtitle(self, subtitle):
        """Download `subtitle`'s :attr:`~subliminal.subtitle.Subtitle.content`.

        :param subtitle: subtitle to download.
        :type subtitle: :class:`~subliminal.subtitle.Subtitle`
        :raise: :class:`~subliminal.exceptions.ProviderError`

        """
        raise NotImplementedError

    def __repr__(self):
        return '<%s [%r]>' % (self.__class__.__name__, self.video_types)
