#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" Abstract classes for data downloaders (readers).
"""

import abc
import typing


class DownloadError(Exception):
    """ Base exception for all exceptions generated by downloaders. """
    pass


class DownloadStringResult:
    """ Contains downloaded string
    and provides mechanism for feedback of downloaded string quality (actuality, correctness, fullness, etc.)
    to downloader (e.g. for cache and throttle management).
    """

    def __init__(self, downloaded_string: str):
        """ Initialize instance.

        :param downloaded_string: Value of downloaded string.
        """
        self._downloaded_string = downloaded_string
        self._is_correct = None

    @property
    def downloaded_string(self):
        """ Stored downloaded string.
        """
        return self._downloaded_string

    @property
    def is_correct(self) -> typing.Optional[bool]:
        """ Correctness of stored downloaded string.

        :return: ``True`` - downloaded string was parsed and verified successfully;
            ``False`` - downloaded string was parsed or verified as incorrect;
            ``None`` - correctness os downloaded string is not known yet.
        """
        return self._is_correct

    def set_correctness(self, is_correct: bool):
        """ Change downloaded string correctness.
        Subclasses may override this method for feedback management of specific downloader.

        :param is_correct: New value of correctness.
            Must be ``True`` or ``False``. ``None`` interprets as ``False``.
        """
        self._is_correct = bool(is_correct)


class Downloader(abc.ABC):
    """ Base abstract downloader.
    """
    @property
    @abc.abstractmethod
    def parameters(self) -> typing.List[typing.Tuple[str, str]]:
        """ Current value of download request parameters: ``[(param_code, param_value)]``
        """
        raise NotImplementedError

    @parameters.setter
    @abc.abstractmethod
    def parameters(self, value: typing.List[typing.Tuple[str, str]]) -> None:
        """ Set download request parameters value.

        :param value: New download request parameters value.
        """
        raise NotImplementedError

    @property
    @abc.abstractmethod
    def headers(self) -> typing.Dict[str, str]:
        """ Current value of download request headers: ``{header_code : header_value}``
        """
        raise NotImplementedError

    @headers.setter
    @abc.abstractmethod
    def headers(self, value: typing.Dict[str, str]) -> None:
        """ Set download request headers value.

        :param value: New download request headers value.
        """
        raise NotImplementedError

    @abc.abstractmethod
    def download_string(self, url: str, encoding: str = None) -> DownloadStringResult:
        """ Downloads string from `url`.

        Raise ``DownloadError`` on errors.

        :param url: URL for download
        :param encoding: Encoding of downloaded string.
            If ``None`` uses default encoding (depends of concrete downloader).
        :return: Container with downloaded string.
        """
        raise NotImplementedError
