/*

Copyright 2013 Alexander Volkov
Copyright 2013 Adam Reichold

This file is part of qpdfview.

qpdfview 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.

qpdfview is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with qpdfview.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "psmodel.h"

#include <QFile>
#include <QFormLayout>
#include <qmath.h>
#include <QSettings>
#include <QSpinBox>

#include <libspectre/spectre-document.h>

namespace
{

using namespace qpdfview;
using namespace qpdfview::Model;

inline void appendRow(QStandardItemModel* model, const QString& key, const QString& value)
{
    model->appendRow(QList< QStandardItem* >() << new QStandardItem(key) << new QStandardItem(value));
}

namespace Defaults
{

const int graphicsAntialiasBits = 4;
const int textAntialiasBits = 2;

} // Defaults

} // anonymous

namespace qpdfview
{

namespace Model
{

PsPage::PsPage(QMutex* mutex, SpectrePage* page, SpectreRenderContext* renderContext) :
    m_mutex(mutex),
    m_page(page),
    m_renderContext(renderContext)
{
}

PsPage::~PsPage()
{
    spectre_page_free(m_page);
    m_page = 0;
}

QSizeF PsPage::size() const
{
    QMutexLocker mutexLocker(m_mutex);

    int w;
    int h;

    spectre_page_get_size(m_page, &w, &h);

    return QSizeF(w, h);
}

QImage PsPage::render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, const QRect& boundingRect) const
{
    QMutexLocker mutexLocker(m_mutex);

    double xscale;
    double yscale;

    switch(rotation)
    {
    default:
    case RotateBy0:
    case RotateBy180:
        xscale = horizontalResolution / 72.0;
        yscale = verticalResolution / 72.0;
        break;
    case RotateBy90:
    case RotateBy270:
        xscale = verticalResolution / 72.0;
        yscale = horizontalResolution / 72.0;
        break;
    }

    spectre_render_context_set_scale(m_renderContext, xscale, yscale);

    switch(rotation)
    {
    default:
    case RotateBy0:
        spectre_render_context_set_rotation(m_renderContext, 0);
        break;
    case RotateBy90:
        spectre_render_context_set_rotation(m_renderContext, 90);
        break;
    case RotateBy180:
        spectre_render_context_set_rotation(m_renderContext, 180);
        break;
    case RotateBy270:
        spectre_render_context_set_rotation(m_renderContext, 270);
        break;
    }

    int w;
    int h;

    spectre_page_get_size(m_page, &w, &h);

    w = qRound(w * xscale);
    h = qRound(h * yscale);

    if(rotation == RotateBy90 || rotation == RotateBy270)
    {
        qSwap(w, h);
    }

    unsigned char* pageData = 0;
    int rowLength = 0;

    spectre_page_render(m_page, m_renderContext, &pageData, &rowLength);

    if (spectre_page_status(m_page) != SPECTRE_STATUS_SUCCESS)
    {
        free(pageData);
        pageData = 0;

        return QImage();
    }

    QImage auxiliaryImage(pageData, rowLength / 4, h, QImage::Format_RGB32);
    QImage image(boundingRect.isNull() ? auxiliaryImage.copy(0, 0, w, h) : auxiliaryImage.copy(boundingRect));

    free(pageData);
    pageData = 0;

    return image;
}

PsDocument::PsDocument(SpectreDocument* document, SpectreRenderContext* renderContext) :
    m_mutex(),
    m_document(document),
    m_renderContext(renderContext)
{
}

PsDocument::~PsDocument()
{
    spectre_render_context_free(m_renderContext);
    m_renderContext = 0;

    spectre_document_free(m_document);
    m_document = 0;
}

int PsDocument::numberOfPages() const
{
    QMutexLocker mutexLocker(&m_mutex);

    return spectre_document_get_n_pages(m_document);
}

Page* PsDocument::page(int index) const
{
    QMutexLocker mutexLocker(&m_mutex);

    SpectrePage* page = spectre_document_get_page(m_document, index);

    return page != 0 ? new PsPage(&m_mutex, page, m_renderContext) : 0;
}

QStringList PsDocument::saveFilter() const
{
    QMutexLocker mutexLocker(&m_mutex);

    if(spectre_document_is_eps(m_document))
    {
        return QStringList() << QLatin1String("Encapsulated PostScript (*.eps)");
    }
    else
    {
        return QStringList() << QLatin1String("PostScript (*.ps)");
    }
}

bool PsDocument::canSave() const
{
    return true;
}

bool PsDocument::save(const QString& filePath, bool withChanges) const
{
    Q_UNUSED(withChanges)

    QMutexLocker mutexLocker(&m_mutex);

    spectre_document_save(m_document, QFile::encodeName(filePath));

    return spectre_document_status(m_document) == SPECTRE_STATUS_SUCCESS;
}

bool PsDocument::canBePrintedUsingCUPS() const
{
    return true;
}

void PsDocument::loadProperties(QStandardItemModel* propertiesModel) const
{
    Document::loadProperties(propertiesModel);

    QMutexLocker mutexLocker(&m_mutex);

    const QString title = QString::fromLocal8Bit(spectre_document_get_title(m_document));
    const QString createdFor = QString::fromLocal8Bit(spectre_document_get_for(m_document));
    const QString creator = QString::fromLocal8Bit(spectre_document_get_creator(m_document));
    const QString creationDate = QString::fromLocal8Bit(spectre_document_get_creation_date(m_document));
    const QString format = QString::fromLocal8Bit(spectre_document_get_format(m_document));
    const QString languageLevel = QString::number(spectre_document_get_language_level(m_document));

    appendRow(propertiesModel, tr("Title"), title);
    appendRow(propertiesModel, tr("Created for"), createdFor);
    appendRow(propertiesModel, tr("Creator"), creator);
    appendRow(propertiesModel, tr("Creation date"), creationDate);
    appendRow(propertiesModel, tr("Format"), format);
    appendRow(propertiesModel, tr("Language level"), languageLevel);
}

} // Model

PsSettingsWidget::PsSettingsWidget(QSettings* settings, QWidget* parent) : SettingsWidget(parent),
    m_settings(settings)
{
    m_layout = new QFormLayout(this);

    // graphics antialias bits

    m_graphicsAntialiasBitsSpinBox = new QSpinBox(this);
    m_graphicsAntialiasBitsSpinBox->setRange(1, 4);
    m_graphicsAntialiasBitsSpinBox->setValue(m_settings->value("graphicsAntialiasBits", Defaults::graphicsAntialiasBits).toInt());

    m_layout->addRow(tr("Graphics antialias bits:"), m_graphicsAntialiasBitsSpinBox);

    // text antialias bits

    m_textAntialiasBitsSpinBox = new QSpinBox(this);
    m_textAntialiasBitsSpinBox->setRange(1, 2);
    m_textAntialiasBitsSpinBox->setValue(m_settings->value("textAntialiasBits", Defaults::textAntialiasBits).toInt());

    m_layout->addRow(tr("Text antialias bits:"), m_textAntialiasBitsSpinBox);
}

void PsSettingsWidget::accept()
{
    m_settings->setValue("graphicsAntialiasBits", m_graphicsAntialiasBitsSpinBox->value());
    m_settings->setValue("textAntialiasBits", m_textAntialiasBitsSpinBox->value());
}

void PsSettingsWidget::reset()
{
    m_graphicsAntialiasBitsSpinBox->setValue(Defaults::graphicsAntialiasBits);
    m_textAntialiasBitsSpinBox->setValue(Defaults::textAntialiasBits);
}

PsPlugin::PsPlugin(QObject* parent) : QObject(parent)
{
    setObjectName("PsPlugin");

    m_settings = new QSettings("qpdfview", "ps-plugin", this);
}

Model::Document* PsPlugin::loadDocument(const QString& filePath) const
{
    SpectreDocument* document = spectre_document_new();

    spectre_document_load(document, QFile::encodeName(filePath));

    if (spectre_document_status(document) != SPECTRE_STATUS_SUCCESS)
    {
        spectre_document_free(document);

        return 0;
    }

    SpectreRenderContext* renderContext = spectre_render_context_new();

    spectre_render_context_set_antialias_bits(renderContext,
                                              m_settings->value("graphicsAntialiasBits", Defaults::graphicsAntialiasBits).toInt(),
                                              m_settings->value("textAntialiasBits", Defaults::textAntialiasBits).toInt());

    return new Model::PsDocument(document, renderContext);
}

SettingsWidget* PsPlugin::createSettingsWidget(QWidget* parent) const
{
    return new PsSettingsWidget(m_settings, parent);
}

} // qpdfview

#if QT_VERSION < QT_VERSION_CHECK(5,0,0)

Q_EXPORT_PLUGIN2(qpdfview_ps, qpdfview::PsPlugin)

#endif // QT_VERSION
