/***************************************************************************
                          definition.cpp  -  description
                             -------------------
    begin                : Mit Nov 20 2002
    copyright            : (C) 2002 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 "definition.h"
#include "sqltables.h"

// TQt includes
#include <tqtextstream.h>
#include <tqsqlquery.h>
#include <tqregexp.h>

// KDE includes
#include <tdeapplication.h>
#include <tdefiledialog.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdestandarddirs.h>
#include <tqprogressdialog.h>

// a simple helper function
// that copies a file
bool filecopy( const char* src, const char* dest )
{
    FILE* s;
    FILE* d;
    int c;

    d = fopen(dest, "w");
    if( d == NULL )
        return false;

    s = fopen(src, "r");
    if( s == NULL ) {
        fclose( d );
        remove( dest );
        return false;
    }

    while(( c = getc( s )) != EOF )
        putc( c, d );

    fclose( s );
    fclose( d );
    return true;
}

class PrivateParser {
    public:
        PrivateParser( TQString line, bool all = true );
        ~PrivateParser();

        const TQString & getId() const { return m_label_def_id; }
        const TQString & getProducer() const { return m_producer; }
        const TQString & getType() const { return m_type; }
        const Measurements & getMeasurements() const { return m_measure; };

    private:
        TQString removeQuote( const TQString & q );

        TQString m_label_def_id;
        TQString m_producer;
        TQString m_type;

        Measurements m_measure;
};

PrivateParser::PrivateParser( TQString line, bool all )
{
    line = line.stripWhiteSpace();
    int pos = line.find("(");
    line = line.mid( pos + 1, line.length() - pos - 1 );

    m_label_def_id = TQString(line.section( ",", 0, 0 )).stripWhiteSpace();
    m_producer     = removeQuote( line.section( ",", 1, 1 ) );
    m_type         = removeQuote( line.section( ",", 2, 2 ) );

    if( all ) {
        m_measure.setGapTopMM( line.section( ",", 4, 4 ).toDouble() );
        m_measure.setGapLeftMM( line.section( ",", 5, 5 ).toDouble() );
        m_measure.setHeightMM( line.section( ",", 6, 6 ).toDouble() );
        m_measure.setWidthMM( line.section( ",", 7, 7 ).toDouble() );
        m_measure.setGapVMM( line.section( ",", 8, 8 ).toDouble() );
        m_measure.setGapHMM( line.section( ",", 9, 9 ).toDouble() );
        m_measure.setNumH( line.section( ",", 10, 10 ).toInt() );
        m_measure.setNumV( line.section( ",", 11, 11 ).toInt() );

        // fix broken label definitions
        // with numh and numv = 0
        if( !m_measure.numH() )
            m_measure.setNumH( 1 );

        if( !m_measure.numV() )
            m_measure.setNumV( 1 );
    }
}

PrivateParser::~PrivateParser()
{ }

TQString PrivateParser::removeQuote( const TQString & q )
{
    TQString quote = q.stripWhiteSpace();

    if( quote.startsWith("'") )
        quote = quote.mid( 1, quote.length() - 1 );

    if( quote.endsWith("'") )
        quote = quote.left( quote.length() - 1 );

    return quote;
}


/***************************************************************************/

Definition::Definition( TQWidget* parent )
    : m_parent( parent )
{
    id = -1;
}

Definition::Definition( int label_def_id, TQWidget* parent )
    : m_parent( parent )
{
    init( TQString("%1").arg( label_def_id ) );
}

Definition::Definition( const TQString & label_def_id, TQWidget* parent )
    : m_parent( parent )
{
    init( label_def_id );
}

Definition::Definition( const TQString & producer, const TQString & type, TQWidget* parent )
    : m_parent( parent )
{
    if( SqlTables::isConnected() ) {
        TQSqlQuery query(
            "select label_no from " TABLE_LABEL_DEF " WHERE manufacture='" + producer + "' AND type='" + type + "'");
        while( query.next() )
            init( query.value( 0 ).toString() );
    } else {
        if(!openFile())
            return;

	initProgress();

        TQString s;
        while( file->readLine( s, 1000 ) != -1 ) {
            if( s.isEmpty() || s.left( 1 ) == "#" )
                continue;

	    increaseProgress();

            PrivateParser p( s );
            if( p.getProducer() ==producer && p.getType() == type ) {
                init( p.getId() );
                break;
            }
        }

	destroyProgress();
    }

}

Definition::~Definition()
{
}

TQFile* Definition::file = 0;
TQByteArray* Definition::array = 0;
TQStringList* Definition::listProducers = 0;
TQMap<TQString,TQStringList> Definition::mapTypes;
TQProgressDialog* Definition::m_progress = 0;

void Definition::initProgress()
{
/*
    if(!m_progress)
	m_progress = new TQProgressDialog( i18n("Loading Label Definitions..."), TQString(), 0, NULL, "m_progress", true );

    m_progress->setTotalSteps( 1000 );
    m_progress->show();
*/
//    m_progress->reparent( m_parent, m_progress->pos(), true );
}

void Definition::increaseProgress()
{
/*
    if( m_progress )
	m_progress->setProgress( m_progress->progress() + 1 );
*/
}

void Definition::destroyProgress()
{
/*
    if( m_progress )
	delete m_progress;
    m_progress = NULL;
*/
}

void Definition::setId( const TQString & label_def_id )
{
    init( label_def_id );
}

void Definition::setId( int label_def_id )
{
    init( TQString("%1").arg( label_def_id ) );
}

void Definition::init( const TQString & label_def_id )
{
    if( SqlTables::isConnected() ) {
        TQSqlQuery* query = new TQSqlQuery(
            "select number_h, number_v, gap_left, gap_top, gap_v, gap_h, width, height, manufacture, type from " TABLE_LABEL_DEF
            " WHERE label_no = " + label_def_id );

        while( query->next() ) {
            m_measure.setNumH( query->value( 0 ).toInt() );
            m_measure.setNumV( query->value( 1 ).toInt() );
            m_measure.setGapLeftMM( query->value( 2 ).toDouble() );
            m_measure.setGapTopMM( query->value( 3 ).toDouble() );
            m_measure.setGapVMM( query->value( 4 ).toDouble() );
            m_measure.setGapHMM( query->value( 5 ).toDouble() );
            m_measure.setWidthMM( query->value( 6 ).toDouble() );
            m_measure.setHeightMM( query->value( 7 ).toDouble() );
            producer = query->value( 8 ).toString();
            type = query->value( 9 ).toString();
        }
    } else {
        getFileMeasurements( label_def_id );
    }

    id = label_def_id.toInt();

}

const Measurements & Definition::getMeasurements() const
{
    return m_measure;
}

void Definition::getFileMeasurements( const TQString & label_def_id )
{
    if(!openFile()) {
        m_measure = Measurements();
        return;
    }

    initProgress();

    TQTextStream stream(*array, IO_ReadOnly );
    while( !stream.atEnd() ) {
        TQString s = stream.readLine();
        if( s.isEmpty() || s.startsWith( "#" ) )
            continue;

	increaseProgress();

        PrivateParser p( s );
        if( p.getId() != label_def_id )
            continue;

        producer  = p.getProducer();
        type      = p.getType();
        m_measure = p.getMeasurements();
        break;
    }

    destroyProgress();
}

bool Definition::openFile()
{
    if( file ) {
        file->at( 0 );
        return true;        
    }

    TQString f = locateLocal( "data", "kbarcode/labeldefinitions.sql" );
    if( !TQFile::exists( f ) ) {
        TDEConfig* config = tdeApp->config();
        config->setGroup( "Definitions" );

        // copy file to new location
        TQString fname = config->readEntry( "defpath", locate( "data", "kbarcode/labeldefinitions.sql" ) );
        if( !TQFile::exists( fname ) || fname.isEmpty() ) 
            return ( showFileError() ? openFile() : false );

        if(!filecopy( fname.local8Bit(), f.local8Bit() ))
            return ( showFileError() ? openFile() : false );
    }

    file = new TQFile( f );
    if( !file->open( IO_ReadOnly ) ) {
        delete file;
        file = 0;
        return ( showFileError() ? openFile() : false );
    }

    // keeping this array around
    // increases speed quite a lot
    // but does also cost lot's of memory
    array = new TQByteArray();
    *array = file->readAll();
    file->at( 0 );

    return true;
}

const TQStringList Definition::getProducers()
{
    if( listProducers )
        return *listProducers;

    listProducers = new TQStringList();

    if( SqlTables::isConnected() ) {
        TQSqlQuery query("SELECT manufacture FROM " TABLE_LABEL_DEF " GROUP by manufacture;");
        while( query.next() )
            listProducers->append( query.value( 0 ).toString() );
    } else {
        if(!openFile() )
            return *listProducers;

	initProgress();

        TQTextStream stream(*array, IO_ReadOnly );
        while( !stream.atEnd() ) {
            TQString s = stream.readLine();
            if( s.isEmpty() || s.startsWith( "#" ) )
                continue;

	    increaseProgress();

            PrivateParser p( s, false );
            if( !listProducers->contains( p.getProducer() ) )
                listProducers->append( p.getProducer() );
        }

	destroyProgress();
    }

    return *listProducers;
}

const TQStringList Definition::getTypes( TQString producer )
{
    if( mapTypes.contains( producer ) ) {
        return mapTypes[producer];
    }

    TQStringList list;

    if( SqlTables::isConnected() ) {
        TQSqlQuery query("SELECT type FROM " TABLE_LABEL_DEF " WHERE manufacture='" + producer + "'" );
        while( query.next() )
            if( !list.contains( query.value( 0 ).toString() ) )
                    list.append( query.value( 0 ).toString() );
    } else {
        if(!openFile())
            return list;

	initProgress();

        TQTextStream stream(*array, IO_ReadOnly );
        while( !stream.atEnd() ) {
            TQString s = stream.readLine();
            if( s.isEmpty() || s.startsWith( "#" ) )
                continue;

	    increaseProgress();

            PrivateParser p( s, false );
            if( p.getProducer() == producer )
                if( !list.contains( p.getType() ) )
                    list.append( p.getType() );
        }

	destroyProgress();
    }

    mapTypes.insert( producer, list );

    return list;
}

void Definition::updateProducer()
{
    // TODO: check wether this function is
    // correct! w/ SQL and without
    if( listProducers ) {
        delete listProducers;
        listProducers = 0;

        if( array ) {
            delete array;
            array = 0;
        }

        if( file ) {
            file->close();
            delete file;
            file = 0;
        }
        mapTypes.clear();
    }
}

int Definition::write( const Measurements & c, TQString type, TQString producer )
{
    int r = -1;
    if( SqlTables::isConnected() )
        r = Definition::writeSQL( c, type, producer );
    else
        r = Definition::writeFile( c, type, producer ); 

    Definition::updateProducer();
    return r;
}

int Definition::writeFile( const Measurements & c, TQString type, TQString producer )
{
    if( !openFile() )
        return -1;
        
    TQStringList data;
    bool datawritten = false;
    int index = 0;
    TQString entry = ", '" +
                  producer + "', '" + type + "', 'C',"+ I2S(c.gapTopMM()) +
                  ", " + I2S(c.gapLeftMM()) + ", " +
                  I2S(c.heightMM()) + ", " + I2S(c.widthMM()) + ", " +
                  I2S(c.gapVMM()) + ", " + I2S(c.gapHMM()) + ", " +
                  I2S(c.numH()) + ", " + I2S(c.numV()) + ", NULL, NULL )";
        
    TQString s;
    while( file->readLine( s, 1000 ) != -1 ) {
        if( s.isEmpty() || s.left( 1 ) == "#" ) {
            data.append( s );
            continue;
        }
            
        PrivateParser p( s );
        if( p.getId().toInt() > index )
            index = p.getId().toInt();

        if( p.getType() == type && p.getProducer() == producer ) {
            // update an item already present in the list
            entry = entry.prepend( "INSERT INTO " TABLE_LABEL_DEF " VALUES (" + I2S(p.getId().toInt()) );
            data.append( entry );
            datawritten = true;
        } else
            data.append( s );
    }

    if( !datawritten ) {
        entry = entry.prepend( "INSERT INTO " TABLE_LABEL_DEF " VALUES (" + I2S(index+1) );
        data.append( entry );
    }
    
    file->close();
    if( !file->open( IO_WriteOnly ) ) {
        file->open( IO_ReadOnly );
        return -1;
    }

    TQTextStream t( file );
    for( unsigned int i = 0; i < data.count(); i++ )
        t << data[i].replace( TQRegExp("\\n"), "" ) << "\n";

    // get the file back to the normal stage
    file->close();
    file->open( IO_ReadOnly );

    return index + 1;
}

int Definition::writeSQL( const Measurements & c, TQString type, TQString producer )
{
    bool newitem = true;
    TQSqlQuery q( "SELECT manufacture, type FROM " TABLE_LABEL_DEF );
    // TODO: use a more inteligent query using where=
    while( q.next() )
        if( q.value( 0 ) == producer &&
            q.value( 1 ) == type )
                newitem = false;

    if( newitem ) {
        TQSqlQuery query(
              "INSERT INTO " TABLE_LABEL_DEF " (manufacture, type, gap_top, gap_left, "
              "width, height, gap_v, gap_h, number_h, number_v) VALUES ('" +
              producer + "', '" + type + "', '"+ I2S( c.gapTopMM() ) +
              "', '" + I2S( c.gapLeftMM() ) + "', '" +
              I2S( c.widthMM() ) + "', '" + I2S( c.heightMM() ) + "', '" +
              I2S( c.gapVMM() ) + "', '" + I2S( c.gapHMM() ) + "', '" +
              I2S( c.numH() ) + "', '" + I2S( c.numV() ) + "')"
              );

        if(!query.isValid())
            tqDebug("Query to insert values not valid!");
    } else {
        TQSqlQuery query( "UPDATE " TABLE_LABEL_DEF " SET "
            "gap_top = " + I2S( c.gapTopMM() ) + " ,gap_left = " + I2S( c.gapLeftMM() ) +
            " ,width = " + I2S( c.widthMM() ) + " ,height = " + I2S( c.heightMM() ) +
            " ,gap_v = " + I2S( c.gapVMM() ) + " ,gap_h = " + I2S( c.gapHMM() ) +
            " ,number_h = " + I2S( c.numH() ) + " ,number_v = " + I2S( c.numV() ) +
            " WHERE manufacture = '" + producer + "' AND"
            " type = '" + type + "'" );

        if(!query.isValid())
            tqDebug("Query to update values not valid!\n%s\n", query.lastQuery().latin1() );
    }

    TQSqlQuery qi("SELECT label_no FROM " TABLE_LABEL_DEF " WHERE manufacture='" + producer + "' AND type='" + type + "'" );
    while( qi.next() )
        return qi.value( 0 ).toInt();

    return -1;
}


bool Definition::nodefmsg = true;
bool Definition::showFileError()
{
    if( nodefmsg ) {
        KMessageBox::information( 0,
            i18n("KBarcode is unable to find its label definitions."
                 "Please make sure that the file $TDEDIR/share/apps/kbarcode/labeldefinitions.sql "
                 "does exist. This file is part of the KBarcode distribution. "
                 "You will be prompted now to select the file containing the labeldefinitions."),
                 "", "NoDefinitionsFound" );

        TQString f = KFileDialog::getOpenFileName( TQString(), TQString(), 0 );
        if( !f.isEmpty() && TQFile::exists( f ) ) {
            TDEConfig* config = tdeApp->config();
            config->setGroup( "Definitions" );
            config->writeEntry( "defpath", f );
            config->sync();
        }
        nodefmsg = false;
        return openFile();
    } else
        tqDebug("No label definitions found. Please install them.");

    return false;
}

int Definition::getClosest( const TQString & producer, const TQString & type )
{
    TQStringList t = Definition::getTypes(producer); 
    for( unsigned int z = 0; z < t.count(); z++ )  {
        if( t[z] == type ) {
            Definition d( producer, type );
            return d.getId();
        }
    }

    return -1;
}
