# SPDX-FileCopyrightText: Freya Bruhin (The Compiler) <mail@qutebrowser.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""QWebHistory serializer for QtWebEngine."""

from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl

from qutebrowser.utils import qtutils


# kHistoryStreamVersion = 3 was originally set when history serializing was
# implemented in QtWebEngine:
# https://codereview.qt-project.org/c/qt/qtwebengine/+/81529
#
# Qt 5.14 added version 4 which also serializes favicons:
# https://codereview.qt-project.org/c/qt/qtwebengine/+/279407
# However, we don't care about those, so let's keep it at 3.
HISTORY_STREAM_VERSION = 3


def _serialize_item(item, stream):
    """Serialize a single TabHistoryItem into a QDataStream.

    Args:
        item: The TabHistoryItem to write.
        stream: The QDataStream to write to.
    """
    # Thanks to Otter Browser:
    # https://github.com/OtterBrowser/otter-browser/blob/v1.0.01/src/modules/backends/web/qtwebengine/QtWebEnginePage.cpp#L260
    #
    # Relevant QtWebEngine source:
    # src/core/web_contents_adapter.cpp serializeNavigationHistory
    #
    # Sample data:
    # [TabHistoryItem(active=True,
    #                 original_url=QUrl('file:///home/freya/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
    #                 title='1.txt',
    #                 url=QUrl('file:///home/freya/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
    #                 user_data={'zoom': 1.0, 'scroll-pos': QPoint()})]

    ## toQt(entry->GetVirtualURL());
    # \x00\x00\x00Jfile:///home/freya/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
    qtutils.serialize_stream(stream, item.url)

    ## toQt(entry->GetTitle());
    # \x00\x00\x00\n\x001\x00.\x00t\x00x\x00t
    stream.writeQString(item.title)

    ## QByteArray(encodedPageState.data(), encodedPageState.size());
    # \xff\xff\xff\xff
    qtutils.serialize_stream(stream, QByteArray())

    ## static_cast<qint32>(entry->GetTransitionType());
    # chromium/ui/base/page_transition_types.h
    # \x00\x00\x00\x00
    stream.writeInt32(0)  # PAGE_TRANSITION_LINK

    ## entry->GetHasPostData();
    # \x00
    stream.writeBool(False)

    ## toQt(entry->GetReferrer().url);
    # \xff\xff\xff\xff
    qtutils.serialize_stream(stream, QUrl())

    ## static_cast<qint32>(entry->GetReferrer().policy);
    # chromium/third_party/WebKit/public/platform/WebReferrerPolicy.h
    # \x00\x00\x00\x00
    stream.writeInt32(0)  # WebReferrerPolicyAlways

    ## toQt(entry->GetOriginalRequestURL());
    # \x00\x00\x00Jfile:///home/freya/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
    qtutils.serialize_stream(stream, item.original_url)

    ## entry->GetIsOverridingUserAgent();
    # \x00
    stream.writeBool(False)

    ## static_cast<qint64>(entry->GetTimestamp().ToInternalValue());
    # \x00\x00\x00\x00^\x97$\xe7
    if item.last_visited is None:
        unix_msecs = 0
    else:
        unix_msecs = item.last_visited.toMSecsSinceEpoch()
    # 11644516800000 is the number of milliseconds from
    # 1601-01-01T00:00 (Windows NT Epoch) to 1970-01-01T00:00 (UNIX Epoch)
    nt_usecs = (unix_msecs + 11644516800000) * 1000
    stream.writeInt64(nt_usecs)
    ## entry->GetHttpStatusCode();
    # \x00\x00\x00\xc8
    stream.writeInt(200)


def serialize(items):
    """Serialize a list of TabHistoryItems to a data stream.

    Args:
        items: An iterable of TabHistoryItems.

    Return:
        A (stream, data, user_data) tuple.
            stream: The reset QDataStream.
            data: The QByteArray with the raw data.
            cur_user_data: The user data for the current item or None.

    Warning:
        If 'data' goes out of scope, reading from 'stream' will result in a
        segfault!
    """
    data = QByteArray()
    stream = QDataStream(data, QIODevice.OpenModeFlag.ReadWrite)
    cur_user_data = None

    current_idx = None

    for i, item in enumerate(items):
        if item.active:
            if current_idx is not None:
                raise ValueError("Multiple active items ({} and {}) "
                                 "found!".format(current_idx, i))
            current_idx = i
            cur_user_data = item.user_data

    if items:
        if current_idx is None:
            raise ValueError("No active item found!")
    else:
        current_idx = -1

    ### src/core/web_contents_adapter.cpp serializeNavigationHistory
    #                                          sample data:
    # kHistoryStreamVersion
    stream.writeInt(HISTORY_STREAM_VERSION)  # \x00\x00\x00\x03
    # count
    stream.writeInt(len(items))              # \x00\x00\x00\x01
    # currentIndex
    stream.writeInt(current_idx)             # \x00\x00\x00\x00

    for item in items:
        _serialize_item(item, stream)

    dev = stream.device()
    assert dev is not None
    dev.reset()
    qtutils.check_qdatastream(stream)
    return stream, data, cur_user_data
