/*
    SPDX-FileCopyrightText: 2007 Tobias Koenig <tokoe@kde.org>

    Based on code written by Bill Janssen 2002

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

#include "qunpluck.h"

#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QFile>
#include <QFont>
#include <QHash>
#include <QLabel>
#include <QStack>
#include <QString>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QTextDocument>
#include <QTextFrame>
#include <QUrl>

#include <core/action.h>
#include <core/document.h>

#include "image.h"

#define GET_FUNCTION_CODE_TYPE(x) (((x) >> 3) & 0x1F)
#define GET_FUNCTION_CODE_DATALEN(x) ((x)&0x7)

#define CELLS(row, col) cells[row * cols + col]

#define READ_BIGENDIAN_SHORT(p) (((p)[0] << 8) | ((p)[1]))
#define READ_BIGENDIAN_LONG(p) (((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | ((p)[3]))
/*
static void LinkRecords
    (
    char*  dir
    )
{
    RecordNode*  ptr;
    char*        realfilename;
    char*        linkname;

    realfilename = (char*)malloc (strlen (dir) + 20);
    linkname = (char*)malloc (strlen (dir) + 20);

    for (ptr = records; ptr != NULL; ptr = ptr->next) {
        if (ptr->page_id != ptr->index) {
            sprintf (realfilename, "%s/r%d.html", dir, ptr->page_id);
            sprintf (linkname, "%s/r%d.html", dir, ptr->index);
            link (realfilename, linkname);
        }
    }

    free (realfilename);
    free (linkname);
}
*/

class Context
{
public:
    int recordId;
    QTextDocument *document;
    QTextCursor *cursor;
    QStack<QTextCharFormat> stack;
    QList<int> images;

    QString linkUrl;
    int linkStart;
    int linkPage;
};

class RecordNode
{
public:
    int index;
    int page_id;
    bool done;
};

static Okular::DocumentViewport calculateViewport(QTextDocument *document, const QTextBlock &block)
{
    if (!block.isValid()) {
        return Okular::DocumentViewport();
    }

    const QRectF rect = document->documentLayout()->blockBoundingRect(block);
    const QSizeF size = document->size();

    int page = qRound(rect.y()) / qRound(size.height());

    Okular::DocumentViewport viewport(page);
    viewport.rePos.normalizedX = (double)rect.x() / (double)size.width();
    viewport.rePos.normalizedY = (double)rect.y() / (double)size.height();
    viewport.rePos.enabled = true;
    viewport.rePos.pos = Okular::DocumentViewport::Center;

    return viewport;
}

QUnpluck::QUnpluck()
    : mDocument(nullptr)
{
}

QUnpluck::~QUnpluck()
{
    mLinks.clear();
    mNamedTargets.clear();
    mPages.clear();
}

bool QUnpluck::open(const QString &fileName)
{
    mLinks.clear();
    mNamedTargets.clear();
    mPages.clear();

    mDocument = plkr_OpenDBFile(QFile::encodeName(fileName).data());
    if (!mDocument) {
        mErrorString = QObject::tr("Unable to open document");
        return false;
    }

    //     bool status = true;

    mInfo.insert(QStringLiteral("name"), QString::fromLocal8Bit(plkr_GetName(mDocument)));
    mInfo.insert(QStringLiteral("title"), QString::fromLocal8Bit(plkr_GetTitle(mDocument)));
    mInfo.insert(QStringLiteral("author"), QString::fromLocal8Bit(plkr_GetAuthor(mDocument)));
    mInfo.insert(QStringLiteral("time"), QDateTime::fromSecsSinceEpoch(plkr_GetPublicationTime(mDocument)).toString());

    AddRecord(plkr_GetHomeRecordID(mDocument));

    int number = GetNextRecordNumber();
    while (number > 0) {
        /*status = */ TranscribeRecord(number);
        number = GetNextRecordNumber();
    }

    // Iterate over all records again to add those which aren't linked directly
    for (int i = 1; i < plkr_GetRecordCount(mDocument); ++i) {
        AddRecord(plkr_GetUidForIndex(mDocument, i));
    }

    number = GetNextRecordNumber();
    while (number > 0) {
        /*status = */ TranscribeRecord(number);
        number = GetNextRecordNumber();
    }

    for (int i = 0; i < mRecords.count(); ++i) {
        delete mRecords[i];
    }

    mRecords.clear();

    plkr_CloseDoc(mDocument);

    /**
     * Calculate hash map
     */
    QHash<int, int> pageHash;
    for (int i = 0; i < mContext.count(); ++i) {
        pageHash.insert(mContext[i]->recordId, i);
    }

    /**
     * Convert ids
     */
    for (int i = 0; i < mContext.count(); ++i) {
        Context *context = mContext[i];
        for (int j = 0; j < context->images.count(); ++j) {
            int imgNumber = context->images[j];
            context->document->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("%1.jpg").arg(imgNumber)), mImages[imgNumber]);
        }

        mPages.append(context->document);
    }
    qDeleteAll(mContext);
    mContext.clear();

    // convert record_id into page
    for (int i = 0; i < mLinks.count(); ++i) {
        mLinks[i].page = pageHash[mLinks[i].page];
        if (mLinks[i].url.startsWith(QLatin1String("page:"))) {
            int page = mLinks[i].url.midRef(5).toInt();
            Okular::DocumentViewport viewport(pageHash[page]);
            viewport.rePos.normalizedX = 0;
            viewport.rePos.normalizedY = 0;
            viewport.rePos.enabled = true;
            viewport.rePos.pos = Okular::DocumentViewport::TopLeft;
            mLinks[i].link = new Okular::GotoAction(QString(), viewport);
        } else if (mLinks[i].url.startsWith(QLatin1String("para:"))) {
            QPair<int, QTextBlock> data = mNamedTargets[mLinks[i].url];

            QTextDocument *document = mPages[mLinks[i].page];

            Okular::DocumentViewport viewport = calculateViewport(document, data.second);

            mLinks[i].link = new Okular::GotoAction(QString(), viewport);
        } else {
            mLinks[i].link = new Okular::BrowseAction(QUrl(mLinks[i].url));
        }
    }

    return true;
}

int QUnpluck::GetNextRecordNumber()
{
    int index = 0;

    for (int pos = 0; pos < mRecords.count(); ++pos) {
        if (!mRecords[pos]->done) {
            index = mRecords[pos]->index;
            break;
        }
    }

    return index;
}

int QUnpluck::GetPageID(int index)
{
    for (int pos = 0; pos < mRecords.count(); ++pos) {
        if (mRecords[pos]->index == index) {
            return mRecords[pos]->page_id;
        }
    }

    return 0;
}

void QUnpluck::AddRecord(int index)
{
    for (int pos = 0; pos < mRecords.count(); ++pos) {
        if (mRecords[pos]->index == index) {
            return;
        }
    }

    RecordNode *node = new RecordNode;
    node->done = false;
    node->index = index;
    node->page_id = index;

    mRecords.append(node);
}

void QUnpluck::MarkRecordDone(int index)
{
    for (int pos = 0; pos < mRecords.count(); ++pos) {
        if (mRecords[pos]->index == index) {
            mRecords[pos]->done = true;
            return;
        }
    }

    AddRecord(index);
    MarkRecordDone(index);
}

void QUnpluck::SetPageID(int index, int page_id)
{
    for (int pos = 0; pos < mRecords.count(); ++pos) {
        if (mRecords[pos]->index == index) {
            mRecords[pos]->page_id = page_id;
            return;
        }
    }

    AddRecord(index);
    SetPageID(index, page_id);
}

QString QUnpluck::MailtoURLFromBytes(unsigned char *record_data)
{
    unsigned char *bytes = record_data + 8;

    int to_offset = (bytes[0] << 8) + bytes[1];
    int cc_offset = (bytes[2] << 8) + bytes[3];
    int subject_offset = (bytes[4] << 8) + bytes[5];
    int body_offset = (bytes[6] << 8) + bytes[7];

    QString url(QStringLiteral("mailto:"));
    if (to_offset != 0) {
        url += QString::fromLatin1((char *)(bytes + to_offset));
    }

    if ((cc_offset != 0) || (subject_offset != 0) || (body_offset != 0)) {
        url += QLatin1String("?");
    }

    if (cc_offset != 0) {
        url += QLatin1String("cc=") + QString::fromLatin1((char *)(bytes + cc_offset));
    }

    if (subject_offset != 0) {
        url += QLatin1String("subject=") + QString::fromLatin1((char *)(bytes + subject_offset));
    }

    if (body_offset != 0) {
        url += QLatin1String("body=") + QString::fromLatin1((char *)(bytes + body_offset));
    }

    return url;
}

QImage QUnpluck::TranscribeImageRecord(unsigned char *bytes)
{
    QImage image;

    TranscribePalmImageToJPEG(bytes + 8, image);

    return image;
}

void QUnpluck::DoStyle(Context *context, int style, bool start)
{
    if (start) {
        QTextCharFormat format(context->cursor->charFormat());
        context->stack.push(format);

        int pointSize = qRound(format.fontPointSize());
        switch (style) {
        case 1:
            format.setFontWeight(QFont::Bold);
            pointSize += 3;
            break;
        case 2:
            format.setFontWeight(QFont::Bold);
            pointSize += 2;
            break;
        case 3:
            format.setFontWeight(QFont::Bold);
            pointSize += 1;
            break;
        case 4:
            format.setFontWeight(QFont::Bold);
            break;
        case 5:
            format.setFontWeight(QFont::Bold);
            pointSize += -1;
            break;
        case 6:
            format.setFontWeight(QFont::Bold);
            pointSize += -2;
            break;
        case 7:
            format.setFontWeight(QFont::Bold);
            break;
        case 8:
            format.setFontFamily(QStringLiteral("Courier New,courier"));
            break;
        }
        format.setFontPointSize(qMax(pointSize, 1));
        context->cursor->setCharFormat(format);
    } else {
        if (!context->stack.isEmpty()) {
            context->cursor->setCharFormat(context->stack.pop());
        }
    }
}

void QUnpluck::ParseText(plkr_Document *doc, unsigned char *ptr, int text_len, int *font, int *style, Context *context)
{
    unsigned char *end;
    int fctype;
    int fclen;

    end = ptr + text_len;
    while (ptr < end) {
        if (ptr[0]) {
            context->cursor->insertText(QString::fromLocal8Bit((char *)ptr));
            ptr += strlen((char *)ptr);
        } else {
            fctype = GET_FUNCTION_CODE_TYPE(ptr[1]);
            fclen = 2 + GET_FUNCTION_CODE_DATALEN(ptr[1]);
            switch (fctype) {
            case PLKR_TFC_LINK:
                switch (fclen) {
                case 4: /* ANCHOR_BEGIN */
                {
                    int record_id = (ptr[2] << 8) + ptr[3];

                    /** TODO:
                    plkr_DataRecordType   type =
                        (plkr_DataRecordType)plkr_GetRecordType (doc, record_id);
                    if (type ==
                        PLKR_DRTYPE_IMAGE
                        || type ==
                        PLKR_DRTYPE_IMAGE_COMPRESSED)
                        output += QString( "<A HREF=\"r%1.jpg\">" ).arg(record_id);
                    else
                        output += QString( "<A HREF=\"r%1.html\">" ).arg(record_id);
                        */
                    AddRecord(record_id);
                } break;
                case 2: /* ANCHOR_END */
                    // TODO:  output += QString( "</A>" );
                    break;
                }
                ptr += fclen;
                break;
            case PLKR_TFC_FONT:
                DoStyle(context, *style, false);
                *style = ptr[2];
                DoStyle(context, *style, true);
                ptr += fclen;
                break;
            case PLKR_TFC_NEWLINE: {
                // TODO: remove the setCharFormat when Qt is fixed
                QTextCharFormat format(context->cursor->charFormat());
                context->cursor->insertText(QStringLiteral("\n"));
                context->cursor->setCharFormat(format);
                ptr += fclen;
                break;
            }
            case PLKR_TFC_BITALIC: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontItalic(true);
                context->cursor->setCharFormat(format);
                ptr += fclen;
                break;
            }
            case PLKR_TFC_EITALIC: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontItalic(false);
                context->cursor->setCharFormat(format);
                ptr += fclen;
                break;
            }
            case PLKR_TFC_COLOR:
                if (*font) {
                    (*font)--;
                    if (!context->stack.isEmpty()) {
                        context->cursor->setCharFormat(context->stack.pop());
                    }
                }

                {
                    QTextCharFormat format(context->cursor->charFormat());
                    context->stack.push(format);

                    format.setForeground(QColor((ptr[2] << 16), (ptr[3] << 8), ptr[4]));
                    context->cursor->setCharFormat(format);
                }

                (*font)++;
                ptr += fclen;
                break;
            case PLKR_TFC_BULINE: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontUnderline(true);
                context->cursor->setCharFormat(format);
                ptr += fclen;
            } break;
            case PLKR_TFC_EULINE: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontUnderline(false);
                context->cursor->setCharFormat(format);
                ptr += fclen;
            } break;
            case PLKR_TFC_BSTRIKE: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontStrikeOut(true);
                context->cursor->setCharFormat(format);
                ptr += fclen;
                break;
            }
            case PLKR_TFC_ESTRIKE: {
                QTextCharFormat format(context->cursor->charFormat());
                format.setFontStrikeOut(false);
                context->cursor->setCharFormat(format);
                ptr += fclen;
                break;
            }
            case PLKR_TFC_TABLE:
                if (fclen == 4) {
                    int record_id, datalen;
                    plkr_DataRecordType type = (plkr_DataRecordType)0;
                    unsigned char *bytes = nullptr;

                    record_id = (ptr[2] << 8) + ptr[3];
                    bytes = plkr_GetRecordBytes(doc, record_id, &datalen, &type);
                    TranscribeTableRecord(doc, context, bytes);
                }
                ptr += fclen;
                break;
            default:
                ptr += fclen;
            }
        }
    }
}

bool QUnpluck::TranscribeTableRecord(plkr_Document *doc, Context *context, unsigned char *bytes)
{
    unsigned char *ptr = &bytes[24];
    unsigned char *end;
    //    char*           align_names[] = { "left", "right", "center" };
    //     bool            in_row = false;
    //     int             cols;
    int size;
    //     int             rows;
    //     int             border;
    int record_id;
    //     int             align;
    int text_len;
    //     int             colspan;
    //     int             rowspan;
    int font = 0;
    int style = 0;
    int fctype;
    int fclen;
    //     long            border_color;
    //     long            link_color;

    size = (bytes[8] << 8) + bytes[9];
    //     cols = (bytes[10] << 8) + bytes[11];
    //     rows = (bytes[12] << 8) + bytes[13];
    //     border = bytes[15];
    //     border_color = (bytes[17] << 16) + (bytes[18] << 8) + (bytes[19] << 8);
    //     link_color = (bytes[21] << 16) + (bytes[22] << 8) + (bytes[23] << 8);

    end = ptr + size - 1;
    /**
        output += QString( "<TABLE border=%1 bordercolor=\"#%2\" "
                 "linkcolor=\"#%3\">\n" ).arg(border, border_color, link_color);
    */
    while (ptr < end) {
        if (ptr[0] == '\0') {
            fctype = GET_FUNCTION_CODE_TYPE(ptr[1]);
            fclen = 2 + GET_FUNCTION_CODE_DATALEN(ptr[1]);
            switch (fctype) {
            case PLKR_TFC_TABLE:
                switch (fclen) {
                case 2: /* NEW_ROW */
                    /*
                        if (in_row)
                            output += QString( "</TR>\n" );
                        output += QString( "<TR>\n" );
                        in_row = true;
                        */
                    ptr += fclen;
                    break;
                case 9: /* NEW_CELL */
                    //                             align = ptr[2];
                    //                             colspan = ptr[5];
                    //                             rowspan = ptr[6];
                    /**
                    output += QString( "<TD align=\"%1\" colspan=%2 "
                             "rowspan=%3 bordercolor=\"#\">" ).arg(
                             align_names[align], colspan, rowspan );
//                                     border_color);
*/
                    if ((record_id = READ_BIGENDIAN_SHORT(&ptr[3]))) {
                        QTextCharFormat format = context->cursor->charFormat();
                        context->cursor->insertImage(QStringLiteral("%1.jpg").arg(record_id));
                        context->cursor->setCharFormat(format);
                        context->images.append(record_id);
                        AddRecord(record_id);
                    }
                    DoStyle(context, style, true);
                    text_len = READ_BIGENDIAN_SHORT(&ptr[7]);
                    ptr += fclen;
                    ParseText(doc, ptr, text_len, &font, &style, context);
                    ptr += text_len;
                    DoStyle(context, style, false);
                    // output += QString( "</TD>\n" );
                    break;
                default:
                    ptr += fclen;
                }
                break;
            default:
                ptr += fclen;
            }
        } else {
            // output += QString( "</TABLE>\n" );
            return false;
        }
    }

    //    output += QString( "</TABLE>\n" );
    return true;
}

typedef struct {
    int size;
    int attributes;
} ParagraphInfo;

static std::vector<ParagraphInfo> ParseParagraphInfo(unsigned char *bytes)
{
    std::vector<ParagraphInfo> paragraph_info;

    int n = (bytes[2] << 8) + bytes[3];
    paragraph_info.reserve(n);
    for (int j = 0; j < n; j++) {
        const int size = (bytes[8 + (j * 4) + 0] << 8) + bytes[8 + (j * 4) + 1];
        const int attributes = (bytes[8 + (j * 4) + 2] << 8) + bytes[8 + (j * 4) + 3];
        paragraph_info.emplace_back(ParagraphInfo {size, attributes});
    }
    return paragraph_info;
}

bool QUnpluck::TranscribeTextRecord(plkr_Document *doc, int id, Context *context, unsigned char *bytes, plkr_DataRecordType type)
{
    unsigned char *ptr;
    unsigned char *run;
    unsigned char *para_start;
    unsigned char *data;
    unsigned char *start;
    bool first_record_of_page = true;
    bool current_link;
    bool current_italic;
    bool current_struckthrough;
    bool current_underline;
    int fctype;
    int fclen;
    size_t para_index;
    int para_len;
    int textlen;
    int data_len;
    int current_font;
    int record_index;
    //     int             current_alignment;
    //     int             current_left_margin;
    //     int             current_right_margin;
    //     long            current_color;

    record_index = id;

    std::vector<ParagraphInfo> paragraphs = ParseParagraphInfo(bytes);
    start = bytes + 8 + ((bytes[2] << 8) + bytes[3]) * 4;

    for (para_index = 0, ptr = start, run = start; para_index < paragraphs.size(); para_index++) {
        para_len = paragraphs[para_index].size;

        /* If the paragraph is the last in the record, and it consists
           of a link to the next record in the logical page, we trim off
           the paragraph and instead insert the whole page */

        if (((para_index + 1) == paragraphs.size()) && (para_len == (sizeof("Click here for the next part") + 5)) && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) &&
            (strcmp((char *)(ptr + 4), "Click here for the next part") == 0)) {
            record_index = (ptr[2] << 8) + ptr[3];
            if ((data = plkr_GetRecordBytes(doc, record_index, &data_len, &type)) == nullptr) {
                //                ShowWarning ("Can't open record %d!", record_index);
                return false;
            } else if (!(type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT)) {
                //                ShowWarning ("Bad record type %d in record linked from end of record %d", type, id);
                return false;
            }
            first_record_of_page = false;
            para_index = 0;
            ptr = data + 8 + ((data[2] << 8) + data[3]) * 4;
            run = ptr;
            paragraphs = ParseParagraphInfo(data);
            para_len = paragraphs[para_index].size;
            MarkRecordDone(record_index);
            SetPageID(record_index, id);
        }

        if ((para_index == 0) && !first_record_of_page && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) && (strcmp((char *)(ptr + 4), "Click here for the previous part") == 0)) {
            /* throw away this inserted paragraph */
            ptr += para_len;
            run = ptr;
            continue;
        }

        QTextCharFormat charFormat(context->cursor->charFormat());
        QTextBlockFormat blockFormat(context->cursor->blockFormat());
        blockFormat.setAlignment(Qt::AlignLeft);
        context->cursor->insertBlock(blockFormat);
        context->cursor->setCharFormat(charFormat);

        mNamedTargets.insert(QStringLiteral("para:%1-%2").arg(record_index).arg(para_index), QPair<int, QTextBlock>(GetPageID(record_index), context->cursor->block()));

        current_link = false;

        /* at the beginning of a paragraph, we start with a clean graphics context */
        current_font = 0;
        //         current_alignment = 0;
        //         current_color = 0;
        current_italic = false;
        current_underline = false;
        current_struckthrough = false;
        //         current_left_margin = 0;
        //         current_right_margin = 0;

        for (para_start = ptr, textlen = 0; (ptr - para_start) < para_len;) {
            if (*ptr == 0) {
                /* function code */

                if ((ptr - run) > 0) {
                    /* write out any pending text */
                    context->cursor->insertText(QString::fromLatin1((char *)run, ptr - run));
                    textlen += (ptr - run);
                }

                ptr++;
                fctype = GET_FUNCTION_CODE_TYPE(*ptr);
                fclen = GET_FUNCTION_CODE_DATALEN(*ptr);
                ptr++;

                if (fctype == PLKR_TFC_NEWLINE) {
                    // TODO: remove the setCharFormat when Qt is fixed
                    QTextCharFormat format(context->cursor->charFormat());
                    context->cursor->insertText(QStringLiteral("\n"));
                    context->cursor->setCharFormat(format);
                } else if (fctype == PLKR_TFC_LINK) {
                    int record_id, real_record_id, datalen;
                    plkr_DataRecordType type = (plkr_DataRecordType)0;
                    unsigned char *bytes = nullptr;
                    char *url = nullptr;

                    if (fclen == 0) {
                        if (current_link) {
                            if (!context->stack.isEmpty()) {
                                context->cursor->setCharFormat(context->stack.pop());
                            }

                            if (!context->linkUrl.isEmpty()) {
                                Link link;
                                link.url = context->linkUrl;
                                link.start = context->linkStart;
                                link.end = context->cursor->position();
                                link.page = GetPageID(id);
                                mLinks.append(link);
                            }
                        }
                        current_link = false;
                    } else {
                        record_id = (ptr[0] << 8) + ptr[1];
                        bytes = plkr_GetRecordBytes(doc, record_id, &datalen, &type);
                        if (!bytes) {
                            url = plkr_GetRecordURL(doc, record_id);
                        }
                        if (bytes && (type == PLKR_DRTYPE_MAILTO)) {
                            context->linkUrl = MailtoURLFromBytes(bytes);
                            context->linkStart = context->cursor->position();

                            QTextCharFormat format(context->cursor->charFormat());
                            context->stack.push(format);
                            format.setForeground(Qt::blue);
                            format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
                            context->cursor->setCharFormat(format);
                            current_link = true;
                        } else if (!bytes && url) {
                            context->linkUrl = QString::fromLatin1(url);
                            context->linkStart = context->cursor->position();

                            QTextCharFormat format(context->cursor->charFormat());
                            context->stack.push(format);
                            format.setForeground(Qt::blue);
                            format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
                            context->cursor->setCharFormat(format);
                            current_link = true;
                        } else if (bytes && (fclen == 2)) {
                            AddRecord(record_id);
                            real_record_id = GetPageID(record_id);
                            if (type == PLKR_DRTYPE_IMAGE || type == PLKR_DRTYPE_IMAGE_COMPRESSED) {
                                context->linkUrl = QStringLiteral("%1.jpg").arg(record_id);
                                context->linkStart = context->cursor->position();
                            } else {
                                context->linkUrl = QStringLiteral("page:%1").arg(real_record_id);
                                context->linkStart = context->cursor->position();
                            }
                            QTextCharFormat format(context->cursor->charFormat());
                            context->stack.push(format);
                            format.setForeground(Qt::blue);
                            format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
                            context->cursor->setCharFormat(format);
                            current_link = true;
                        } else if (bytes && (fclen == 4)) {
                            AddRecord(record_id);

                            context->linkUrl = QStringLiteral("para:%1-%2").arg(record_id).arg((ptr[2] << 8) + ptr[3]);
                            context->linkStart = context->cursor->position();

                            QTextCharFormat format(context->cursor->charFormat());
                            context->stack.push(format);
                            format.setForeground(Qt::blue);
                            format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
                            context->cursor->setCharFormat(format);
                            current_link = true;
                        } else {
                            //    ShowWarning("odd link found:  record_id=%d, bytes=0x%p, type=%d, url=%s", record_id, bytes, type, (url ? url : "0x0"));
                        }
                    }

                } else if (fctype == PLKR_TFC_FONT) {
                    if (current_font != *ptr) {
                        if (!context->stack.isEmpty()) {
                            context->cursor->setCharFormat(context->stack.pop());
                        }

                        QTextCharFormat format(context->cursor->charFormat());
                        context->stack.push(format);

                        int pointSize = qRound(format.fontPointSize());
                        if (*ptr == 1) {
                            format.setFontWeight(QFont::Bold);
                            pointSize += 3;
                        } else if (*ptr == 2) {
                            format.setFontWeight(QFont::Bold);
                            pointSize += 2;
                        } else if (*ptr == 3) {
                            format.setFontWeight(QFont::Bold);
                            pointSize += 1;
                        } else if (*ptr == 4) {
                            format.setFontWeight(QFont::Bold);
                        } else if (*ptr == 5) {
                            format.setFontWeight(QFont::Bold);
                            pointSize += -1;
                        } else if (*ptr == 6) {
                            format.setFontWeight(QFont::Bold);
                            pointSize += -2;
                        } else if (*ptr == 7) {
                            format.setFontWeight(QFont::Bold);
                        } else if (*ptr == 8) {
                            format.setFontFamily(QStringLiteral("Courier New,courier"));
                        } else if (*ptr == 11) {
                            format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
                        }
                        format.setFontPointSize(qMax(pointSize, 1));

                        context->cursor->setCharFormat(format);

                        current_font = *ptr;
                    }

                } else if (fctype == PLKR_TFC_BITALIC) {
                    QTextCharFormat format(context->cursor->charFormat());
                    format.setFontItalic(true);
                    context->cursor->setCharFormat(format);

                    current_italic = true;

                } else if (fctype == PLKR_TFC_EITALIC) {
                    if (current_italic) {
                        QTextCharFormat format(context->cursor->charFormat());
                        format.setFontItalic(false);
                        context->cursor->setCharFormat(format);
                        current_italic = false;
                    }

                } else if (fctype == PLKR_TFC_BULINE) {
                    QTextCharFormat format(context->cursor->charFormat());
                    format.setFontUnderline(true);
                    context->cursor->setCharFormat(format);
                    current_underline = true;

                } else if (fctype == PLKR_TFC_EULINE) {
                    if (current_underline) {
                        QTextCharFormat format(context->cursor->charFormat());
                        format.setFontUnderline(false);
                        context->cursor->setCharFormat(format);
                        current_underline = false;
                    }

                } else if (fctype == PLKR_TFC_BSTRIKE) {
                    QTextCharFormat format(context->cursor->charFormat());
                    format.setFontStrikeOut(true);
                    context->cursor->setCharFormat(format);
                    current_struckthrough = true;

                } else if (fctype == PLKR_TFC_ESTRIKE) {
                    if (current_struckthrough) {
                        QTextCharFormat format(context->cursor->charFormat());
                        format.setFontStrikeOut(false);
                        context->cursor->setCharFormat(format);
                        current_struckthrough = false;
                    }

                } else if (fctype == PLKR_TFC_HRULE) {
                    QTextCharFormat charFormat = context->cursor->charFormat();
                    QTextBlockFormat oldBlockFormat = context->cursor->blockFormat();

                    QTextBlockFormat blockFormat;
                    blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, QStringLiteral("100%"));
                    context->cursor->insertBlock(blockFormat);
                    context->cursor->insertBlock(oldBlockFormat);
                    context->cursor->setCharFormat(charFormat);
                } else if (fctype == PLKR_TFC_ALIGN) {
                    //                     current_alignment = 0;

                    if (*ptr < 4) {
                        QTextBlockFormat format(context->cursor->blockFormat());
                        if (*ptr == 0) {
                            format.setAlignment(Qt::AlignLeft);
                        } else if (*ptr == 1) {
                            format.setAlignment(Qt::AlignRight);
                        } else if (*ptr == 2) {
                            format.setAlignment(Qt::AlignCenter);
                        } else if (*ptr == 3) {
                            format.setAlignment(Qt::AlignJustify);
                        }

                        QTextCharFormat charFormat(context->cursor->charFormat());
                        context->cursor->insertBlock(format);
                        context->cursor->setCharFormat(charFormat);

                        //                         current_alignment = (*ptr) + 1;
                    }

                } else if (fctype == PLKR_TFC_COLOR) {
                    /* not sure what to do here yet */
                    /*
                    fprintf (fp, "<!-- color=\"#%02x%02x%02x\" -->",
                             ptr[0], ptr[1], ptr[2]);*/
                    //                     current_color =
                    //                         (ptr[0] << 16) + (ptr[1] << 8) + ptr[2];

                } else if (fctype == PLKR_TFC_IMAGE || fctype == PLKR_TFC_IMAGE2) {
                    QTextCharFormat format = context->cursor->charFormat();
                    context->cursor->insertImage(QStringLiteral("%1.jpg").arg((ptr[0] << 8) + ptr[1]));
                    context->images.append((ptr[0] << 8) + ptr[1]);
                    context->cursor->setCharFormat(format);
                    AddRecord((ptr[0] << 8) + ptr[1]);

                } else if (fctype == PLKR_TFC_TABLE) {
                    int record_id, datalen;
                    plkr_DataRecordType type = (plkr_DataRecordType)0;
                    unsigned char *bytes = nullptr;

                    record_id = (ptr[0] << 8) + ptr[1];
                    bytes = plkr_GetRecordBytes(doc, record_id, &datalen, &type);

                    TranscribeTableRecord(doc, context, bytes);

                } else if (fctype == PLKR_TFC_UCHAR) {
                    if (fclen == 3) {
                        context->cursor->insertText(QChar((ptr[1] << 8) + ptr[2]));
                    } else if (fclen == 5) {
                        context->cursor->insertText(QChar((ptr[3] << 8) + ptr[4]));
                    }
                    /* skip over alternate text */
                    ptr += ptr[0];
                }

                ptr += fclen;
                run = ptr;
            } else {
                ptr++;
            }
        }

        if ((ptr - run) > 0) {
            /* output any pending text at the end of the paragraph */
            context->cursor->insertText(QString::fromLatin1((char *)run, ptr - run));
            textlen += (ptr - run);
            run = ptr;
        }

        /* clear the graphics state again */

        if (current_font > 0 && current_font < 9) {
            if (!context->stack.isEmpty()) {
                context->cursor->setCharFormat(context->stack.pop());
            }
        }

        if (current_italic) {
            QTextCharFormat format(context->cursor->charFormat());
            format.setFontItalic(false);
            context->cursor->setCharFormat(format);
        }
        if (current_underline) {
            QTextCharFormat format(context->cursor->charFormat());
            format.setFontUnderline(false);
            context->cursor->setCharFormat(format);
        }
        if (current_struckthrough) {
            QTextCharFormat format(context->cursor->charFormat());
            format.setFontStrikeOut(false);
            context->cursor->setCharFormat(format);
        }
    }
    return true;
}

bool QUnpluck::TranscribeRecord(int index)
{
    plkr_DataRecordType type;
    int data_len;
    bool status = true;

    unsigned char *data = plkr_GetRecordBytes(mDocument, index, &data_len, &type);
    if (!data) {
        MarkRecordDone(index);
        return false;
    }

    if (type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT) {
        QTextDocument *document = new QTextDocument;

        QTextFrameFormat format(document->rootFrame()->frameFormat());
        format.setMargin(20);
        document->rootFrame()->setFrameFormat(format);

        Context *context = new Context;
        context->recordId = index;
        context->document = document;
        context->cursor = new QTextCursor(document);

        QTextCharFormat charFormat;
        charFormat.setFontPointSize(10);
        charFormat.setFontFamily(QStringLiteral("Helvetica"));
        context->cursor->setCharFormat(charFormat);

        status = TranscribeTextRecord(mDocument, index, context, data, type);
        document->setTextWidth(600);

        delete context->cursor;
        mContext.append(context);
    } else if (type == PLKR_DRTYPE_IMAGE_COMPRESSED || type == PLKR_DRTYPE_IMAGE) {
        QImage image = TranscribeImageRecord(data);
        mImages.insert(index, image);
    } else if (type == PLKR_DRTYPE_MULTIIMAGE) {
        QImage image;
        if (TranscribeMultiImageRecord(mDocument, image, data)) {
            mImages.insert(index, image);
        }
    } else {
        status = false;
    }

    // plkr_GetHomeRecordID (doc)))

    MarkRecordDone(index);

    return status;
}
