/*
    SPDX-FileCopyrightText: 2009 Joris Guisson <joris.guisson@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "localwindow.h"
#include "utpprotocol.h"
#include <QtAlgorithms>
#include <util/log.h>

using namespace bt;

namespace utp
{
WindowPacket::WindowPacket(bt::Uint16 seq_nr)
    : seq_nr(seq_nr)
    , bytes_read(0)
{
}

WindowPacket::WindowPacket(bt::Uint16 seq_nr, std::unique_ptr<bt::Buffer> packet, bt::Uint32 data_off)
    : seq_nr(seq_nr)
    , packet(std::move(packet))
    , bytes_read(data_off)
{
}

WindowPacket::~WindowPacket()
{
}

bt::Uint32 WindowPacket::read(bt::Uint8 *dst, bt::Uint32 max_len)
{
    const bt::Uint32 to_read = qMin(packet->size() - bytes_read, max_len);
    if (to_read == 0) {
        return 0;
    }

    memcpy(dst, packet->get() + bytes_read, to_read);
    bytes_read += to_read;
    return to_read;
}

bool WindowPacket::fullyRead() const
{
    return bytes_read == packet->size();
}

void WindowPacket::set(std::unique_ptr<bt::Buffer> packet, bt::Uint32 data_off)
{
    this->packet = std::move(packet);
    bytes_read = data_off;
}

bool operator<(const WindowPacket &a, const WindowPacket &b)
{
    return SeqNrCmpS(a.seq_nr, b.seq_nr);
}

bool operator<(const WindowPacket &a, bt::Uint16 seq_nr)
{
    return SeqNrCmpS(a.seq_nr, seq_nr);
}

bool operator<(bt::Uint16 seq_nr, const WindowPacket &a)
{
    return SeqNrCmpS(seq_nr, a.seq_nr);
}

LocalWindow::LocalWindow(bt::Uint32 cap)
    : last_seq_nr(0)
    , first_seq_nr(0)
    , capacity(cap)
    , window_space(cap)
    , bytes_available(0)
{
}

LocalWindow::~LocalWindow()
{
}

void LocalWindow::setLastSeqNr(bt::Uint16 lsn)
{
    last_seq_nr = lsn;
    first_seq_nr = lsn;
}

bt::Uint32 LocalWindow::read(bt::Uint8 *data, bt::Uint32 max_len)
{
    bt::Uint16 off = SeqNrDiff(incoming_packets.front().seq_nr, first_seq_nr);
    bt::Uint32 written = 0;
    while (off < incoming_packets.size() && incoming_packets[off].packet && SeqNrCmpSE(incoming_packets[off].seq_nr, last_seq_nr) && written < max_len) {
        WindowPacket &pkt = incoming_packets[off];
        const bt::Uint32 ret = pkt.read(data + written, max_len - written);
        written += ret;
        window_space += ret;
        bytes_available -= ret;
        if (pkt.fullyRead()) {
            pkt.packet.reset();
            first_seq_nr++;
            off++;
        } else {
            break;
        }
    }

    // Erase is inefficient, so lets do it when we have a whole bunch to throw away
    off = SeqNrDiff(incoming_packets.front().seq_nr, first_seq_nr);
    if (off > 20) {
        incoming_packets.erase(incoming_packets.begin(), incoming_packets.begin() + off);
    }

    return written;
}

bool LocalWindow::packetReceived(const utp::Header *hdr, std::unique_ptr<bt::Buffer> packet, bt::Uint32 data_off)
{
    // Drop duplicate data packets
    // Make sure we take into account wrapping around
    if (SeqNrCmpSE(hdr->seq_nr, last_seq_nr)) {
        return true;
    }

    const bt::Uint32 data_size = packet->size() - data_off;
    if (availableSpace() < data_size) {
        Out(SYS_UTP | LOG_DEBUG) << "Not enough space in local window " << availableSpace() << " " << data_size << endl;
        return false;
    }

    if (incoming_packets.empty()) {
        first_seq_nr = last_seq_nr + 1;
        for (bt::Uint16 i = last_seq_nr + 1; SeqNrCmpS(i, hdr->seq_nr); i++) {
            incoming_packets.push_back(WindowPacket(i));
        }
        incoming_packets.push_back(WindowPacket(hdr->seq_nr, std::move(packet), data_off));
    } else if (SeqNrCmpS(incoming_packets.back().seq_nr, hdr->seq_nr)) {
        for (bt::Uint16 i = incoming_packets.back().seq_nr + 1; SeqNrCmpS(i, hdr->seq_nr); i++) {
            incoming_packets.push_back(WindowPacket(i));
        }
        incoming_packets.push_back(WindowPacket(hdr->seq_nr, std::move(packet), data_off));
    } else if (incoming_packets[SeqNrDiff(incoming_packets.front().seq_nr, hdr->seq_nr)].packet) {
        // Already got this one
        return true;
    } else {
        const bt::Uint16 off = SeqNrDiff(incoming_packets.front().seq_nr, hdr->seq_nr);
        incoming_packets[off].set(std::move(packet), data_off);
    }

    bt::Uint16 next_seq_nr = last_seq_nr + 1;
    if (hdr->seq_nr == next_seq_nr) {
        bytes_available += data_size;
        last_seq_nr = hdr->seq_nr;
        next_seq_nr = last_seq_nr + 1;

        bt::Uint16 off = SeqNrDiff(incoming_packets.front().seq_nr, next_seq_nr);
        // See if we can increase the last_seq_nr some more
        while (off < incoming_packets.size()) {
            WindowPacket &pkt = incoming_packets[off];
            if (pkt.packet) {
                bytes_available += pkt.packet->size() - pkt.bytes_read;
                last_seq_nr = next_seq_nr;
                next_seq_nr++;
                off++;
            } else {
                break;
            }
        }
    }

    /*
            Out(SYS_GEN|LOG_DEBUG) << "1 LocalWindow " << bytes_available << " " << last_seq_nr << " " << first_seq_nr << endl;
            for (int i = 0; i < incoming_packets.size(); i++)
                Out(SYS_GEN|LOG_DEBUG) << incoming_packets[i].seq_nr << ": " << (incoming_packets[i].packet ? "OK" : "") << endl;
    */
    window_space -= data_size;
    return true;
}

bt::Uint32 LocalWindow::selectiveAckBits() const
{
    if (!incoming_packets.empty() && SeqNrCmpS(last_seq_nr, incoming_packets.back().seq_nr)) {
        return SeqNrDiff(last_seq_nr, incoming_packets.back().seq_nr) - 1;
    } else {
        return 0;
    }
}

void LocalWindow::fillSelectiveAck(SelectiveAck *sack)
{
    // First turn off all bits
    memset(sack->bitmask, 0, sack->length);

    WindowPacketList::iterator itr = std::upper_bound(incoming_packets.begin(), incoming_packets.end(), last_seq_nr + 1);
    while (itr != incoming_packets.end()) {
        if (itr->packet) {
            Ack(sack, SeqNrDiff(last_seq_nr, itr->seq_nr));
        }
        ++itr;
    }
}

}
