# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:

# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.

"""Various utilities shared between webpage/webview subclasses."""

import html

from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
from qutebrowser.mainwindow import mainwindow


class CallSuper(Exception):

    """Raised when the caller should call the superclass instead."""


def custom_headers():
    """Get the combined custom headers."""
    headers = {}

    dnt_config = config.val.content.headers.do_not_track
    if dnt_config is not None:
        dnt = b'1' if dnt_config else b'0'
        headers[b'DNT'] = dnt
        headers[b'X-Do-Not-Track'] = dnt

    conf_headers = config.val.content.headers.custom
    for header, value in conf_headers.items():
        headers[header.encode('ascii')] = value.encode('ascii')

    accept_language = config.val.content.headers.accept_language
    if accept_language is not None:
        headers[b'Accept-Language'] = accept_language.encode('ascii')

    return sorted(headers.items())


def authentication_required(url, authenticator, abort_on):
    """Ask a prompt for an authentication question."""
    realm = authenticator.realm()
    if realm:
        msg = '<b>{}</b> says:<br/>{}'.format(
            html.escape(url.toDisplayString()), html.escape(realm))
    else:
        msg = '<b>{}</b> needs authentication'.format(
            html.escape(url.toDisplayString()))
    answer = message.ask(title="Authentication required", text=msg,
                         mode=usertypes.PromptMode.user_pwd,
                         abort_on=abort_on)
    if answer is not None:
        authenticator.setUser(answer.user)
        authenticator.setPassword(answer.password)
    return answer


def javascript_confirm(url, js_msg, abort_on):
    """Display a javascript confirm prompt."""
    log.js.debug("confirm: {}".format(js_msg))
    if config.val.content.javascript.modal_dialog:
        raise CallSuper

    msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
                                          html.escape(js_msg))
    ans = message.ask('Javascript confirm', msg,
                      mode=usertypes.PromptMode.yesno,
                      abort_on=abort_on)
    return bool(ans)


def javascript_prompt(url, js_msg, default, abort_on):
    """Display a javascript prompt."""
    log.js.debug("prompt: {}".format(js_msg))
    if config.val.content.javascript.modal_dialog:
        raise CallSuper
    if not config.val.content.javascript.prompt:
        return (False, "")

    msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
                                          html.escape(js_msg))
    answer = message.ask('Javascript prompt', msg,
                         mode=usertypes.PromptMode.text,
                         default=default,
                         abort_on=abort_on)

    if answer is None:
        return (False, "")
    else:
        return (True, answer)


def javascript_alert(url, js_msg, abort_on):
    """Display a javascript alert."""
    log.js.debug("alert: {}".format(js_msg))
    if config.val.content.javascript.modal_dialog:
        raise CallSuper

    if not config.val.content.javascript.alert:
        return

    msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
                                          html.escape(js_msg))
    message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
                abort_on=abort_on)


def javascript_log_message(level, source, line, msg):
    """Display a JavaScript log message."""
    logstring = "[{}:{}] {}".format(source, line, msg)
    # Needs to line up with the values allowed for the
    # content.javascript.log setting.
    logmap = {
        'none': lambda arg: None,
        'debug': log.js.debug,
        'info': log.js.info,
        'warning': log.js.warning,
        'error': log.js.error,
    }
    logger = logmap[config.val.content.javascript.log[level.name]]
    logger(logstring)


def ignore_certificate_errors(url, errors, abort_on):
    """Display a certificate error question.

    Args:
        url: The URL the errors happened in
        errors: A list of QSslErrors or QWebEngineCertificateErrors

    Return:
        True if the error should be ignored, False otherwise.
    """
    ssl_strict = config.val.content.ssl_strict
    log.webview.debug("Certificate errors {!r}, strict {}".format(
        errors, ssl_strict))

    for error in errors:
        assert error.is_overridable(), repr(error)

    if ssl_strict == 'ask':
        err_template = jinja.environment.from_string("""
            Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
            <ul>
            {% for err in errors %}
                <li>{{err}}</li>
            {% endfor %}
            </ul>
        """.strip())
        msg = err_template.render(url=url, errors=errors)

        ignore = message.ask(title="Certificate errors - continue?", text=msg,
                             mode=usertypes.PromptMode.yesno, default=False,
                             abort_on=abort_on)
        if ignore is None:
            # prompt aborted
            ignore = False
        return ignore
    elif ssl_strict is False:
        log.webview.debug("ssl_strict is False, only warning about errors")
        for err in errors:
            # FIXME we might want to use warn here (non-fatal error)
            # https://github.com/qutebrowser/qutebrowser/issues/114
            message.error('Certificate error: {}'.format(err))
        return True
    elif ssl_strict is True:
        return False
    else:
        raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
    raise utils.Unreachable


def feature_permission(url, option, msg, yes_action, no_action, abort_on):
    """Handle a feature permission request.

    Args:
        url: The URL the request was done for.
        option: An option name to check.
        msg: A string like "show notifications"
        yes_action: A callable to call if the request was approved
        no_action: A callable to call if the request was denied
        abort_on: A list of signals which interrupt the question.

    Return:
        The Question object if a question was asked, None otherwise.
    """
    config_val = config.instance.get(option)
    if config_val == 'ask':
        if url.isValid():
            text = "Allow the website at <b>{}</b> to {}?".format(
                html.escape(url.toDisplayString()), msg)
        else:
            text = "Allow the website to {}?".format(msg)

        return message.confirm_async(
            yes_action=yes_action, no_action=no_action,
            cancel_action=no_action, abort_on=abort_on,
            title='Permission request', text=text)
    elif config_val:
        yes_action()
        return None
    else:
        no_action()
        return None


def get_tab(win_id, target):
    """Get a tab widget for the given usertypes.ClickTarget.

    Args:
        win_id: The window ID to open new tabs in
        target: A usertypes.ClickTarget
    """
    if target == usertypes.ClickTarget.tab:
        win_id = win_id
        bg_tab = False
    elif target == usertypes.ClickTarget.tab_bg:
        win_id = win_id
        bg_tab = True
    elif target == usertypes.ClickTarget.window:
        tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                    window=win_id)
        window = mainwindow.MainWindow(private=tabbed_browser.private)
        window.show()
        win_id = window.win_id
        bg_tab = False
    else:
        raise ValueError("Invalid ClickTarget {}".format(target))

    tabbed_browser = objreg.get('tabbed-browser', scope='window',
                                window=win_id)
    return tabbed_browser.tabopen(url=None, background=bg_tab)


def get_user_stylesheet():
    """Get the combined user-stylesheet."""
    css = ''
    stylesheets = config.val.content.user_stylesheets

    for filename in stylesheets:
        with open(filename, 'r', encoding='utf-8') as f:
            css += f.read()

    if not config.val.scrolling.bar:
        css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'

    return css
