/***************************************************************************
                         pixmapbarcode.cpp  -  description
                             -------------------
    begin                : Mon Nov 22 2004
    copyright            : (C) 2004 by Dominik Seichter
    email                : domseichter@web.de
 ***************************************************************************/

/***************************************************************************
                                                                          
    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 "pixmapbarcode.h"
#include "barkode.h"

// patch from Ali Akcaagac says that this include is needed
#include <stdlib.h>

#include <tdeapplication.h>
#include <kprocess.h>
#include <tdetempfile.h>

#include <tqbuffer.h>
#include <tqdom.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqpainter.h>
#include <tqpaintdevicemetrics.h>
#include <tqpixmap.h>
#include <tqtextstream.h>

/* Margin added by GNU Barcode to the barcodes */
#define BARCODE_MARGIN 10     

/* Use a 5KB buffer for pipes */
#define BUFFER_SIZE 1024 * 5 

PDF417Options::PDF417Options()
{
    defaults();
}

const PDF417Options& PDF417Options::operator=( const BarkodeEngineOptions& rhs )
{
    const PDF417Options* pdf = (dynamic_cast<const PDF417Options*>(&rhs));

    this->m_row = pdf->m_row;
    this->m_col = pdf->m_col;
    this->m_err = pdf->m_err;

    return *this;
}

void PDF417Options::defaults()
{
    m_row = 24;
    m_col = 8;
    m_err = 5;
}

void PDF417Options::load( const TQDomElement* tag )
{
    m_row = tag->attribute( "pdf417.row", "24" ).toInt();
    m_col = tag->attribute( "pdf417.col", "8" ).toInt();
    m_err = tag->attribute( "pdf417.err", "5" ).toInt();
}

void PDF417Options::save( TQDomElement* tag )
{
    tag->setAttribute( "pdf417.row", m_row );
    tag->setAttribute( "pdf417.col", m_col );
    tag->setAttribute( "pdf417.err", m_err );
}

PixmapBarcode::PixmapBarcode()
 : BarkodeEngine()
{
}


PixmapBarcode::~PixmapBarcode()
{
}

const PixmapBarcode & PixmapBarcode::operator=( const BarkodeEngine & rhs )
{
    const PixmapBarcode* pix = dynamic_cast<const PixmapBarcode*>(&rhs);

    if( pix ) 
    {
        m_pdf417_options = pix->m_pdf417_options;
        p                = pix->p;
    }

    return *this;
}

const TQSize PixmapBarcode::size() const
{
    return ( p.size().isNull() ? TQSize( 100, 80 ) : p.size() );
}

void PixmapBarcode::update( const TQPaintDevice* device )
{
    p.resize( 0, 0 );
    createBarcode( &p, device );
}

void PixmapBarcode::drawBarcode( TQPainter & painter, int x, int y )
{
    if( p.isNull() )
        createBarcode( &p, painter.device() );
    
    if( p.isNull() ) // still no barcode....
    {
        barkode->drawInvalid( painter, x, y );
        return;
    }
        
    painter.drawPixmap( x, y, p );
}

bool PixmapBarcode::createPixmap( TQPixmap* target, int resx, int resy )
{
    char* postscript = NULL;
    long postscript_size = 0;
    TQString cmd;
    bool bMonocrome;

    bMonocrome = ( barkode->foreground() == TQt::black && 
                   barkode->background() == TQt::white &&
                   barkode->textColor() == TQt::black );

    KTempFile* input = new KTempFile( TQString(), bMonocrome ? ".pbm" : ".ppm" );
    input->file()->close();

    if( Barkode::engineForType( barkode->type() ) == PDF417 ) {
        if(!createPdf417( input )) {
            cleanUp( input, target );
            return false;
        }

	target->load( input->name(), "GIF" );
    } else { 
        if( !createPostscript( &postscript, &postscript_size ) )
        {
            cleanUp( input, target );
            return false;
        }

	FILE* gs_pipe;

	if( !postscript_size )
	{
	    // GNU Barcode was not able to encode this barcode
	    cleanUp( input, target );
	    return false;
	}

        TQRect size = bbox( postscript, postscript_size );
        double sw = (double)(size.x() + size.width())/72 * resx;
        double sh = (double)(size.y() + size.height())/72 * resy;

        if( Barkode::engineForType( barkode->type() ) == TBARCODE )
        {
            sw = (double)(size.x() + size.width());
            sh = (double)(size.y() + size.height());
        }

	cmd = TQString("gs -g%1x%2").arg(int(sw*(double)barkode->scaling())).arg(int(sh*(double)barkode->scaling()));
	cmd += " -r" + TQString::number( resx*(double)barkode->scaling()) + "x" + TQString::number( resy*(double)barkode->scaling() );
	cmd += TQString(" -sDEVICE=%1 -sOutputFile=").arg( bMonocrome ? "pbmraw" : "ppm" );
        cmd += input->name();
	cmd += " -sNOPAUSE -q - -c showpage quit";
	
        tqDebug("cmd: %s", cmd.ascii() );
	gs_pipe = popen( cmd.latin1(), "w" );
        if( !gs_pipe )
	{
	    tqDebug("ERROR: cannot open Ghostscript pipe!");
            cleanUp( input, target );
	    return false;
	}

	fwrite( postscript, sizeof(char), postscript_size, gs_pipe );
	pclose( gs_pipe );

        target->load( input->name(), "PBM" );
    }
        

    free( postscript );

    input->unlink();
    delete input;

    return true;
}

bool PixmapBarcode::createPostscript( char** postscript, long* postscript_size )
{
    TQString cmd;

    /*
    if( Barkode::engineForType( barkode->type() ) == TBARCODE ) 
    {
        cmd = createTBarcodeCmd();
        tqDebug("tbarcodeclient commandline: %s", cmd.latin1() );
    }
    else // GNU_BARCODE
    */
    {
        cmd = "barcode -E -b ";
        cmd += KShellProcess::quote( barkode->parsedValue() ) + (barkode->textVisible() ? "" : " -n");
        cmd += " -e " + barkode->type();
    }
    
    if( !readFromPipe( cmd.latin1(), postscript, postscript_size ) )
        return false;

    return true;
}

TQRect PixmapBarcode::bbox( const char* data, long size )  
{
    int x = 0, y = 0, w = 0, h = 0;
    const char* bbox = "%%BoundingBox:";
    int len = strlen( bbox );

    TQRect s(0,0,0,0);
    TQByteArray array;
    array.setRawData( data, size );
    
    TQBuffer b( array );
    if( !b.open( IO_ReadOnly ) )
        return s;
    
    TQTextStream t( &b );
        
    TQString text = t.readLine();
    while( !text.isNull() )
    {
        if( text.startsWith( bbox ) )
        {
            text = text.right( text.length() - len );
            sscanf( text.latin1(), "%d %d %d %d", &x, &y, &w, &h );
            s = TQRect( x, y, w, h );
            break;
        }

	text = t.readLine();
    }

    b.close();
    array.resetRawData( data, size );

    return s;
}

bool PixmapBarcode::readFromPipe( const char* command, char** buffer, long* buffer_size )
{
    FILE* pipe = popen( command, "r" );
    if( !pipe )
    {
        tqDebug("ERROR: cannot open pipe %s!", command );
        return false;
    }

    char* buf = (char*)malloc( BUFFER_SIZE );
    char* tmp = NULL;
    int s     = 0;

    *buffer_size = 0;
    do {
        s = fread( buf, sizeof(char), BUFFER_SIZE, pipe );

        // Special case:
        // GNU Barcode Error
        if( !s ) 
            break;

        // there won't be more data to read
        tmp = (char*)malloc( *buffer_size + s );
        
        if( *buffer ) 
        {
            memcpy( tmp, *buffer, *buffer_size );
            free( *buffer );
        }
        
        memcpy( tmp+ *buffer_size, buf, s );
        *buffer = tmp;
        *buffer_size += s;
    } while( s == BUFFER_SIZE );
    
    pclose( pipe );
    free( buf );

    return true;
}

void PixmapBarcode::createBarcode( TQPixmap* target, const TQPaintDevice* device )
{
    TQPaintDeviceMetrics pdm( device );
    int resx = pdm.logicalDpiX();
    int resy = pdm.logicalDpiY();

    TQPixmap* cached = 0;//BarcodeCache::instance()->read( barcode, resx, resy, value );

    // no matching barcode found in cache
    if( !cached ) {
        if( !createPixmap( target, resx, resy ) )
            return;
    } else {
        *target = *cached;
        delete cached;
    }
    
    if( Barkode::hasFeature( barkode->type(), PDF417 ) ) {
        // we have to scale to the correct resolution.
        // we scale already here and not at the end,
        // so that the addMargin function does not get a scaled margin.
        TQPaintDeviceMetrics pdm( TDEApplication::desktop() );
        int screenresx = pdm.logicalDpiX();
        int screenresy = pdm.logicalDpiY();
    
        TQWMatrix m;
        double scalex = (resx/screenresx)*barkode->scaling();
        double scaley = (resy/screenresy)*barkode->scaling();
        m.scale( scalex, scaley );
        *target = target->xForm( m );
    }
    *target = cut( target, barkode->cut() );
    *target = addMargin( target );

    // Rotate
    TQWMatrix m;
    m.rotate( (double)barkode->rotation() );
    *target = target->xForm( m );

    //barcode.valid = true;
}

bool PixmapBarcode::createPdf417( KTempFile* output )
{
    const PDF417Options* options = (dynamic_cast<const PDF417Options*>(barkode->engine()->options()));

    if( !options ) 
    {
        tqDebug("No PDF417Options available!");
        return false;
    }

    KTempFile text( TQString(), ".txt" );
    TQTextStream t( text.file() );
    t << barkode->parsedValue();
    text.file()->close();

    // ps does not work because bounding box information is missing
    // pbm cannot be loaded by KImgIO (works fine in GIMP)
    // gif is the only other option
    KShellProcess proc;
    proc << "pdf417_enc" << "-tgif" << text.name() << output->name()
         << options->row()
         << options->col()
         << options->err();
         
    proc.start( TDEProcess::Block, TDEProcess::NoCommunication );
    proc.resume();

    if( proc.exitStatus() ) {
        text.unlink();
        return false;
    }

    text.unlink();    
    return true;    
}

#if 0
TQString PixmapBarcode::createTBarcodeCmd()
{
    TQString cmd;

    // print text
    TQString flag = barkode->textVisible() ? " Ton" : " n Toff"; // we pass the old parameter Ton and the new one: n
    // escape text
    flag.append( barkode->tbarcodeOptions()->escape() ? " son" : " soff" );
    // autocorrection
    flag.append( barkode->tbarcodeOptions()->autocorrect() ? " Aon" : " Aoff" );
    // barcode height
    flag.append( TQString( " h%1" ).arg( barkode->tbarcodeOptions()->height() ) );
    // text above
    if( barkode->tbarcodeOptions()->above() )
        flag.append( " a" );
    
    cmd = "tbarcodeclient "; 
    if( !Barkode::hasFeature( barkode->type(), BARCODE2D ) )
        cmd += TQString( " m%1" ).arg( barkode->tbarcodeOptions()->moduleWidth() * 1000 );

    if( Barkode::hasFeature( barkode->type(), DATAMATRIX ) ) 
        cmd += TQString( " Ds%1" ).arg( barkode->datamatrixSize() );

    if( Barkode::hasFeature( barkode->type(), PDF417BARCODE ) )
        cmd += TQString( " Pr%1 Pc%2 Pe%3" ).arg( barkode->pdf417Options()->row() )
                                           .arg( barkode->pdf417Options()->col() )
                                           .arg( barkode->pdf417Options()->err() );
        
    cmd += " " + barkode->type() + TQString(" tPS c%1").arg( barkode->tbarcodeOptions()->checksum() );
    cmd += flag + " d" + KShellProcess::quote(  barkode->parsedValue() );

    return cmd;
}
#endif // 0

void PixmapBarcode::cleanUp( KTempFile* file, TQPixmap* target )
{
    target->resize( 0, 0 );

    file->unlink();
    delete file;
}

TQPixmap PixmapBarcode::cut( TQPixmap* pic, double cut)
{
    if( cut == 1.0 )
        return (*pic);

    TQPixmap pcut( pic->width(), int((double)pic->height() * cut) );
    pcut.fill( TQt::white ); // barcode.bg

    TQWMatrix m;
    /*
     * if text is above the barcode cut from
     * below the barcode.
     */

    // TODO: put this into one if, I am to stupid today.....
    if( Barkode::hasFeature( barkode->type(), TBARCODEADV ) ) {
        //if( !barcode.tbarcode.above )
            m.rotate( 180 );
    } else
        m.rotate( 180 );

    TQPainter painter( &pcut );
    painter.drawPixmap( 0, 0, pic->xForm( m ) );

    return pcut.xForm( m );
}

TQPixmap PixmapBarcode::addMargin( TQPixmap* pic )
{
    TQPixmap p;

    /* We have to handle UPC special because of the checksum character
     * which is printed on the right margin.
     * The samve goes for ISBN codes.
     * Any other formats??
     */

    bool gnubarcode = (Barkode::engineForType( barkode->type() ) == GNU_BARCODE) ||
        (Barkode::engineForType( barkode->type() ) == PURE_POSTSCRIPT);
    double barm = gnubarcode ? BARCODE_MARGIN * barkode->scaling() : 0.0;

    // Add margin
    double sx = barm;
    double sy = barm;
    double sw = pic->width()  - (barm * 2);
    double sh = pic->height() - (barm * 2);
    int margin = (int)(barkode->quietZone()*2 - barm*2);

    if( gnubarcode && (barkode->type() == "upc" || barkode->type() == "isbn") ) 
    {
        sw = pic->width() - barm;

        p.resize( pic->width() + int(barkode->quietZone()*2 - barm), pic->height() + margin );
    } 
    else
        p.resize( pic->width() + margin, pic->height() + margin );

    p.fill( barkode->background() );
    TQPainter painter( &p );
    painter.drawPixmap( barkode->quietZone(), barkode->quietZone(), *pic, (int)sx, (int)sy, (int)sw, (int)sh );
    painter.end();

    return p;
}
