import re
import requests
import json

try:
    import urlparse  # py2
except:
    import urllib.parse as urlparse  # py3

from django.utils.functional import cached_property

from .utils import import_by_path
from .settings import EMBED_VIDEO_BACKENDS, EMBED_VIDEO_CACHE

cache = None
if EMBED_VIDEO_CACHE:
    from django.core.cache import cache


class VideoDoesntExistException(Exception):
    pass


class UnknownBackendException(Exception):
    pass


class UnknownIdException(Exception):
    pass


def detect_backend(url):
    """
    Detect the right backend for given URL.
    """

    for backend_name in EMBED_VIDEO_BACKENDS:
        backend = import_by_path(backend_name)
        if backend.is_valid(url):
            return backend(url)

    raise UnknownBackendException


class VideoBackend(object):
    """
    Base backend, good to inherit.
    """

    re_code = None
    """
    Compiled regex (:py:func:`re.compile`) to search code in URL.

    Example: ``re.compile(r'myvideo\.com/\?code=(?P<code>\w+)')``
    """

    re_detect = None
    """
    Compilede regec (:py:func:`re.compile`) to detect, if input URL is valid
    for current backend.

    Example: ``re.compile(r'^http://myvideo\.com/.*')``
    """

    pattern_url = None
    """
    Pattern in which the code is inserted.

    Example: ``http://myvideo.com?code=%s``
    """

    pattern_thumbnail_url = None
    """
    Pattern in which the code is inserted to get thumbnail url.

    Example: ``http://static.myvideo.com/thumbs/%s``
    """

    def __init__(self, url):
        """
        First it tries to load data from cache and if it don't succeed, run
        :py:meth:`init` and then save it to cache.
        """
        if not self._restore_cache(url):
            self.init(url)
            self._set_cache(url)

    def _restore_cache(self, url):
        """
        Restore data from cache if cache is enabled.
        """
        if cache:
            data = cache.get(url)
            if data:
                self.__dict__ = data
                return True

    def _set_cache(self, url):
        """
        Set data to cache if cache is enabled.
        """
        if cache:
            cache.set(url, self.__dict__)

    def init(self, url):
        """
        Load all data (:py:meth:`get_code`, :py:meth:`get_url` etc.)
        """
        self._url = url
        self.backend = self.__class__.__name__
        self.code = self.get_code()
        self.url = self.get_url()

    @cached_property
    def thumbnail(self):
        return self.get_thumbnail_url()

    @cached_property
    def info(self):
        return self.get_info()

    @classmethod
    def is_valid(klass, url):
        """
        Class method to control if passed url is valid for current backend. By
        default it is done by :py:data:`re_detect` regex.
        """
        return True if klass.re_detect.match(url) else False

    def get_code(self):
        """
        Returns searched code from URL by :py:data:`re_code`.
        """
        match = self.re_code.search(self._url)
        if match:
            return match.group('code')

    def get_url(self):
        """
        Returns URL folded from :py:data:`pattern_url` and parsed code.
        """
        return self.pattern_url % self.code

    def get_thumbnail_url(self):
        """
        Returns thumbnail URL folded from :py:data:`pattern_thumbnail_url` and
        parsed code.
        """
        return self.pattern_thumbnail_url % self.code

    def get_embed_code(self, width, height):
        """
        Returns embed code.
        """
        return '<iframe width="%(width)d" height="%(height)d" ' \
                'src="%(url)s" frameborder="0" allowfullscreen>' \
                '</iframe>' % {
                    'url': self.url,
                    'width': width,
                    'height': height,
                }


class YoutubeBackend(VideoBackend):
    """
    Backend for YouTube URLs.
    """
    re_detect = re.compile(
        r'^(http(s)?://)?(www\.)?youtu(\.?)be(\.com)?/.*', re.I
    )

    re_code = re.compile(
        r'''youtu(\.?)be(\.com)?/  # match youtube's domains
            (embed/)?  # match the embed url syntax
            (v/)?
            (watch\?v=)?  # match the youtube page url
            (ytscreeningroom\?v=)?
            (feeds/api/videos/)?
            (user\S*[^\w\-\s])?
            (\S*[^\w\-\s])?
            (?P<code>[\w\-]{11})[a-z0-9;:@?&%=+/\$_.-]*  # match and extract
        ''',
        re.I | re.X
    )

    pattern_url = 'http://www.youtube.com/embed/%s?wmode=opaque'
    pattern_thumbnail_url = 'http://img.youtube.com/vi/%s/hqdefault.jpg'

    def get_code(self):
        code = super(YoutubeBackend, self).get_code()

        if not code:
            parse_data = urlparse.urlparse(self._url)

            try:
                code = urlparse.parse_qs(parse_data.query)['v'][0]
            except KeyError:
                raise UnknownIdException

        return code


class VimeoBackend(VideoBackend):
    """
    Backend for Vimeo URLs.
    """
    re_detect = re.compile(
        r'^(http(s)?://)?(www\.)?(player\.)?vimeo\.com/.*', re.I
    )
    re_code = re.compile(r'''vimeo\.com/(video/)?(?P<code>[0-9]+)''', re.I)
    pattern_url = 'http://player.vimeo.com/video/%s'
    pattern_info = 'http://vimeo.com/api/v2/video/%s.json'

    def init(self, url):
        self._url = url
        self.code = self.get_code()

        super(VimeoBackend, self).init(url)

    def get_info(self):
        try:
            response = requests.get(self.pattern_info % self.code)
            return json.loads(response.text)[0]
        except ValueError:
            raise VideoDoesntExistException()

    def get_thumbnail_url(self):
        return self.info.get('thumbnail_large')


class SoundCloudBackend(VideoBackend):
    """
    Backend for SoundCloud URLs.
    """
    base_url = 'http://soundcloud.com/oembed'

    re_detect = re.compile(r'^(http(s)?://(www\.)?)?soundcloud\.com/.*', re.I)
    re_code = re.compile(r'src=".*%2F(?P<code>\d+)&show_artwork.*"', re.I)
    re_url = re.compile(r'src="(?P<url>.*?)"', re.I)

    @cached_property
    def width(self):
        return self.info.get('width')

    @cached_property
    def height(self):
        return self.info.get('height')

    def get_info(self):
        params = {
            'format': 'json',
            'url': self._url,
        }
        r = requests.get(self.base_url, data=params)
        return json.loads(r.text)

    def get_thumbnail_url(self):
        return self.info.get('thumbnail_url')

    def get_url(self):
        match = self.re_url.search(self.info.get('html'))
        return match.group('url')

    def get_code(self):
        match = self.re_code.search(self.info.get('html'))
        return match.group('code')

    def get_embed_code(self, width, height):
        return super(SoundCloudBackend, self). \
                get_embed_code(width=width, height=self.height)
