#include "compressed_archive.h"

#define archive_error(msg) msg << ": [" << archive_errno(a) << "]" << archive_error_string(a)

CompressedArchive::CompressedArchive(const QString &filePath, QObject *parent)
    : QObject(parent), a(nullptr), num_entries(0), valid(false), idx(0), filename(filePath)
{
    if (!open_archive()) {
        qWarning() << "error opening archive:" << filename;
        return;
    }

    archive_entry *entry;
    int result;
    while ((result = archive_read_next_header(a, &entry)) == ARCHIVE_OK) {
        entries.append(archive_entry_pathname(entry));
        archive_read_data_skip(a);
        idx++;
    }

    num_entries = entries.size();

    if (result != ARCHIVE_EOF) {
        qDebug() << "finished reading archive with result of:" << result;
        qWarning() << archive_error("error reading archive");
    } else if (num_entries == 0) {
        qWarning() << "no entries read from archive.";
    } else {
        qDebug() << "# of pages in archive:" << num_entries;
        valid = true;
    }

    close_archive();
}

CompressedArchive::~CompressedArchive()
{
    close_archive();
}

bool CompressedArchive::open_archive()
{
    qDebug() << "opening archive:" << filename;
    idx = 0;

    if (a != nullptr) {
        close_archive();
    }

    a = archive_read_new();
    archive_read_support_format_all(a);
    archive_read_support_filter_all(a);

    if (archive_read_open_filename(a, filename.toStdString().c_str(), 10240) != ARCHIVE_OK) {
        qWarning() << archive_error("error opening archive");
        close_archive();
        return false;
    }

    return true;
}

void CompressedArchive::close_archive()
{
    qDebug() << "closing archive.";
    archive_read_free(a);
    a = nullptr;
}

bool CompressedArchive::archive_seek(quint32 index)
{
    if (idx == index) {
        return true;
    }

    // libarchive uses a streaming architecture so we cannot read files before our current position.
    // because of this, when we need to seek to an index before our current position,
    // we must reopen the archive.
    if (idx > index) {
        qDebug() << "asked for index [" << index << "] less than position [" << idx << "]."
                 << "reopening archive.";
        close_archive();

        if (!open_archive()) {
            return false;
        }
    }

    qDebug() << "current pos = [" << idx << "] seeking to [" << index << "]";

    archive_entry *entry;
    for (; idx < index; idx++) {
        if (archive_read_next_header(a, &entry) != ARCHIVE_OK) {
            qWarning() << archive_error("error reading header");
            return false;
        }

        if (archive_read_data_skip(a) != ARCHIVE_OK) {
            qWarning() << archive_error("error skipping data");
            return false;
        }
    }
    return true;
}

void CompressedArchive::getAllData(const QVector<quint32> &indexes,
                                   ExtractDelegate *delegate)
{
    qDebug() << "called getAllData: [" << indexes << "]";
    if (indexes.isEmpty())
        return;

    for (int i = 0; i < indexes.count(); i++) {
        if (delegate == nullptr || delegate->isCancelled())
            return;

        quint32 index = indexes[i];
        QByteArray bytes = getRawDataAtIndex(index);
        if (bytes.size() > 0) {
            delegate->fileExtracted(index, bytes);
        } else {
            qWarning() << "getAllData error at index: [" << index << "]";
            delegate->unknownError(index);
            return;
        }
    }
}

QByteArray CompressedArchive::read_entry()
{
    QByteArray bytes;
    archive_entry *entry;

    if (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        int64_t size = archive_entry_size(entry);
        bytes.resize(size);
        archive_read_data(a, bytes.data(), size);
    } else {
        qWarning() << archive_error("error reading entry");
    }

    idx++;
    return bytes;
}

QByteArray CompressedArchive::getRawDataAtIndex(int index)
{
    QByteArray bytes;
    if (archive_seek(index)) {
        bytes = read_entry();
    } else {
        qWarning() << "error reading data from archive. index:" << index;
    }
    return bytes;
}