/*
 *      qpdl.cpp                (C) 2006-2007, Aurélien Croc (AP²C)
 *
 *   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; version 2 of the License.
 *
 *   This program 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 this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *   $Id: qpdl.cpp 231 2008-10-19 13:16:24Z ap2c $
 */
#include "qpdl.h"
#include <QtCore/QFile>
#include <QtCore/QByteArray>
#include <QtCore/QTextStream>
#include "i18n.h"

/*
 * Définitions et déclarations
 * Definitions and declarations
 */
#define HEADER_SIGNATURE    0x00
#define HEADER_YRESOLUTION  0x01
#define HEADER_NRCOPIES_H   0x02
#define HEADER_NRCOPIES_L   0x03
#define HEADER_PAPERTYPE    0x04
#define HEADER_WIDTH_H      0x05
#define HEADER_WIDTH_L      0x06
#define HEADER_HEIGHT_H     0x07
#define HEADER_HEIGHT_L     0x08
#define HEADER_PAPERSOURCE  0x09
#define HEADER_UNKNOWN1	    0x0A
#define HEADER_DUPLEX       0x0B
#define HEADER_TUMBLE       0x0C
#define HEADER_UNKNOWN2	    0x0D
#define HEADER_QPDLVERSION  0x0E
#define HEADER_UNKNOWN3     0x0F
#define HEADER_XRESOLUTION  0x10

#define BAND_NUMBER         0x0
#define BAND_WIDTH_H        0x1
#define BAND_WIDTH_L        0x2
#define BAND_HEIGHT_H       0x3
#define BAND_HEIGHT_L       0x4

#define MAX_PAPER_TYPE 24
static const char *_paperTypeName[] = {
    "Letter",       // 0
    "Legal",
    "A4",
    "Executive",
    "Ledger",
    "A3",           // 5
    "Com10",
    "Monarch",
    "C5",
    "DL",
    "JB4",          // 10
    "JB5",
    "B5",
    "Not listed",
    "JPost",
    "JDouble",      // 15
    "A5",
    "A6",
    "JB6",
    "*Unknown*",
    "*Unknown*",    // 20
    "Custom",
    "*Unknown",
    "C6",
    "Folio"};       // 24

#define MAX_PAPER_SOURCE 7
static const char *_paperSourceName[] = {
    "*Unknown*",    // 0
    "Auto",
    "Manual",
    "Multi",
    "Top",
    "Lower",        // 5
    "Envelopes",
    "Third"};       // 7



/*
 * Constructeur - Destructeur
 * Init - Uninit
 */
QPDLDocument::QPDLDocument()
{
    _pageNr = 0;
}



/*
 * Lecture de l'en-tête d'une page
 * Read page header
 */
QPDLDocument::Result QPDLDocument::_readPageHeader(QFile& data, 
    QTextStream& out, QTextStream& err)
{
    QByteArray header = data.read(0x11);

    // Check header signature
    if (header.at(HEADER_SIGNATURE) == '\t')
        return End;
    if (header.at(HEADER_SIGNATURE) != 0) {
        err << QString(_("QPDL: bad page header signature (%1)")).
            arg((quint8)header.at(HEADER_SIGNATURE)) << endl;
        return Error;
    }

    // Resolution
    _resolutionX = header.at(HEADER_XRESOLUTION) ? 
        (quint8)header.at(HEADER_XRESOLUTION) * 100 : 
        (quint8)header.at(HEADER_YRESOLUTION) * 100;
    _resolutionY = (quint8)header.at(HEADER_YRESOLUTION) * 100;

    // Paper type
    _paperType = header.at(HEADER_PAPERTYPE);

    // Paper source
    _paperSource = header.at(HEADER_PAPERSOURCE);

    // Printable area size
    _width = ((quint8)header.at(HEADER_WIDTH_H) << 8) + 
        (quint8)header.at(HEADER_WIDTH_L);
    _height = ((quint8)header.at(HEADER_HEIGHT_H) << 8) + 
        (quint8)header.at(HEADER_HEIGHT_L);

    // Copies number
    _nrCopies = ((quint8)header.at(HEADER_NRCOPIES_H) << 8) +
        (quint8)header.at(HEADER_NRCOPIES_L);

    // Duplex and Tumble
    _duplex = header.at(HEADER_DUPLEX);
    _tumble = header.at(HEADER_TUMBLE);

    // QPDL VERSION
    _qpdl = header.at(HEADER_QPDLVERSION);

    // Unknown bytes
    _unknown[0] = header.at(HEADER_UNKNOWN1);
    _unknown[1] = header.at(HEADER_UNKNOWN2);
    _unknown[2] = header.at(HEADER_UNKNOWN3);

    // Print a summary
    if (!_quiet) {
        out << QString(_("Page %1 header:")).arg(_pageNr) << endl;
        out << "    " << QString(_("QPDL version..... = %1")).
            arg(_qpdl) << endl;
        out << "    " << QString(_("Number of copies. = %1")).
            arg(_nrCopies) << endl;
        out << "    " << QString(_("Resolution....... = %1×%2")).
            arg(_resolutionX).arg(_resolutionY) << endl;
        out << "    " << QString(_("Paper type....... = %1")).
            arg(_paperType <= MAX_PAPER_TYPE ? _paperTypeName[_paperType] :
            QString("*Unknown* (%1)").arg(_paperType)) << endl;
        out << "    " << QString(_("Paper source..... = %1")).
            arg(_paperSource<=MAX_PAPER_SOURCE ? _paperSourceName[_paperSource]:
            QString("*Unknown* (%1)").arg(_paperSource)) << endl;
        out << "    " << QString(_("Printable area... = %1×%2")).
            arg(_width).arg(_height) << endl;
        out << "    " << QString(_("Duplex - Tumble.. = %1 %2")).
            arg(_duplex, 1).arg(_tumble, 1) << endl;
        out << "    " << QString(_("Unknown bytes.... = %1 %2 %3")).
            arg((quint8)_unknown[0], 1).arg((quint8)_unknown[1], 1).
            arg((quint8)_unknown[2], 1) << endl;
    }

    return Ok;
}



/*
 * Extraction de chaques bandes
 * Band extraction
 */
QPDLDocument::Result QPDLDocument::_readPageContent(QFile& data, 
    QTextStream& out, QTextStream& err)
{
    if (!_quiet) {
        if (_decompression)
            out << _("    Analysing and decompression");
        else
            out << _("    Analysing");
    }

    while (!data.atEnd()) {
        QByteArray header, content;
        quint16 width, height;
        char signature, color=1;
        quint8 compression;
        quint32 size;

        // Check the band signature
        data.getChar(&signature);
        if (signature == 0x1) { // End of the page
            if (!_quiet)
                out << QString("(0x%1)").arg(compression, 0, 16) << endl;
            return Ok;
        } else if (signature != 0xC) {
            err << QString(_("QPDL: bad band header signature (%1)")).
                arg((quint8)signature) << endl;
            return Error;
        }

        // Extract the band information
        header = data.read(5);
        _currentBandNr = header.at(BAND_NUMBER);
        width = ((quint8)header.at(BAND_WIDTH_H) << 8) + 
            (quint8)header.at(BAND_WIDTH_L);
        height = ((quint8)header.at(BAND_HEIGHT_H) << 8) + 
            (quint8)header.at(BAND_HEIGHT_L);
        if (!_quiet) {
            out << '.';
            out.flush();
        }

        // Read the band data -- SPLc in QPDL V. 0, 1 or 5
        if (_type == SPLc && (_qpdl <= 1 || _qpdl == 5)) {
            while (!data.atEnd()) {
                // Read the color
                data.getChar(&color);
                if (!color)
                    break;
                if (color < 0 || color > 4) {
                    err << QString(_("QPDL: bad color value (%1) in band %2")).
                        arg((quint8)color).arg(_currentBandNr) << endl;
                    return Error;
                }

                // Read the compression and the size
                header = data.read(5);
                compression = header.at(0);
                size = ((quint8)header.at(1) << 24) + 
                    ((quint8)header.at(2) << 16) + 
                    ((quint8)header.at(3) << 8) + (quint8)header.at(4);
                content = data.read(size);

                // Process the band
                if (!_processBandAnalysis(color, width, height, compression, 
                    content, out, err))
                    return Error;
            } 


        // Read the band data -- SPLc in QPDL V. 2
        } else if (_type == SPLc && _qpdl == 2) {
            // Read the color
            data.getChar(&color);
            if (color <= 0 || color > 4) {
                err << QString(_("QPDL: bad color value (%1) in band %2")).
                    arg((quint8)color).arg(_currentBandNr) << endl;
                return Error;
            }

            // Read the compression and the size
            header = data.read(5);
            compression = header.at(0);
            size = ((quint8)header.at(1) << 24) + ((quint8)header.at(2) << 16) +
                ((quint8)header.at(3) << 8) + (quint8)header.at(4);
            content = data.read(size);

            // Process the band
            if (!_processBandAnalysis(color, width, height, compression, 
                        content, out, err))
                return Error;


        // Read the band data -- SPL-2 in any QPDL
        } else {
            // Read the compression and the size
            header = data.read(5);
            compression = header.at(0);
            size = ((quint8)header.at(1) << 24) + ((quint8)header.at(2) << 16) +
                ((quint8)header.at(3) << 8) + (quint8)header.at(4);
            content = data.read(size);

            // Process the band
            if (!_processBandAnalysis(1, width, height, compression, 
                        content, out, err))
                return Error;
        }
    }
    return Error;
}



/*
 * Traitement d'une bande
 * Band management
 */
bool QPDLDocument::_processBandAnalysis(quint8 color, quint16 width, 
    quint16 height, quint8 compression, QByteArray& content,
    QTextStream& out, QTextStream& err)
{
    quint32 checkSum = 0, givenCheckSum;
    quint8 version;
    bool be;

    // Check the signature and the BE/LE
    if (((quint8)content.at(0) & 0xF) != 0x9 || (quint8)content.at(1) != 0xAB ||
        (quint8)content.at(2) != 0xCD || (quint8)content.at(3) != 0xEF) {
        if (((quint8)content.at(3) & 0xF) != 0x09 || 
            (quint8)content.at(2) != 0xAB || (quint8)content.at(1) != 0xCD || 
            (quint8)content.at(0) != 0xEF) {
            err << QString(_("QPDL: Invalid signature (0x%1%2%3%4)")).
                arg((quint8)content.at(0),0,16).arg((quint8)content.at(1),0,16).
                arg((quint8)content.at(2),0,16).arg((quint8)content.at(3),0,16) 
                << endl;
            return false;
        } else {
            be = false;
            version = (quint8)content.at(3) >> 4;
        }
    } else {
        be = true;
        version = (quint8)content.at(0) >> 4;
    }
    _page.setSubHeaderVersion(version);
    _page.setBE(be);

    // Check the checksum
    if (_qpdl > 0) {
        givenCheckSum = ((quint8)content.at(content.size() - 4) << 24) + 
            ((quint8)content.at(content.size() - 3) << 16) + 
            ((quint8)content.at(content.size() - 2) << 8) +
            (quint8)content.at(content.size() - 1);
        for (unsigned int i=0; i < content.size() - 4; i++)
            checkSum += (quint8)content.at(i);
        if (givenCheckSum != checkSum) {
            err << QString(_("QPDL: band checksum invalid! (0x%1-0x%2)")).
                arg(givenCheckSum, 8, 16, QChar('0')).arg(checkSum, 8, 16,
                QChar('0')) << endl;
            return false;
        }
        content.remove(content.size() - 4, 4);
    }
    content.remove(0, 4);

    // Decompress the band
    if (_decompression || _dump)
        return _page.process(color, width, height, _dump ? 0 : compression, 
            content, out, err);
    return true;
}



/* 
 * Boucle générale de traitement des données
 * Data management main loop
 */
bool QPDLDocument::parse(QFile& data, QTextStream& out, QTextStream& err)
{
    _page.setQPDLDocument(this);

    while (!data.atEnd()) {
        QByteArray footer;
        quint16 nrCopies;
        Result res;

        _pageNr++;
        _page.clear();

        // Read the page header
        res = _readPageHeader(data, out, err);
        if (res == Error)
            return false;
        if (res == End)
            break;

        // Read the page bands
        res = _readPageContent(data, out, err);
        if (res == Error)
            return false;
        if (res == End)
            break;

        // Read the page footer
        footer = data.read(2);
        nrCopies = ((quint8)footer.at(0) << 8) + (quint8)footer.at(1);
        if (!_quiet)
            out << QString(_("Page %1 done (%2 copie(s)).")).arg(_pageNr).
                arg(nrCopies) << endl << endl;
        _page.flush();
    }
    return true;
}

/* vim: set expandtab tabstop=4 shiftwidth=4 smarttab tw=80 cin enc=utf8 : */

