/**************************************************************************
** Piperka Client
** Copyright (C) 2019  Kari Pahula
**
** This program 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 2 of the License, or
** (at your option) any later version.
**
** This program 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 this program; if not, write to the Free Software Foundation, Inc.,
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
**************************************************************************/

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>

#include "page.h"

static const QVector<int> subscriptionRoles = {PageModel::SubscribedRole};

Page::Page(const int &ord, const QString &name, const int subPages)
    : m_ord(ord), m_name(name), m_subPages(subPages)
{
}

PageModel::PageModel(UpdatesModel &updates, User &user, QObject *parent)
    : QAbstractListModel(parent)
    , m_updates(updates)
    , m_user(user)
    , timer(this)
{
    timer.setSingleShot(true);
    connect(&timer, &QTimer::timeout, this,
            [=]() {
        emit cursorChanged();
    });
}

int PageModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    // +1 for current marker row
    return m_pages.count()+1;
}

void PageModel::loadComic(int cid)
{
    m_cid = cid;
    //m_loading = true;
    emit loadingChanged();
    emit loadPages(cid);
}

static const QVector<int> cursorModels = {PageModel::CursorRole};

void PageModel::setCursor(int ord)
{
    if (m_allRead) {
        m_allRead = false;
        emit allReadChanged();
    }
    if (ord < 0)
        ord = 0;
    else if (ord >= m_pages.count())
        ord = m_pages.count()-1;

    QModelIndex oldIndex = createIndex(m_cursor, 0);
    QModelIndex newIndex = createIndex(ord, 0);
    m_cursor = ord;
    emit cursorChanged();
    emit dataChanged(oldIndex, oldIndex, cursorModels);
    emit dataChanged(newIndex, newIndex, cursorModels);

    bool oldHaveNext = m_haveNext;
    // Extra -1 to account for current marker
    bool atEnd = m_cursor >= rowCount() - 2;
    if (m_autoSwitch && atEnd) {
        if (!m_nextIsSwitch) {
            m_nextIsSwitch = true;
            emit nextIsSwitchChanged();
        }
    } else if (m_nextIsSwitch) {
        m_nextIsSwitch = false;
        emit nextIsSwitchChanged();
    }
    m_haveNext = !atEnd || m_nextIsSwitch;
    if (oldHaveNext != m_haveNext)
        emit haveNextChanged();

    /* Archives using fragments don't seem to change WebView contents on
     * navigation without emitting cursorChanged twice.
     */
    if (!m_initialLoad && data(cursor(), UriRole).toString().contains('#'))
        timer.start(0);
    m_initialLoad = false;
}

// Bump cursor and set bookmark when appropriate
void PageModel::setCursorNext()
{
    setCursor(m_cursor+1);
    if (m_autoBookmark && (m_sub.isNull() || m_cursor > m_sub->ord()))
        m_user.subscribeAt(m_cid, m_cursor+1);
}

bool PageModel::switchNext()
{
    if (m_autoBookmark && !m_sub.isNull())
        m_sub->setOrdMax();

    if (m_updates.rowCount() == 0) {
        m_allRead = true;
        emit allReadChanged();
        return false;
    }

    QModelIndex index = m_updates.index(0, 0);
    int cid = m_updates.data(index, ComicModel::CidRole).toInt();
    if (cid == m_cid) {
        index = m_updates.index(1, 0);
        if (index.isValid())
            cid = m_updates.data(index, ComicModel::CidRole).toInt();
        else {
            m_allRead = true;
            emit allReadChanged();
            return false;
        }
    }
    loadComic(cid);
    return true;
}

void PageModel::setSubscription(const QPointer<Subscription> &subs)
{
    if (!subs.isNull() && subs->cid() != m_cid)
        return;

    if (!m_sub.isNull()) {
        disconnect(&(*m_sub), &Subscription::move,
                   this, &PageModel::subscriptionMoved);
        disconnect(&(*m_sub), &Subscription::unsubscribing,
                   this, &PageModel::unsubscribing);
    }

    m_sub = subs;
    if (!m_sub.isNull()) {
        connect(&(*m_sub), &Subscription::move,
                this, &PageModel::subscriptionMoved);
        connect(&(*m_sub), &Subscription::unsubscribing,
                this, &PageModel::unsubscribing);
    }
    if (!m_sub.isNull()) {
        QModelIndex index = createIndex(m_sub->ord(), 0);
        emit dataChanged(index, index, subscriptionRoles);
    }
    emit subscriptionChanged();
}

QModelIndex PageModel::cursor() const
{
    return createIndex(m_cursor, 0);
}

QModelIndex PageModel::subscription() const
{
    if (m_sub.isNull())
        return QModelIndex();
    else {
        int ord = m_sub->ord();
        if (ord < 0 || ord > m_pages.count())
            return QModelIndex();
        return createIndex(ord, 0);
    }
}

bool PageModel::invalidSubscription() const
{
    return !m_sub.isNull() && !subscription().isValid();
}

QString PageModel::cursorUri() const
{
    return data(cursor(), UriRole).toString();
}

void PageModel::setAutoBookmark(const bool &bm)
{
    if (m_autoBookmark == bm)
        return;
    m_autoBookmark = bm;
    emit autoBookmarkChanged();
}

void PageModel::loadPagesComplete(const QJsonDocument &doc)
{
    m_initialLoad = true;
    beginResetModel();
    m_pages.clear();
    endResetModel();
    QJsonObject obj = doc.object();
    QJsonValue val = obj.value("url_base");
    if (val.isUndefined())
        return;

    m_url_base = val.toString();
    m_url_tail = obj.value("url_tail").toString();
    m_fixed_head = obj.value("fixed_head").toString();
    m_homepage = obj.value("homepage").toString();
    QJsonArray arr = obj.value("pages").toArray();
    int i = 0;
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    for (QJsonArray::const_iterator iter = arr.constBegin();
         iter != arr.constEnd(); ++iter, ++i) {
        QJsonArray sub = iter->toArray();
        if (!sub.empty()) {
            m_pages.append(Page(i, sub.at(0).toString(), sub.at(1).toInt()));
        }
    }
    endInsertRows();
    emit rowCountChanged();
    emit cursorChanged();
    emit pagesLoaded();
}

void PageModel::subscriptionMoved(const int &old)
{
    Subscription *subs = qobject_cast<Subscription *>(sender());
    if (subs->cid() != m_cid || old == subs->ord())
        return;

    QModelIndex oldIndex = createIndex(old, 0);
    QModelIndex newIndex = createIndex(subs->ord(), 0);
    if (abs(oldIndex.row()-newIndex.row())) {
        if (oldIndex.row() > newIndex.row())
            emit dataChanged(newIndex, oldIndex, subscriptionRoles);
        else
            emit dataChanged(oldIndex, newIndex, subscriptionRoles);
    } else {
        emit dataChanged(oldIndex, oldIndex, subscriptionRoles);
        emit dataChanged(newIndex, newIndex, subscriptionRoles);
    }
    emit subscriptionChanged();
}

void PageModel::unsubscribing()
{
    Subscription *subs = qobject_cast<Subscription *>(sender());
    if (subs->cid() != m_cid)
        return;

    QModelIndex index = createIndex(subs->ord(), 0);
    emit dataChanged(index, index, subscriptionRoles);
}

QVariant PageModel::data(const QModelIndex &index, int role) const
{
    if (role == SubscribedRole) {
        QModelIndex i = subscription();
        return i.isValid() && i.row() == index.row();
    } else if (index.row() == m_pages.count()) {
        if (role == CurrentMarkerRole)
            return true;
        else if (role == CursorRole)
            return false;
    }
    if (index.row() < 0 || index.row() >= m_pages.count())
        return QVariant();

    if (role == CursorRole)
        return index.row() == m_cursor;
    else if (role == CurrentMarkerRole)
        return false;

    const Page &page = m_pages[index.row()];
    if (role == OrdRole)
        return index.row();
    else if (role == NameRole)
        return page.name().isNull() ? QVariant() : page.name();
    else if (role == UriRole) {
        if (page.name().isNull())
            return m_fixed_head.isNull() ? m_homepage : m_fixed_head;
        QString str(m_url_base);
        str.append(page.name());
        str.append(m_url_tail);
        return str;
    }
    return QVariant();
}

QHash<int, QByteArray> PageModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[OrdRole] = "ord";
    roles[NameRole] = "name";
    roles[UriRole] = "uri";
    roles[CursorRole] = "cursor";
    roles[CurrentMarkerRole] = "currentMarker";
    roles[SubscribedRole] = "subscribed";
    return roles;
}
