# 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.

from ..compat import parse_qs, urlopen, urlparse
import bencode

import hashlib
import os.path
import re


class TorrentParser(object):
    def __init__(self, torrent):
        """Decode and parse given torrent

        @param torrent: handles: urls, file paths, string of torrent data
        @type torrent: str

        @raise AssertionError: Can be raised for a couple reasons:
                               - If _get_raw_torrent() couldn't figure out
                               what X{torrent} is
                               - if X{torrent} isn't a valid bencoded torrent file
        """
        self.torrent = torrent
        self._raw_torrent = None  # : testing yo
        self._torrent_decoded = None  # : what up
        self.file_type = None

        self._get_raw_torrent()
        assert self._raw_torrent is not None, 'Couldn\'t get raw_torrent.'
        if self._torrent_decoded is None:
            self._decode_torrent()
        assert isinstance(self._torrent_decoded, dict), 'Invalid torrent file.'
        self._parse_torrent()

    @property
    def raw_torrent(self):
        return self._raw_torrent

    def _is_raw(self):
        raw = False
        if isinstance(self.torrent, (str, bytes)):
            if isinstance(self._decode_torrent(self.torrent), dict):
                raw = True
            else:
                # reset self._torrent_decoded (currently equals False)
                self._torrent_decoded = None

        return raw

    def _get_raw_torrent(self):
        """Get raw torrent data by determining what self.torrent is"""
        # already raw?
        if self._is_raw():
            self.file_type = 'raw'
            self._raw_torrent = self.torrent
            return
        # local file?
        if os.path.isfile(self.torrent):
            self.file_type = 'file'
            self._raw_torrent = open(self.torrent, 'rb').read()
        # url?
        elif re.search(r'^(http|ftp)s?://', self.torrent, re.I):
            self.file_type = 'url'
            self._raw_torrent = urlopen(self.torrent).read()
        # magnet link?
        elif re.match(r'magnet:', self.torrent, re.I):
            self.file_type = 'url'
            query = {}
            try:
                parsed = urlparse(self.torrent)
                query = parse_qs(parsed.query)
            except (BaseException, Exception):
                pass
            self.title = query.get('dn', [''])[0]
            self.info_hash = query.get('xt', [''])[0].split(':')[-1]
            self.trackers = query.get('tr', [])
            self._torrent_decoded = query
            self._raw_torrent = self.torrent

    def _decode_torrent(self, raw_torrent=None):
        if raw_torrent is None:
            raw_torrent = self._raw_torrent
        self._torrent_decoded = bencode.decode(raw_torrent)
        return self._torrent_decoded

    def _calc_info_hash(self):
        self.info_hash = None
        if 'info' in self._torrent_decoded:
            info_encoded = bencode.encode(self._torrent_decoded['info'])

            if info_encoded:
                self.info_hash = hashlib.sha1(info_encoded).hexdigest().upper()

        return self.info_hash

    def _parse_torrent(self):
        for k in self._torrent_decoded:
            key = k.replace(' ', '_').lower()
            setattr(self, key, self._torrent_decoded[k])

        self._calc_info_hash()
