/***************************************************************************
 *   Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org>                   *
 *                                                                         *
 *            Based on code written by Bill Janssen 2002                   *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "qunpluck.h"

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

#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 ParagraphInfo *ParseParagraphInfo
    (
    unsigned char*  bytes,
    int*            nparas
    )
{
    ParagraphInfo*  paragraph_info;
    int             j;
    int             n;

    n = (bytes[2] << 8) + bytes[3];
    paragraph_info = (ParagraphInfo *) malloc (sizeof (ParagraphInfo) * n);
    for (j = 0; j < n; j++) {
        paragraph_info[j].size =
            (bytes[8 + (j * 4) + 0] << 8) + bytes[8 + (j * 4) + 1];
        paragraph_info[j].attributes =
            (bytes[8 + (j * 4) + 2] << 8) + bytes[8 + (j * 4) + 3];
    }
    *nparas = n;
    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;
    ParagraphInfo*  paragraphs;
    bool            first_record_of_page = true;
    bool            current_link;
    bool            current_italic;
    bool            current_struckthrough;
    bool            current_underline;
    int             fctype;
    int             fclen;
    int             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;
    int             nparagraphs;
//     long            current_color;

    record_index = id;

    paragraphs = ParseParagraphInfo (bytes, &nparagraphs);
    start = bytes + 8 + ((bytes[2] << 8) + bytes[3]) * 4;

    for (para_index = 0, ptr = start, run = start;
         para_index < nparagraphs; 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) == nparagraphs) &&
            (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);
                free (paragraphs);
                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);
                free (paragraphs);
                return false;
            }
            first_record_of_page = false;
            para_index = 0;
            ptr = data + 8 + ((data[2] << 8) + data[3]) * 4;
            run = ptr;
            free (paragraphs);
            paragraphs = ParseParagraphInfo (data, &nparagraphs);
            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 format( context->cursor->charFormat() );
        QTextBlockFormat blockFormat( context->cursor->blockFormat() );
        blockFormat.setAlignment( Qt::AlignLeft );
        context->cursor->insertBlock( blockFormat );
        context->cursor->setCharFormat( format );

        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_MARGINS) {

                    /* Not easy to set, in HTML */
#if 0
                    output += QString( "<!-- margins:  %1, %2 -->" ).arg(ptr[0], ptr[1]);
                    if (current_left_margin != ptr[0]
                        || current_right_margin != ptr[1]) {
                        if (current_right_margin != 0)
                            fprintf (fp, "</td><td width=%d>&nbsp;",
                                     current_right_margin);
                        fprintf (fp, "</td></tr></table>\n");
                    }
                    current_left_margin = ptr[0];
                    current_right_margin = ptr[1];
                    if (current_right_margin > 0
                        || current_left_margin > 0) {
                        fprintf (fp, "<table border=1><tr>");
                        if (current_left_margin != 0) {
                            fprintf (fp, "<td width=%d align=right>",
                                     current_left_margin);
                            if ((ptr - run) > 2) {
                                fwrite (run, 1, ((ptr - 2) - run), fp);
                                textlen += ((ptr - 2) - run);
                            }
                            else {
                                fprintf (fp, "&nbsp;");
                            }
                            fprintf (fp, "</td>");
                        }
                        fprintf (fp, "<td>");
                        if (current_left_margin == 0 && (ptr - run) > 2) {
                            fwrite (run, 1, ((ptr - 2) - run), fp);
                            textlen += ((ptr - 2) - run);
                        }
                    }
                    else {
                        if ((ptr - run) > 2) {
                            fwrite (run, 1, ((ptr - 2) - run), fp);
                            textlen += ((ptr - 2) - run);
                        }
                    }
#endif

//                     current_left_margin = ptr[0];
//                     current_right_margin = 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];

                }
                else {

                    /* ignore function */
                    //output += QString( "<!-- function code %1 ignored -->" ).arg(fctype);

                }

                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 );
        }
#if 0
        if (current_alignment > 0) {
            context->cursor->insertBlock();
        }

        if (current_right_margin > 0)
            fprintf (fp, "</td><td width=%d>&nbsp;</td></tr></table>",
                     current_right_margin);
        else if (current_left_margin > 0)
            fprintf (fp, "</td></tr></table>");

        /* end the paragraph */
        context->cursor->insertBlock();
#endif
    }
    free (paragraphs);
    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;
}


