/*
 * sm.cpp - XMPP Stream Management protocol
 * Copyright (C) 2016  Aleksey Andreev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#ifdef IRIS_SM_DEBUG
#include <QDebug>
#endif


#include "sm.h"

using namespace XMPP;

SMState::SMState()
{
    enabled = false;
    resumption_id.clear();
    resumption_location.host.clear();
    resumption_location.port = 0;
    resetCounters();
}

void SMState::resetCounters()
{
    received_count = 0;
    server_last_handled = 0;
    send_queue.clear();
}


StreamManagement::StreamManagement(QObject *parent)
    : QObject(parent)
    , sm_started(false)
    , sm_resumed(false)
    , sm_stanzas_notify(0)
    , sm_resend_pos(0)
{
}

void StreamManagement::reset()
{
    sm_started = false;
    sm_resumed = false;
    sm_stanzas_notify = 0;
    sm_resend_pos = 0;
    sm_timeout_data.elapsed_timer = QElapsedTimer();
    sm_timeout_data.waiting_answer = false;
}

void StreamManagement::start(const QString &resumption_id)
{
    reset();
    state_.resetCounters();
    state_.resumption_id = resumption_id;
    sm_started = true;
    sm_timeout_data.elapsed_timer.start();
}

void StreamManagement::resume(quint32 last_handled)
{
    sm_resumed = true;
    sm_resend_pos = 0;
    processAcknowledgement(last_handled);
    sm_timeout_data.waiting_answer = false;
    sm_timeout_data.elapsed_timer.start();
}

void StreamManagement::setLocation(const QString &host, int port)
{
    state_.resumption_location.host = host;
    state_.resumption_location.port = port;
}

int StreamManagement::lastAckElapsed() const
{
    if (!sm_timeout_data.elapsed_timer.isValid())
        return 0;

    int msecs = sm_timeout_data.elapsed_timer.elapsed();
    int secs = msecs / 1000;
    if (msecs % 1000 != 0)
        ++secs;
    return secs;
}

int StreamManagement::takeAckedCount()
{
    int cnt = sm_stanzas_notify;
    sm_stanzas_notify = 0;
    return cnt;
}

void StreamManagement::countInputRawData(int bytes)
{
    if (sm_timeout_data.waiting_answer) {
        if (bytes > 2) // '\r' and '\n'
            sm_timeout_data.elapsed_timer.start();
    }
}

QDomElement StreamManagement::getUnacknowledgedStanza()
{
    if (sm_resend_pos < state_.send_queue.size())
        return state_.send_queue.at(sm_resend_pos++);
    return QDomElement();
}

int StreamManagement::addUnacknowledgedStanza(const QDomElement &e)
{
    state_.send_queue.enqueue(e);
    int len = state_.send_queue.length();
#ifdef IRIS_SM_DEBUG
    qDebug() << "Stream Management: [INF] Send queue length is changed: " << len;
#endif
    return len;
}

void StreamManagement::processAcknowledgement(quint32 last_handled)
{
    sm_timeout_data.waiting_answer = false;
    sm_timeout_data.elapsed_timer.start();
#ifdef IRIS_SM_DEBUG
    bool f = false;
#endif
    while (!state_.send_queue.isEmpty() && state_.server_last_handled != last_handled) {
        state_.send_queue.dequeue();
        ++state_.server_last_handled;
        ++sm_stanzas_notify;
#ifdef IRIS_SM_DEBUG
        f = true;
#endif
    }
#ifdef IRIS_SM_DEBUG
    if (f) {
        qDebug() << "Stream Management: [INF] Send queue length is changed: " << state_.send_queue.length();
        if (state_.send_queue.isEmpty() && last_handled != state_.server_last_handled)
            qDebug() << "Stream Management: [ERR] Send queue is empty but last_handled != server_last_handled " << last_handled << state_.server_last_handled;
    }
#endif
}

void StreamManagement::markStanzaHandled()
{
    ++state_.received_count;
#ifdef IRIS_SM_DEBUG
    qDebug() << "Stream Management: [INF] current received id: " << state_.received_count;
#endif
}

QDomElement StreamManagement::generateRequestStanza(QDomDocument &doc)
{
    if (!sm_timeout_data.waiting_answer) {
#ifdef IRIS_SM_DEBUG
        qDebug() << "Stream Management: [?->] Sending request of acknowledgment to server";
#endif
        sm_timeout_data.waiting_answer = true;
        sm_timeout_data.elapsed_timer.start();
        return doc.createElementNS(NS_STREAM_MANAGEMENT, "r");
    }
    return QDomElement();
}

QDomElement StreamManagement::makeResponseStanza(QDomDocument &doc)
{
#ifdef IRIS_SM_DEBUG
    qDebug() << "Stream Management: [-->] Sending acknowledgment with h =" << state_.received_count;
#endif
    QDomElement e = doc.createElementNS(NS_STREAM_MANAGEMENT, "a");
    e.setAttribute("h", state_.received_count);
    return e;
}
