# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals

from gettext import gettext as _


default_timeout = 300


def set_default_timeout(val):
    nonlocal default_timeout
    default_timeout = val


def encode_query_component(x):
    ans = encodeURIComponent(x)
    # The following exceptions are to make epubcfi() look better
    ans = ans.replace(/%2[fF]/g, '/')
    ans = ans.replace(/%40/g, '@')
    ans = ans.replace(/%5[bB]/g, '[')
    ans = ans.replace(/%5[dD]/g, ']')
    ans = ans.replace(/%5[eE]/g, '^')
    ans = ans.replace(/%3[aA]/g, ':')
    return ans


def encode_query(query, qchar):
    if not query:
        return ''
    qchar = qchar or '?'
    keys = Object.keys(query).sort()
    ans = ''
    if keys.length:
        for k in keys:
            val = query[k]
            if val is undefined or val is None or val is '':
                continue
            ans += qchar + encodeURIComponent(k) + '=' + encode_query_component(val.toString())
            qchar = '&'
    return ans


def absolute_path(path):
    if path[0] is not '/':
        slash = '' if window.location.pathname[-1] is '/' else '/'
        path = window.location.pathname + slash + path
    return path


def workaround_qt_bug(xhr, end_type, ok_code=200):
    if end_type is 'error' and xhr.status is ok_code and window.navigator.userAgent.indexOf('calibre-viewer') is 0:
        end_type = 'load'
    return end_type


def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=None, ok_code=200, progress_totals_needed=True):
    # Run an AJAX request. on_complete must be a function that accepts three
    # arguments: end_type, xhr, ev where end_type is one of 'abort', 'error',
    # 'load', 'timeout'. In case end_type is anything other than 'load' you can
    # get a descriptive error message via `xhr.error_html`.  on_progress, if
    # provided must be a function accepting three arguments: loaded, total, xhr
    # where loaded is the number of bytes received, total is the total size.
    #
    # Returns the xhr object, call xhr.send() to start the request.
    if timeout is None:
        timeout = default_timeout
    query = query or {}
    xhr = XMLHttpRequest()
    eq = encode_query(query)
    has_query = eq.length > 0
    path = absolute_path(path) + eq
    if bypass_cache:
        path += ('&' if has_query else '?') + Date().getTime()

    xhr.request_path = path
    xhr.error_html = ''

    def set_error(event, is_network_error):
        if is_network_error:
            xhr.error_html = str.format(_('Failed to communicate with "{}", network error. Is the server running and accessible?'), xhr.request_path)
        elif event is 'timeout':
            xhr.error_html = str.format(_('Failed to communicate with "{}", timed out after: {} seconds'), xhr.request_path, timeout)
        elif event is 'abort':
            xhr.error_html = str.format(_('Failed to communicate with "{}", aborted'), xhr.request_path)
        else:
            try:
                rtext = xhr.responseText or ''
            except:
                rtext = ''
            xhr.error_html = str.format(
                _('Failed to communicate with "{}", with status: [{} ({})] {}<br><br>{}'),
                xhr.request_path,
                xhr.status,
                event,
                xhr.statusText,
                rtext[:200]
            )

    def progress_callback(ev):
        if ev.lengthComputable:
            on_progress(ev.loaded, ev.total, xhr)
        elif ev.loaded:
            if not progress_totals_needed:
                on_progress(ev.loaded, undefined, xhr)
                return
            ul = xhr.getResponseHeader('Calibre-Uncompressed-Length')
            if ul:
                try:
                    ul = int(ul)
                except Exception:
                    return
                on_progress(ev.loaded, ul, xhr)

    def complete_callback(end_type, ev):
        is_network_error = ev if end_type is 'error' else False
        if xhr.status is not ok_code and end_type is 'load':
            end_type = 'error'
        end_type = workaround_qt_bug(xhr, end_type, ok_code)
        if end_type is not 'load':
            set_error(end_type, is_network_error)
        on_complete(end_type, xhr, ev)

    if on_progress:
        xhr.addEventListener('progress', progress_callback)
    xhr.addEventListener('abort', def (ev): complete_callback('abort', ev);)
    xhr.addEventListener('error', def (ev): complete_callback('error', ev);)
    xhr.addEventListener('load', def (ev): complete_callback('load', ev);)
    xhr.addEventListener('timeout', def (ev): complete_callback('timeout', ev);)
    xhr.open(method, path)
    xhr.timeout = timeout * 1000  # IE requires timeout to be set after open
    return xhr


def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=None, ok_code=200):
    # Unfortunately, browsers do not allow sending of data with HTTP GET, except
    # as query parameters, so we have to use POST
    xhr = ajax(path, on_complete, on_progress, False, 'POST', query, timeout, ok_code)
    xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
    xhr.send(JSON.stringify(data))
    return xhr


def ajax_send_file(path, file, on_complete, on_progress, timeout=None, ok_code=200):
    xhr = ajax(path, on_complete, on_progress, False, 'POST', timeout, ok_code)
    if file.type:
        xhr.overrideMimeType(file.type)
    xhr.send(file)
    return xhr


def console_print():
    ρσ_print.apply(this, arguments)  # noqa: undef
    data = ' '.join(map(str, Array.from (arguments))) + '\n'
    xhr = ajax('console-print', def (): pass;, method='POST', progress_totals_needed=False)
    xhr.send(data)

    # TODO: Implement AJAX based switch user by:
    # 1) POST to a logout URL with an incorrect username and password
    # 2) Have the server accept that incorrect username/password
    # 3) Navigate to / which should cause the browser to re-prompt for username and password
    # (only problem is login dialog will likely pre-show the wrong username) To workaround that,
    # You can consider using a dedicated ajax based login form, see http://www.peej.co.uk/articles/http-auth-with-html-forms.html)
