"""
Controls how a TorrentDef is downloaded (rate, where on disk, etc.).

Author(s): Arno Bakker, Egbert Bouman
"""
from __future__ import absolute_import

import copy
import logging
import os

from six import string_types
from six.moves.configparser import MissingSectionHeaderError, ParsingError

from Tribler.Core.Utilities.configparser import CallbackConfigParser
from Tribler.Core.defaults import dldefaults
from Tribler.Core.osutils import get_home_dir
from Tribler.Core.simpledefs import DLMODE_VOD

logger = logging.getLogger(__name__)


class DownloadConfigInterface(object):

    """
    (key,value) pair config of per-torrent runtime parameters,
    e.g. destdir, file-allocation policy, etc. Also options to advocate
    torrent, e.g. register in DHT, advertise via Buddycast.

    Use DownloadStartupConfig to manipulate download configs before download
    startup time. This is just a parent class.

    cf. libtorrent torrent_handle
    """

    def __init__(self, dlconfig=None, state_dir=None):
        super(DownloadConfigInterface, self).__init__()

        self.state_dir = state_dir # Dirty hack to support relative paths
        self.dlconfig = dlconfig or CallbackConfigParser()
        self.is_bootstrap_download = False

        # Dumb^WPoor man's versioning of DownloadConfig, add missing default values.
        write = False
        for section, sect_dict in dldefaults.items():
            if not self.dlconfig.has_section(section):
                self.dlconfig.add_section(section)
            for k, v in sect_dict.items():
                if not self.dlconfig.has_option(section, k):
                    write = True
                    self.dlconfig.set(section, k, v)

        if write and self.dlconfig.filename:
            self.dlconfig.write_file()

    def copy(self):
        return DownloadConfigInterface(dlconfig=self.dlconfig.copy(), state_dir=self.state_dir)

    def set_dest_dir(self, path):
        """ Sets the directory where to save this Download.
        @param path A path of a directory.
        """
        # If something is saved inside the Tribler state dir, it should use relative path
        if self.state_dir:
            base_path = self.state_dir
            if base_path == os.path.commonprefix([path, base_path]):
                path = os.path.relpath(path, base_path)
        assert isinstance(path, string_types), path
        self.dlconfig.set('download_defaults', 'saveas', path)

    def get_dest_dir(self):
        """ Gets the directory where to save this Download.
        """
        dest_dir = self.dlconfig.get('download_defaults', 'saveas')
        if not dest_dir:
            dest_dir = get_default_dest_dir()
            self.set_dest_dir(dest_dir)

        # This is required to support relative paths
        if not os.path.isabs(dest_dir):
            dest_dir = os.path.join(self.state_dir, dest_dir)

        return dest_dir

    def get_corrected_filename(self):
        """ Gets the directory name where to save this torrent
        """
        return self.dlconfig.get('download_defaults', 'correctedfilename')

    def set_corrected_filename(self, correctedfilename):
        """ Sets the directory name where to save this torrent
        @param correctedfilename name for multifile directory
        """
        self.dlconfig.set('download_defaults', 'correctedfilename', correctedfilename)

    def set_mode(self, mode):
        """ Sets the mode of this download.
        @param mode DLMODE_NORMAL/DLMODE_VOD """
        self.dlconfig.set('download_defaults', 'mode', mode)

    def get_mode(self):
        """ Returns the mode of this download.
        @return DLMODE_NORMAL/DLMODE_VOD """
        return self.dlconfig.get('download_defaults', 'mode')

    def set_hops(self, hops):
        self.dlconfig.set('download_defaults', 'hops', hops)

    def get_hops(self):
        return self.dlconfig.get('download_defaults', 'hops')

    def set_safe_seeding(self, value):
        self.dlconfig.set('download_defaults', 'safe_seeding', value)

    def set_bootstrap_download(self, value):
        self.dlconfig.set('download_defaults', 'bootstrap_download', value)

    def get_bootstrap_download(self):
        return self.dlconfig.get('download_defaults', 'bootstrap_download')

    def get_safe_seeding(self):
        return self.dlconfig.get('download_defaults', 'safe_seeding')

    def set_seeding_mode(self, value):
        self.dlconfig.set('download_defaults', 'seeding_mode', value)

    def get_seeding_mode(self):
        return self.dlconfig.get('download_defaults', 'seeding_mode')

    def set_seeding_time(self, value):
        self.dlconfig.set('download_defaults', 'seeding_time', value)

    def get_seeding_time(self):
        return self.dlconfig.get('download_defaults', 'seeding_time')

    def set_seeding_ratio(self, value):
        self.dlconfig.set('download_defaults', 'seeding_ratio', value)

    def get_seeding_ratio(self):
        return self.dlconfig.get('download_defaults', 'seeding_ratio')

    def set_credit_mining(self, value):
        self.dlconfig.set('download_defaults', 'credit_mining', value)

    def get_credit_mining(self):
        return bool(self.dlconfig.get('download_defaults', 'credit_mining'))

    def set_user_stopped(self, value):
        self.dlconfig.set('download_defaults', 'user_stopped', value)

    def get_user_stopped(self):
        return self.dlconfig.get('download_defaults', 'user_stopped')

    def set_time_added(self, value):
        self.dlconfig.set('download_defaults', 'time_added', value)

    def get_time_added(self):
        return self.dlconfig.get('download_defaults', 'time_added')

    def set_selected_files(self, files):
        """ Select which files in the torrent to download. The filenames must
        be the names as they appear in the content def, including encoding.
        Trivially, when the torrent contains a file 'sjaak.avi' the files
        parameter must be 'sjaak.avi'. When the content def is a torrent def
        and contains multiple files and is named 'filecollection', the files
        parameter must be
            os.path.join('filecollection','sjaak.avi')
        For a swift def, the files must be following the multi-file spec encoding
        (i.e., UTF-8 and /).

        @param files Can be a single filename or a list of filenames (e.g.
        ['harry.avi','sjaak.avi']). Not Unicode strings!
        """
        # TODO: can't check if files exists, don't have tdef here.... bugger
        if isinstance(files, str):  # convenience
            files = [files]

        if self.get_mode() == DLMODE_VOD and len(files) > 1:
            raise ValueError("In Video-On-Demand mode only 1 file can be selected for download")

        self.dlconfig.set('download_defaults', 'selected_files', files)

    def get_selected_files(self):
        """ Returns the list of files selected for download.
        @return A list of strings. """
        return self.dlconfig.get('download_defaults', 'selected_files')

    def set_channel_download(self, value):
        self.dlconfig.set('download_defaults', 'channel_download', value)

    def get_channel_download(self):
        return bool(self.dlconfig.get('download_defaults', 'channel_download'))

    def set_add_to_channel(self, value):
        self.dlconfig.set('download_defaults', 'add_to_channel', value)

    def get_add_to_channel(self):
        return bool(self.dlconfig.get('download_defaults', 'add_to_channel'))


class DownloadStartupConfig(DownloadConfigInterface):

    """
    (key,value) pair config of per-torrent runtime parameters,
    e.g. destdir, file-allocation policy, etc. Also options to advocate
    torrent, e.g. register in DHT, advertise via Buddycast.

    cf. libtorrent torrent_handle
    """

    def __init__(self, *args, **kwargs):
        """ Normal constructor for DownloadStartupConfig (copy constructor
        used internally) """
        is_bootstrap_download = kwargs.pop("is_bootstrap_download") if "is_bootstrap_download" in kwargs else False
        DownloadConfigInterface.__init__(self, *args, **kwargs)
        if is_bootstrap_download:
            self.set_bootstrap_download(True)
        self.is_bootstrap_download = is_bootstrap_download
    #
    # Class method
    #

    def load(filename):
        """
        Load a saved DownloadStartupConfig from disk.

        @param filename  An absolute Unicode filename
        @return DownloadStartupConfig object
        """
        # Class method, no locking required
        dlconfig = CallbackConfigParser()
        try:
            dlconfig.read_file(filename)
        except (ParsingError, MissingSectionHeaderError, IOError):
            logger.error("Failed to open download config file: %s", filename)
            raise

        return DownloadStartupConfig(dlconfig)

    load = staticmethod(load)

    def save(self, filename):
        """ Save the DownloadStartupConfig to disk.
        @param filename  An absolute Unicode filename
        """
        # Called by any thread
        self.dlconfig.write_file(filename)

    def copy(self):
        return DownloadStartupConfig(self.dlconfig.copy())


class DefaultDownloadStartupConfig(DownloadStartupConfig):
    """
    This class is used to quickly get information about the default download startup config.
    This is used for instance when adding a new torrent to the downloads. In this case, the default
    download settings should be accessed and displayed to the user.
    """
    __single = None

    def __init__(self, *args, **kwargs):

        if DefaultDownloadStartupConfig.__single:
            raise RuntimeError("DefaultDownloadStartupConfig is singleton")
        DefaultDownloadStartupConfig.__single = self

        DownloadStartupConfig.__init__(self, *args, **kwargs)

        self._logger = logging.getLogger(self.__class__.__name__)

    @staticmethod
    def getInstance(*args, **kw):
        if DefaultDownloadStartupConfig.__single is None:
            DefaultDownloadStartupConfig(*args, **kw)
        return DefaultDownloadStartupConfig.__single

    @staticmethod
    def delInstance(*args, **kw):
        DefaultDownloadStartupConfig.__single = None

    @staticmethod
    def load(filename):
        dlconfig = CallbackConfigParser()
        dlconfig.read_file(filename)
        return DefaultDownloadStartupConfig(dlconfig)

    def copy(self):
        config = CallbackConfigParser()
        config._sections = {'download_defaults': copy.deepcopy(self.dlconfig._sections['download_defaults'])}
        return DownloadStartupConfig(config)


def get_default_dest_dir():
    """
    Returns the default dir to save content to.

    If Downloads/ exists: Downloads/TriblerDownloads
    else: Home/TriblerDownloads
    """
    download_dir = u"TriblerDownloads"

    # TODO: Is this here so the unit tests work?
    if os.path.isdir(download_dir):
        return os.path.abspath(download_dir)

    downloads_dir = os.path.join(get_home_dir(), u"Downloads")
    if os.path.isdir(downloads_dir):
        return os.path.join(downloads_dir, download_dir)
    else:
        return os.path.join(get_home_dir(), download_dir)
