/***************************************************************************
 *   Copyright (C) 2007 by Tobias Koenig <tokoe@kde.org>                   *
 *                                                                         *
 *   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 "converter.h"

#include <QtCore/QDate>
#include <QtCore/QUrl>
#include <QtGui/QAbstractTextDocumentLayout>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocument>
#include <QtGui/QTextFrame>
#include <QtXml/QDomElement>
#include <QtXml/QDomText>

#include <KLocalizedString>

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

#include "document.h"

using namespace FictionBook;

class Converter::TitleInfo
{
    public:
        QStringList mGenres;
        QString mAuthor;
        QString mTitle;
        QStringList mKeywords;
        QDate mDate;
        QDomElement mCoverPage;
        QString mLanguage;
};

class Converter::DocumentInfo
{
    public:
        QString mAuthor;
        QString mProducer;
        QDate mDate;
        QString mId;
        QString mVersion;
};

Converter::Converter()
    : mTextDocument( nullptr ), mCursor( nullptr ),
      mTitleInfo( nullptr ), mDocumentInfo( nullptr )
{
}

Converter::~Converter()
{
    delete mTitleInfo;
    delete mDocumentInfo;
}

QTextDocument* Converter::convert( const QString &fileName )
{
    Document fbDocument( fileName );
    if ( !fbDocument.open() ) {
        emit error( fbDocument.lastErrorString(), -1 );
        return nullptr;
    }

    mTextDocument = new QTextDocument;
    mCursor = new QTextCursor( mTextDocument );
    mSectionCounter = 0;
    mLocalLinks.clear();
    mSectionMap.clear();

    const QDomDocument document = fbDocument.content();

    /**
     * Set the correct page size
     */
    mTextDocument->setPageSize( QSizeF( 600, 800 ) );

    QTextFrameFormat frameFormat;
    frameFormat.setMargin( 20 );

    QTextFrame *rootFrame = mTextDocument->rootFrame();
    rootFrame->setFrameFormat( frameFormat );

    /**
     * Parse the content of the document
     */
    const QDomElement documentElement = document.documentElement();

    if ( documentElement.tagName() != QLatin1String( "FictionBook" ) ) {
        emit error( i18n( "Document is not a valid FictionBook" ), -1 );
        delete mCursor;
        return nullptr;
    }

    /**
     * First we read all images, so we can calculate the size later.
     */
    QDomElement element = documentElement.firstChildElement();
    while ( !element.isNull() ) {
        if ( element.tagName() == QLatin1String( "binary" ) ) {
            if ( !convertBinary( element ) ) {
                delete mCursor;
                return nullptr;
            }
        }

        element = element.nextSiblingElement();
    }

    /**
     * Read the rest...
     */
    element = documentElement.firstChildElement();
    while ( !element.isNull() ) {
        if ( element.tagName() == QLatin1String( "description" ) ) {
            if ( !convertDescription( element ) ) {
                delete mCursor;
                return nullptr;
            }
        } else if ( element.tagName() == QLatin1String( "body" ) ) {
            if ( !mTitleInfo->mCoverPage.isNull() ) {
                convertCover( mTitleInfo->mCoverPage );
                mCursor->insertBlock();
            }

            QTextFrame *topFrame = mCursor->currentFrame();

            QTextFrameFormat frameFormat;
            frameFormat.setBorder( 2 );
            frameFormat.setPadding( 8 );
            frameFormat.setBackground( Qt::lightGray );

            if ( !mTitleInfo->mTitle.isEmpty() ) {
                mCursor->insertFrame( frameFormat );

                QTextCharFormat charFormat;
                charFormat.setFontPointSize( 22 );
                charFormat.setFontWeight( QFont::Bold );
                mCursor->insertText( mTitleInfo->mTitle, charFormat );

                mCursor->setPosition( topFrame->lastPosition() );
            }

            if ( !mTitleInfo->mAuthor.isEmpty() ) {
                frameFormat.setBorder( 1 );
                mCursor->insertFrame( frameFormat );

                QTextCharFormat charFormat;
                charFormat.setFontPointSize( 10 );
                mCursor->insertText( mTitleInfo->mAuthor, charFormat );

                mCursor->setPosition( topFrame->lastPosition() );
                mCursor->insertBlock();
            }

            mCursor->insertBlock();

            if ( !convertBody( element ) ) {
                delete mCursor;
                return nullptr;
            }
        }

        element = element.nextSiblingElement();
    }

    /**
     * Add document info.
     */
    if ( mTitleInfo ) {
        if ( !mTitleInfo->mTitle.isEmpty() )
            emit addMetaData( Okular::DocumentInfo::Title, mTitleInfo->mTitle );

        if ( !mTitleInfo->mAuthor.isEmpty() )
            emit addMetaData( Okular::DocumentInfo::Author, mTitleInfo->mAuthor );
    }

    if ( mDocumentInfo ) {
        if ( !mDocumentInfo->mProducer.isEmpty() )
            emit addMetaData( Okular::DocumentInfo::Producer, mDocumentInfo->mProducer );

        if ( mDocumentInfo->mDate.isValid() )
            emit addMetaData( Okular::DocumentInfo::CreationDate,
                              QLocale().toString( mDocumentInfo->mDate, QLocale::ShortFormat ) );
    }

    QMapIterator<QString, QPair<int, int> > it( mLocalLinks );
    while ( it.hasNext() ) {
        it.next();

        const QTextBlock block = mSectionMap[ it.key() ];
        if ( !block.isValid() ) // local link without existing target
          continue;

        Okular::DocumentViewport viewport = calculateViewport( mTextDocument, block );

        Okular::GotoAction *action = new Okular::GotoAction( QString(), viewport );

        emit addAction( action, it.value().first, it.value().second );
    }

    delete mCursor;

    return mTextDocument;
}

bool Converter::convertBody( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "section" ) ) {
            mCursor->insertBlock();
            if ( !convertSection( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "image" ) ) {
            if ( !convertImage( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "title" ) ) {
            if ( !convertTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
            if ( !convertEpigraph( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertDescription( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "title-info" ) ) {
            if ( !convertTitleInfo( child ) )
                return false;
        } if ( child.tagName() == QLatin1String( "document-info" ) ) {
            if ( !convertDocumentInfo( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertTitleInfo( const QDomElement &element )
{
    delete mTitleInfo;
    mTitleInfo = new TitleInfo;

    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "genre" ) ) {
            QString genre;
            if ( !convertTextNode( child, genre ) )
                return false;

            if ( !genre.isEmpty() )
                mTitleInfo->mGenres.append( genre );
        } else if ( child.tagName() == QLatin1String( "author" ) ) {
            QString firstName, middleName, lastName, dummy;

            if ( !convertAuthor( child, firstName, middleName, lastName, dummy, dummy ) )
                return false;

            mTitleInfo->mAuthor = QStringLiteral( "%1 %2 %3" ).arg( firstName, middleName, lastName );
        } else if ( child.tagName() == QLatin1String( "book-title" ) ) {
            if ( !convertTextNode( child, mTitleInfo->mTitle ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "keywords" ) ) {
            QString keywords;
            if ( !convertTextNode( child, keywords ) )
                return false;

            mTitleInfo->mKeywords = keywords.split( QLatin1Char(' '), QString::SkipEmptyParts );
        } else if ( child.tagName() == QLatin1String( "date" ) ) {
            if ( !convertDate( child, mTitleInfo->mDate ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "coverpage" ) ) {
            mTitleInfo->mCoverPage = child;
        } else if ( child.tagName() == QLatin1String( "lang" ) ) {
            if ( !convertTextNode( child, mTitleInfo->mLanguage ) )
                return false;
        }
        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertDocumentInfo( const QDomElement &element )
{
    delete mDocumentInfo;
    mDocumentInfo = new DocumentInfo;

    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "author" ) ) {
            QString firstName, middleName, lastName, email, nickname;

            if ( !convertAuthor( child, firstName, middleName, lastName, email, nickname ) )
                return false;

            mDocumentInfo->mAuthor = QStringLiteral( "%1 %2 %3 <%4> (%5)" )
                                      .arg( firstName, middleName, lastName, email, nickname );
        } else if ( child.tagName() == QLatin1String( "program-used" ) ) {
            if ( !convertTextNode( child, mDocumentInfo->mProducer ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "date" ) ) {
            if ( !convertDate( child, mDocumentInfo->mDate ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "id" ) ) {
            if ( !convertTextNode( child, mDocumentInfo->mId ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "version" ) ) {
            if ( !convertTextNode( child, mDocumentInfo->mVersion ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}
bool Converter::convertAuthor( const QDomElement &element, QString &firstName, QString &middleName, QString &lastName,
                               QString &email, QString &nickname )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "first-name" ) ) {
            if ( !convertTextNode( child, firstName ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "middle-name" ) ) {
            if ( !convertTextNode( child, middleName ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "last-name" ) ) {
            if ( !convertTextNode( child, lastName ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "email" ) ) {
            if ( !convertTextNode( child, email ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "nickname" ) ) {
            if ( !convertTextNode( child, nickname ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertTextNode( const QDomElement &element, QString &data )
{
    QDomNode child = element.firstChild();
    while ( !child.isNull() ) {
        QDomText text = child.toText();
        if ( !text.isNull() )
            data = text.data();

        child = child.nextSibling();
    }

    return true;
}

bool Converter::convertDate( const QDomElement &element, QDate &date )
{
    if ( element.hasAttribute( QStringLiteral("value") ) )
        date = QDate::fromString( element.attribute( QStringLiteral("value") ), Qt::ISODate );

    return true;
}

bool Converter::convertSection( const QDomElement &element )
{
    if ( element.hasAttribute( QStringLiteral("id") ) )
        mSectionMap.insert( element.attribute( QStringLiteral("id") ), mCursor->block() );

    mSectionCounter++;

    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "title" ) ) {
            if ( !convertTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
            if ( !convertEpigraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "image" ) ) {
            if ( !convertImage( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "section" ) ) {
            if ( !convertSection( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "p" ) ) {
            QTextBlockFormat format;
            format.setTextIndent( 10 );
            mCursor->insertBlock( format );
            if ( !convertParagraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "poem" ) ) {
            if ( !convertPoem( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "subtitle" ) ) {
            if ( !convertSubTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "cite" ) ) {
            if ( !convertCite( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
            if ( !convertEmptyLine( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    mSectionCounter--;

    return true;
}

bool Converter::convertTitle( const QDomElement &element )
{
    QTextFrame *topFrame = mCursor->currentFrame();

    QTextFrameFormat frameFormat;
    frameFormat.setBorder( 1 );
    frameFormat.setPadding( 8 );
    frameFormat.setBackground( Qt::lightGray );

    mCursor->insertFrame( frameFormat );

    QDomElement child = element.firstChildElement();

    bool firstParagraph = true;
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "p" ) ) {
            if ( firstParagraph )
                firstParagraph = false;
            else
                mCursor->insertBlock();

            QTextCharFormat origFormat = mCursor->charFormat();

            QTextCharFormat titleFormat( origFormat );
            titleFormat.setFontPointSize( 22 - ( mSectionCounter*2 ) );
            titleFormat.setFontWeight( QFont::Bold );
            mCursor->setCharFormat( titleFormat );

            if ( !convertParagraph( child ) )
                return false;

            mCursor->setCharFormat( origFormat );

            emit addTitle( mSectionCounter, child.text(), mCursor->block() );

        } else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
            if ( !convertEmptyLine( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    mCursor->setPosition( topFrame->lastPosition() );

    return true;
}


bool Converter::convertParagraph( const QDomElement &element )
{
    QDomNode child = element.firstChild();
    while ( !child.isNull() ) {
        if ( child.isElement() ) {
            const QDomElement childElement = child.toElement();
            if ( childElement.tagName() == QLatin1String( "emphasis" ) ) {
                if ( !convertEmphasis( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "strong" ) ) {
                if ( !convertStrong( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "style" ) ) {
                if ( !convertStyle( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "a" ) ) {
                if ( !convertLink( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "image" ) ) {
                if ( !convertImage( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "strikethrough" ) ) {
                if ( !convertStrikethrough( childElement ) )
                    return false;
            }
        } else if ( child.isText() ) {
            const QDomText childText = child.toText();
            mCursor->insertText( childText.data() );
        }

        child = child.nextSibling();
    }

    return true;
}

bool Converter::convertEmphasis( const QDomElement &element )
{
    QTextCharFormat origFormat = mCursor->charFormat();

    QTextCharFormat italicFormat( origFormat );
    italicFormat.setFontItalic( true );
    mCursor->setCharFormat( italicFormat );

    if ( !convertParagraph( element ) )
        return false;

    mCursor->setCharFormat( origFormat );

    return true;
}

bool Converter::convertStrikethrough( const QDomElement &element )
{
    QTextCharFormat origFormat = mCursor->charFormat();

    QTextCharFormat strikeoutFormat( origFormat );
    strikeoutFormat.setFontStrikeOut( true );
    mCursor->setCharFormat( strikeoutFormat );

    if ( !convertParagraph( element ) )
        return false;

    mCursor->setCharFormat( origFormat );

    return true;
}

bool Converter::convertStrong( const QDomElement &element )
{
    QTextCharFormat origFormat = mCursor->charFormat();

    QTextCharFormat boldFormat( origFormat );
    boldFormat.setFontWeight( QFont::Bold );
    mCursor->setCharFormat( boldFormat );

    if ( !convertParagraph( element ) )
        return false;

    mCursor->setCharFormat( origFormat );

    return true;
}

bool Converter::convertStyle( const QDomElement &element )
{
    if ( !convertParagraph( element ) )
        return false;

    return true;
}

bool Converter::convertBinary( const QDomElement &element )
{
    const QString id = element.attribute( QStringLiteral("id") );

    const QDomText textNode = element.firstChild().toText();
    QByteArray data = textNode.data().toLatin1();
    data = QByteArray::fromBase64( data );

    mTextDocument->addResource( QTextDocument::ImageResource, QUrl( id ), QImage::fromData( data ) );

    return true;
}

bool Converter::convertCover( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "image" ) ) {
            if ( !convertImage( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertImage( const QDomElement &element )
{
    QString href = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href") );

    if ( href.startsWith( QLatin1Char('#') ) )
        href = href.mid( 1 );

    const QImage img = qvariant_cast<QImage>( mTextDocument->resource( QTextDocument::ImageResource, QUrl( href ) ) );

    QTextImageFormat format;
    format.setName( href );

    if ( img.width() > 560 )
        format.setWidth( 560 );

    format.setHeight( img.height() );

    mCursor->insertImage( format );

    return true;
}

bool Converter::convertEpigraph( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "p" ) ) {
            QTextBlockFormat format;
            format.setTextIndent( 10 );
            mCursor->insertBlock( format );
            if ( !convertParagraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "poem" ) ) {
            if ( !convertPoem( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "cite" ) ) {
            if ( !convertCite( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
            if ( !convertEmptyLine( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertPoem( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "title" ) ) {
            if ( !convertTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "epigraph" ) ) {
            if ( !convertEpigraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
            if ( !convertEmptyLine( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "stanza" ) ) {
            if ( !convertStanza( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertSubTitle( const QDomElement &element )
{
    Q_UNUSED( element )
    return true;
}

bool Converter::convertCite( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "p" ) ) {
            QTextBlockFormat format;
            format.setTextIndent( 10 );
            mCursor->insertBlock( format );
            if ( !convertParagraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "poem" ) ) {
            if ( !convertParagraph( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "empty-line" ) ) {
            if ( !convertEmptyLine( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}

bool Converter::convertEmptyLine( const QDomElement& )
{
    mCursor->insertText( QStringLiteral("\n\n") );
    return true;
}

bool Converter::convertLink( const QDomElement &element )
{
    QString href = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("href") );
    QString type = element.attributeNS( QStringLiteral("http://www.w3.org/1999/xlink"), QStringLiteral("type") );

    if ( type == QLatin1String("note") )
        mCursor->insertText( QStringLiteral("[") );

    int startPosition = mCursor->position();

    QTextCharFormat origFormat( mCursor->charFormat() );

    QTextCharFormat format( mCursor->charFormat() );
    format.setForeground( Qt::blue );
    format.setFontUnderline( true );
    mCursor->setCharFormat( format );

    QDomNode child = element.firstChild();
    while ( !child.isNull() ) {
        if ( child.isElement() ) {
            const QDomElement childElement = child.toElement();
            if ( childElement.tagName() == QLatin1String( "emphasis" ) ) {
                if ( !convertEmphasis( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "strong" ) ) {
                if ( !convertStrong( childElement ) )
                    return false;
            } else if ( childElement.tagName() == QLatin1String( "style" ) ) {
                if ( !convertStyle( childElement ) )
                    return false;
            }
        } else if ( child.isText() ) {
            const QDomText text = child.toText();
            if ( !text.isNull() ) {
                mCursor->insertText( text.data() );
            }
        }

        child = child.nextSibling();
    }
    mCursor->setCharFormat( origFormat );

    int endPosition = mCursor->position();

    if ( type == QLatin1String("note") )
        mCursor->insertText( QStringLiteral("]") );

    if ( href.startsWith( QLatin1Char('#') ) ) { // local link
        mLocalLinks.insert( href.mid( 1 ), QPair<int, int>( startPosition, endPosition ) );
    } else {
        // external link
        Okular::BrowseAction *action = new Okular::BrowseAction( QUrl(href) );
        emit addAction( action, startPosition, endPosition );
    }

    return true;
}

bool Converter::convertStanza( const QDomElement &element )
{
    QDomElement child = element.firstChildElement();
    while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "title" ) ) {
            if ( !convertTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "subtitle" ) ) {
            if ( !convertSubTitle( child ) )
                return false;
        } else if ( child.tagName() == QLatin1String( "v" ) ) {
            QTextBlockFormat format;
            format.setTextIndent( 50 );
            mCursor->insertBlock( format );
            if ( !convertParagraph( child ) )
                return false;
        }

        child = child.nextSiblingElement();
    }

    return true;
}
