/***************************************************************************
                          sqltables.cpp  -  description
                             -------------------
    begin                : Son Dez 29 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 "sqltables.h"
#include "definition.h"

// TQt includes
#include <tqcheckbox.h>
#include <tqfile.h>
#include <tqgroupbox.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqmap.h>
#include <tqprogressdialog.h>
#include <tqsqldatabase.h>

// KDE includes
#include <tdeapplication.h>
#include <kcombobox.h>
#include <tdeconfig.h>
#include <klineedit.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpushbutton.h>
#include <kstandarddirs.h>

TQMap<TQString,SqlDescription*> drivers;

class MySqlDescription : public SqlDescription {
    public:
        virtual const TQString initdb( TQString ) const { return "mysql"; }
        virtual const TQString autoIncrement() const { return "int(11) NOT NULL auto_increment"; }
        virtual const TQString showColumns( const TQString & table ) const { return "SHOW COLUMNS FROM " + table; }
};

class PostgreSQLDescription : public SqlDescription {
    public:
        virtual const TQString initdb( TQString ) const { return "template1"; }
        virtual const TQString autoIncrement() const { return "serial NOT NULL"; }
        virtual const TQString showColumns( const TQString & table ) const {
            return "select a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), "
                   "a.attnotnull, a.atthasdef, a.attnum, substring(d.adsrc for 128) "
                   "from pg_catalog.pg_attribute a "
                   "inner join pg_catalog.pg_class c on c.oid = a.attrelid "
                   "left join pg_catalog.pg_attrdef d on d.adrelid = c.oid and a.attnum = "
                   "d.adnum "
                   "where a.attnum > 0 and not a.attisdropped and c.relname ~ '^" + table + "$';";
            }
};

/** should work for ODBC, too */
class SQLiteDescription : public SqlDescription {
    public:
        virtual const TQString initdb( TQString db = TQString() ) const { return db; }
        virtual const TQString autoIncrement() const { return "serial NOT NULL"; }
        virtual const TQString showColumns( const TQString & table ) const { return "SHOW COLUMNS FROM " + table; }
};

SqlTables::SqlTables( TQObject* parent )
    : TQObject( parent, "sqltables" )
{
    drivers.insert( "QMYSQL3", new MySqlDescription() );
    drivers.insert( "QPSQL7", new PostgreSQLDescription() );
    /* The same driver plugin is used for TQDBC and SQLite */
    drivers.insert( "QSQLITE", new SQLiteDescription() );
    drivers.insert( "QODBC3", new SQLiteDescription() );

    drivers.insert( "TQMYSQL3", new MySqlDescription() );
    drivers.insert( "TQPSQL7", new PostgreSQLDescription() );
    /* The same driver plugin is used for TQDBC and SQLite */
    drivers.insert( "TQSQLITE", new SQLiteDescription() );
    drivers.insert( "TQODBC3", new SQLiteDescription() );

    db = NULL;
    connected = false;
    loadConfig();

    connect( kapp, TQ_SIGNAL( aboutToQuit() ), this, TQ_SLOT( saveConfig() ) );
}

SqlTables::~SqlTables()
{
}

SqlTables* SqlTables::instance = NULL;

SqlTables* SqlTables::getInstance()
{
    if( !instance )
        instance = new SqlTables();

    return instance;
}

const SqlDescription* SqlTables::driver() const
{
    return drivers[sqldata.driver];
}
bool SqlTables::connectMySQL()
{
    db = TQSqlDatabase::addDatabase( sqldata.driver );

    db->setDatabaseName( sqldata.database );
    db->setUserName( sqldata.username );
    db->setPassword( sqldata.password );
    db->setHostName( sqldata.hostname );

    if( !db->open() )
        KMessageBox::error( 0, i18n("<qt>Unable to open database: ") + sqldata.database + "<br>" +
              db->lastError().databaseText() + "</qt>");

    connected = db->open();
    if( connected ) {
        updateTables();
        emit connectedSQL();
        // be sure that we have a clean list of producers
        Definition::updateProducer();
    }

    return connected;
}

bool SqlTables::newTables()
{
    return newTables( sqldata.username, sqldata.password, sqldata.hostname, sqldata.database, sqldata.driver );
}

bool SqlTables::newTables( const TQString & username, const TQString & password, const TQString & hostname, const TQString & database, const TQString & driver )
{
	if( KMessageBox::warningContinueCancel( 0,
			i18n("We are going to re-create the tables '") +
				TQString( TABLE_BASIC "', '" TABLE_CUSTOMER "', '"
				TABLE_CUSTOMER_TEXT) +i18n("' and '") + TQString(TABLE_LABEL_DEF "'")) ==  KMessageBox::Cancel )
		return false;
	
	if( !drivers[driver] )
		return false;
	
	TQSqlDatabase*dbase = TQSqlDatabase::addDatabase(driver, drivers[driver]->initdb( database ) );
	dbase->setDatabaseName( drivers[driver]->initdb( database ) );
	dbase->setUserName( username );
	dbase->setPassword( password );
	dbase->setHostName( hostname );
	
	if (dbase->open()) {
		if ((driver != "QSQLITE") && (driver != "TQSQLITE"))
		{
			bool found = false;
			TQSqlQuery existing("SHOW DATABASES LIKE '" + database + "';");
			while( existing.next() )
				found = true;
		
			TQSqlQuery firstquery( NULL, dbase );
			if( !found && !firstquery.exec("CREATE DATABASE " + database + ";")) {
				if( KMessageBox::warningContinueCancel( 0, i18n("<qt>Can't create database ") + database + i18n("<br>You can continue if the database exists already.</qt>")
					+ firstquery.lastError().databaseText() ) == KMessageBox::Cancel ) {
					dbase->close();
					TQSqlDatabase::removeDatabase(drivers[driver]->initdb( database ));
					return false;
				}
			}
		}
		dbase->close();
		TQSqlDatabase::removeDatabase(drivers[driver]->initdb( database ));
	} else {
		TQSqlError dbError = dbase->lastError();
		dbase->close();
		TQSqlDatabase::removeDatabase(drivers[driver]->initdb( database ));
		if (KMessageBox::warningContinueCancel(0, i18n("<qt>Can't connect to database to create table.") + "<p>" + dbError.driverText() + "<br>" + dbError.databaseText() + i18n("<br>You can continue if the table exists already.</qt>")) == KMessageBox::Cancel) {
			return false;
		}
	}

	// The database is created, now connect to the one specified by the user
	dbase = TQSqlDatabase::addDatabase(driver, database );
	dbase->setDatabaseName( database );
	dbase->setUserName( username );
	dbase->setPassword( password );
	dbase->setHostName( hostname );
		if(!dbase->open() || !dbase->isOpen()) {
		KMessageBox::error( 0, i18n("KBarcode could not create the required database. Please create it manually.") + dbase->lastError().databaseText() );
		TQSqlDatabase::removeDatabase( database );
		return false;
	}

	TQSqlQuery query( NULL, dbase );

	// barcode_basic
	query.exec("DROP TABLE " TABLE_BASIC );
	exec( &query, "CREATE TABLE " TABLE_BASIC " ("
		"    uid " + drivers[driver]->autoIncrement() + ","
		"    article_no varchar(50) DEFAULT NULL,"
		"    article_desc varchar(50) DEFAULT NULL,"
		"    barcode_no TEXT DEFAULT NULL,"
		"    encoding_type varchar(50) DEFAULT NULL,"
		"    field0 varchar(50) DEFAULT NULL,"
		"    field1 varchar(50) DEFAULT NULL,"
		"    field2 varchar(50) DEFAULT NULL,"
		"    field3 varchar(50) DEFAULT NULL,"
		"    field4 varchar(50) DEFAULT NULL,"
		"    field5 varchar(50) DEFAULT NULL,"
		"    field6 varchar(50) DEFAULT NULL,"
		"    field7 varchar(50) DEFAULT NULL,"
		"    field8 varchar(50) DEFAULT NULL,"
		"    field9 varchar(50) DEFAULT NULL,"
		"    PRIMARY KEY  (uid)"
		");" );

	// customer
	query.exec("DROP TABLE " TABLE_CUSTOMER );
	exec( &query, "CREATE TABLE " TABLE_CUSTOMER " ("
		"    uid " + drivers[driver]->autoIncrement() + " ,"
		"    customer_no varchar(20) DEFAULT NULL,"
		"    customer_name varchar(20) DEFAULT NULL,"
		"    PRIMARY KEY  (uid)"
		");" );

	// customer_text
	query.exec("DROP TABLE " TABLE_CUSTOMER_TEXT );
	exec( &query, "CREATE TABLE " TABLE_CUSTOMER_TEXT " ("
		"    uid " + drivers[driver]->autoIncrement() + ","
		"    customer_no varchar(20) DEFAULT NULL,"
		"    encoding_type varchar(50) DEFAULT NULL,"
		"    article_no varchar(50) DEFAULT NULL,"
		"    article_no_customer varchar(50) NULL,"
		"    barcode_no TEXT DEFAULT NULL,"
		"    line0 varchar(50) DEFAULT NULL,"
		"    line1 varchar(50) DEFAULT NULL,"
		"    line2 varchar(50) DEFAULT NULL,"
		"    line3 varchar(50) DEFAULT NULL,"
		"    line4 varchar(50) DEFAULT NULL,"
		"    line5 varchar(50) DEFAULT NULL,"
		"    line6 varchar(50) DEFAULT NULL,"
		"    line7 varchar(50) DEFAULT NULL,"
		"    line8 varchar(50) DEFAULT NULL,"
		"    line9 varchar(50) DEFAULT NULL,"
		"    PRIMARY KEY  (uid)"
		");" );

	// label_def
	query.exec("DROP TABLE " TABLE_LABEL_DEF );
	exec( &query, "CREATE TABLE " TABLE_LABEL_DEF " ("
		"    label_no " + drivers[driver]->autoIncrement() + ","
		"    manufacture varchar(255) DEFAULT NULL,"
		"    type varchar(255) DEFAULT NULL,"
		"    paper char(1) DEFAULT NULL,"
		"    gap_top NUMERIC(10,4) NULL,"
		"    gap_left NUMERIC(10,4) NULL,"
		"    height NUMERIC(10,4) NULL,"
		"    width NUMERIC(10,4) NULL,"
		"    gap_v NUMERIC(10,4) NULL,"
		"    gap_h NUMERIC(10,4) NULL,"
		"    number_h int DEFAULT NULL," //smalint(6)
		"    number_v int DEFAULT NULL," //smalint(6)
		"    paper_type varchar(30) DEFAULT NULL,"
		"    compatibility varchar(10) DEFAULT NULL," // keep compatibility with older versions, was "remark text"
		"    PRIMARY KEY  (label_no)"
		");" );

	dbase->close();
	TQSqlDatabase::removeDatabase( database );
	KMessageBox::information( 0, i18n("Created table ")+database+i18n(" successfully!") );

	return true;
}

void SqlTables::importLabelDef()
{
    if( KMessageBox::warningContinueCancel( 0, i18n("We are going to delete the complete table: " ) + TABLE_LABEL_DEF ) ==
        KMessageBox::Cancel )
        return;

    TQSqlQuery query( TQString(), db );
    exec( &query, "delete from " TABLE_LABEL_DEF );

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

    importData( f, db );

    Definition::updateProducer();
}

void SqlTables::importExampleData()
{
    if( KMessageBox::warningContinueCancel( 0,
        i18n("We are going to delete the complete tables: " ) +  TQString( TABLE_BASIC ", " TABLE_CUSTOMER ", " TABLE_CUSTOMER_TEXT ) ) ==
        KMessageBox::Cancel )
        return;

    importData( locate("appdata", "exampledata.sql"), db );
}

void SqlTables::importData( const TQString & filename, TQSqlDatabase* db )
{
    if( !db ) {
        tqDebug("Can't import data, dabase not open!");
        return;
    }

    if( filename.isEmpty() || !db->isOpen() ) // quick escape
    {
        KMessageBox::error( NULL, i18n("Data file for import not found. Continuing without importing data. Please check your KBarcode installation.") );
        return;
    }

    TQFile data( filename);
    TQProgressDialog* dlg = new TQProgressDialog( i18n("SQL import progress:"),  TQString(), data.size(), 0, "dlg", true );

    if( data.open( IO_ReadOnly ) ) {
        TQString s;
        TQSqlQuery query( TQString(), db );
        while( data.readLine( s, 1024 ) != -1 )
            if( !s.isEmpty() ) {
                dlg->setProgress( dlg->progress() + s.length() );
                exec( &query, s );
            }
    } else
        KMessageBox::sorry( 0, i18n("Can't open the data file containing the label definitions.") );

    dlg->close( true );
    data.close();
}

void SqlTables::exec( TQSqlQuery* query, const TQString & text )
{
    if( !query->exec( text ) )
        KMessageBox::sorry( 0, i18n("<qt>Can't execute command:<br><b>") + text + "</b><br></qt>" + query->lastError().databaseText(), "sqlexecerror" );
}

void SqlTables::loadConfig()
{
    TDEConfig* config = kapp->config();

    config->setGroup("SQL");
    sqldata.username = config->readEntry("username", "root");
    sqldata.password = config->readEntry("password", "" );
    sqldata.hostname = config->readEntry("hostname", "localhost" );
    sqldata.database = config->readEntry("database", "kbarcode" );
    sqldata.driver = config->readEntry("driver", "TQMYSQL3" );
    sqldata.autoconnect = config->readBoolEntry("autoconnect", false );
}

void SqlTables::saveConfig()
{
    TDEConfig* config = kapp->config();

    config->setGroup("SQL");
    config->writeEntry("username", sqldata.username );
    config->writeEntry("password", sqldata.password );
    config->writeEntry("hostname", sqldata.hostname );
    config->writeEntry("database", sqldata.database );
    config->writeEntry("driver", sqldata.driver );
    config->writeEntry("autoconnect", sqldata.autoconnect );

    config->sync();
}

void SqlTables::updateTables()
{
    /* Older versions of KBarcode did only
     * support MySQL. As a reason, databases
     * do not need to be updated for other DBs
     * as they have already been created with the most
     * recent database structures.
     */
    if ((sqldata.driver != "QMYSQL3") && (sqldata.driver != "TQMYSQL3"))
        return;

    bool changed = false;
    // check for field0 - field9 (1.4.0)
    TQStringList fields;
    for( unsigned int i = 0; i < 10; i++ )
        fields.append( TQString("field%1").arg( i )  );

    TQSqlQuery query("SHOW FIELDS FROM " TABLE_BASIC );
    while ( query.next() )
        if( fields.grep( query.value( 0 ).toString(), false ).count() ) {
            fields.remove( query.value( 0 ).toString() );
        }

    if( fields.count() ) {
        TQSqlQuery q;
        for( unsigned int i = 0; i < 10; i++ )
            q.exec("ALTER TABLE " TABLE_BASIC " ADD " + fields[i] + " varchar(50)");

        tqDebug("changing fields");
        changed = true;
    }

    // change barcode_no from varchar to TEXT (1.5.0)
    TQSqlQuery query2("SHOW FIELDS FROM " TABLE_BASIC );
    while( query2.next() )
        if( query2.value( 0 ).toString() == "barcode_no" && query2.value(1) == "varchar(50)" ) {
            query2.exec( "ALTER TABLE " TABLE_BASIC " CHANGE barcode_no barcode_no TEXT" );
            changed = true;
        }

    // change barcode_no from varchar to TEXT (1.5.0)
    TQSqlQuery query3("SHOW FIELDS FROM " TABLE_CUSTOMER_TEXT );
    while( query3.next() )
        if( query3.value( 0 ).toString() == "barcode_no" && query3.value(1) == "varchar(50)" ) {
            query3.exec( "ALTER TABLE " TABLE_CUSTOMER_TEXT " CHANGE barcode_no barcode_no TEXT" );
            changed = true;
        }

    // change NUMERIC DEFAULT TO NUMERIC(10,4) (1.6.1)
    TQStringList update;
    update << "gap_top" << "gap_left" << "height" << "width" << "gap_v" << "gap_h";
    TQSqlQuery query4("SHOW FIELDS FROM " TABLE_LABEL_DEF );
    while( query4.next() )
        if( update.contains( query4.value( 0 ).toString() ) && query4.value(1).toString() == "decimal(10,0)" ) {
            TQSqlQuery q;
            q.exec( TQString("ALTER TABLE " TABLE_LABEL_DEF " CHANGE ") +
                    query4.value( 0 ).toString() + " " + query4.value( 0 ).toString() + " NUMERIC(10,4)" );
            changed = true;
        }

    if( changed )
        KMessageBox::information( 0, i18n("The SQL tables of KBarcode have changed since the last version. "
                                          "KBarcode updated them without any loss of data."  ) );
}

bool SqlTables::testSettings( const TQString & username, const TQString & password, const TQString & hostname, const TQString & database, const TQString & driver )
{
    TQSqlDatabase* db = TQSqlDatabase::addDatabase( driver );
    if( !drivers[driver] )
      return false;

    db->setDatabaseName( database );
    db->setUserName( username );
    db->setPassword( password );
    db->setHostName( hostname );

    if( !db->open() ) 
    {
        TQSqlDatabase::removeDatabase( database );
    }
    else 
    {
        KMessageBox::information( 0, i18n("Connected successfully to your database") );
        db->close();
        TQSqlDatabase::removeDatabase( database );
        return true;
    }

    db = TQSqlDatabase::addDatabase( driver );
    
    db->setDatabaseName( drivers[driver]->initdb( database ) );

    db->setUserName( username );
    db->setPassword( password );
    db->setHostName( hostname );

    if( !db->open() ) {
        KMessageBox::error( 0, i18n("<qt>Connection failed:<br>") + database + "<br>" +
              db->lastError().databaseText() + "</qt>" );
        TQSqlDatabase::removeDatabase(drivers[driver]->initdb( database ));
        return false;
    } else {
        KMessageBox::information( 0, i18n("Connected successfully to your database") );
        db->close();
        TQSqlDatabase::removeDatabase(drivers[driver]->initdb( database ));
        return true;
    }

}

const TQString SqlTables::getBarcodeMaxLength( const TQString & name )
{
    if( SqlTables::isConnected() )
    {
        TQSqlQuery query("select uid, (length(barcode_no)) as LEN from " TABLE_BASIC
            " where encoding_type = '" + name +"' ORDER by LEN DESC LIMIT 1" );
        
        while( query.next() ) {
            TQSqlQuery queryuid("select barcode_no from barcode_basic where uid = '"
                + query.value( 0 ).toString() + "'" );
            while( queryuid.next() )
                if(!queryuid.value( 0 ).toString().isEmpty())
                    return queryuid.value( 0 ).toString();
        }
        
        TQSqlQuery query1("select uid, (length(barcode_no)) as LEN from " TABLE_CUSTOMER_TEXT 
                        " where encoding_type = '" + name +"' ORDER by LEN DESC LIMIT 1" );
        
        while( query1.next() ) {
            TQSqlQuery queryuid("select barcode_no from customer_text where uid = '"
                + query1.value( 0 ).toString() + "'" );
            while( queryuid.next() )
                if(!queryuid.value( 0 ).toString().isEmpty())
                    return queryuid.value( 0 ).toString();
        }
    }
    
    return "1234567";        
}

SqlWidget::SqlWidget( bool showlabel, TQWidget* parent, const char* name )
    : TQWidget( parent, name )
{
    TQVBoxLayout* layout = new TQVBoxLayout( this );

    TQGroupBox* groupDatabase = new TQGroupBox( this );
    groupDatabase->setTitle( i18n( "Database Settings" ) );
    groupDatabase->setColumnLayout(0, TQt::Vertical );
    groupDatabase->layout()->setSpacing( 6 );
    groupDatabase->layout()->setMargin( 11 );
    TQVBoxLayout* groupDatabaseLayout = new TQVBoxLayout( groupDatabase->layout() );
    groupDatabaseLayout->setAlignment( TQt::AlignTop );

    TQGridLayout* grid = new TQGridLayout( 2, 2 );

    TQLabel* label = new TQLabel( groupDatabase );
    label->setText( i18n("Username :") );
    grid->addWidget( label, 0, 0 );

    m_username = new KLineEdit( groupDatabase );
    grid->addWidget( m_username, 0, 1 );

    label = new TQLabel( groupDatabase );
    label->setText( i18n("Password :") );
    grid->addWidget( label, 1, 0 );

    m_password = new KLineEdit( groupDatabase );
    m_password->setEchoMode( KLineEdit::Password );
    grid->addWidget( m_password, 1, 1 );

    label = new TQLabel( groupDatabase );
    label->setText( i18n("Database :") );
    grid->addWidget( label, 2, 0 );

    m_database = new KLineEdit( "kbarcode", groupDatabase );
    grid->addWidget( m_database, 2, 1 );

    label = new TQLabel( groupDatabase );
    label->setText( i18n("Host :") );
    grid->addWidget( label, 3, 0 );

    m_hostname = new KLineEdit( "localhost", groupDatabase );
    grid->addWidget( m_hostname, 3, 1 );

    label = new TQLabel( groupDatabase );
    label->setText( i18n("Driver :") );
    grid->addWidget( label, 4, 0 );

    m_driver = new KComboBox( false, groupDatabase );
    TQStringList drList = TQSqlDatabase::drivers();
    TQStringList::Iterator it = drList.begin();
    while( it != drList.end() ) {
        m_driver->insertItem( *it );
        ++it;
    }
    grid->addWidget( m_driver, 4, 1 );
    groupDatabaseLayout->addLayout( grid );

    m_autoconnect = new TQCheckBox( i18n("&Autoconnect on program start"), groupDatabase );
    groupDatabaseLayout->addWidget( m_autoconnect );

    buttonTest = new KPushButton( groupDatabase, "buttonTest" );
    buttonTest->setText( i18n( "&Test Settings" ) );

    groupDatabaseLayout->addWidget( buttonTest );
    if( showlabel )
        groupDatabaseLayout->addWidget( new TQLabel( i18n("<b>You have to test your database settings before you can procede.</b>"), groupDatabase ) );

    layout->add( groupDatabase );

    connect( buttonTest, TQ_SIGNAL( clicked() ), this, TQ_SLOT( testSettings() ) );

    const mysqldata & sqldata = SqlTables::getInstance()->getData();
    m_username->setText( sqldata.username );
    m_password->setText( sqldata.password );
    m_hostname->setText( sqldata.hostname );
    m_database->setText( sqldata.database );
    m_autoconnect->setChecked( sqldata.autoconnect );
    for( int i = 0; i < m_driver->count(); i++ )
        if( m_driver->text(i) == sqldata.driver )
            m_driver->setCurrentItem( i );
}

SqlWidget::~SqlWidget()
{
}

void SqlWidget::save( bool usedb )
{
    mysqldata sqldata = SqlTables::getInstance()->getData();
    sqldata.username = m_username->text();
    sqldata.password = m_password->text();
    sqldata.hostname = m_hostname ->text();
    sqldata.database = m_database->text();
    sqldata.driver = m_driver->currentText();
    sqldata.autoconnect = ( usedb ? m_autoconnect->isChecked() : false );
    SqlTables::getInstance()->setData( sqldata );
}

void SqlWidget::testSettings()
{
    emit databaseWorking(
    SqlTables::getInstance()->testSettings( m_username->text(), m_password->text(),
                                            m_hostname->text(), m_database->text(),
                                            m_driver->currentText() ) );
}

const TQString SqlWidget::username() const
{
    return m_username->text();
}

const TQString SqlWidget::driver() const
{
    return m_driver->currentText();
}

const TQString SqlWidget::database() const
{
    return m_database->text();
}

const TQString SqlWidget::hostname() const
{
    return m_hostname->text();
}

const TQString SqlWidget::password() const
{
    return m_password->text();
}

bool SqlWidget::autoconnect() const
{
    return m_autoconnect->isChecked();
}

int SqlWidget::driverCount() const
{
    return m_driver->count();
}


#include "sqltables.moc"
