/* This file is part of the KDE project
   Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include <kexidb/driver.h>
#include <kexidb/driver_p.h>
#include <kexidb/drivermanager.h>
#include <kexidb/drivermanager_p.h>
#include "error.h"
#include "drivermanager.h"
#include "connection.h"
#include "connectiondata.h"
#include "admin.h"

#include <tqfileinfo.h>

#include <tdelocale.h>
#include <kdebug.h>

#include <assert.h>

using namespace KexiDB;

/*! used when we do not have Driver instance yet,
 or when we cannot get one */
TQValueVector<TQString> dflt_typeNames;


//---------------------------------------------


DriverBehaviour::DriverBehaviour()
	: UNSIGNED_TYPE_KEYWORD("UNSIGNED")
	, AUTO_INCREMENT_FIELD_OPTION("AUTO_INCREMENT")
	, AUTO_INCREMENT_PK_FIELD_OPTION("AUTO_INCREMENT PRIMARY KEY")
	, SPECIAL_AUTO_INCREMENT_DEF(false)
	, AUTO_INCREMENT_REQUIRES_PK(false)
	, ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE(false)
	, QUOTATION_MARKS_FOR_IDENTIFIER('"')
	, USING_DATABASE_REQUIRED_TO_CONNECT(true)
	, _1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY(false)
	, SELECT_1_SUBQUERY_SUPPORTED(false)
	, SQL_KEYWORDS(0)
{
}

//---------------------------------------------

Driver::Info::Info()
 : fileBased(false)
 , allowImportingTo(true)
{
}

//---------------------------------------------

Driver::Driver( TQObject *parent, const char *name, const TQStringList & )
	: TQObject( parent, name )
	, Object()
	, beh( new DriverBehaviour() )
	, d( new DriverPrivate() )
{
	d->connections.setAutoDelete(false);
	//TODO: reasonable size
	d->connections.resize(101);
	d->typeNames.resize(Field::LastType + 1);

	d->initKexiKeywords();
}


Driver::~Driver()
{
	DriverManagerInternal::self()->aboutDelete( this );
//	KexiDBDbg << "Driver::~Driver()" << endl;
	TQPtrDictIterator<Connection> it( d->connections );
	Connection *conn;
	while ( (conn = it.toFirst()) ) {
		delete conn;
	}
	delete beh;
	delete d;
//	KexiDBDbg << "Driver::~Driver() ok" << endl;
}

bool Driver::isValid()
{
	clearError();
	if (KexiDB::version().major != version().major
		|| KexiDB::version().minor != version().minor)
	{
		setError(ERR_INCOMPAT_DRIVER_VERSION,
		i18n("Incompatible database driver's \"%1\" version: found version %2, expected version %3.")
		.arg(name())
		.arg(TQString("%1.%2").arg(version().major).arg(version().minor))
		.arg(TQString("%1.%2").arg(KexiDB::version().major).arg(KexiDB::version().minor)));
		return false;
	}

	TQString inv_impl = i18n("Invalid database driver's \"%1\" implementation:\n").arg(name());
	TQString not_init = i18n("Value of \"%1\" is not initialized for the driver.");
	if (beh->ROW_ID_FIELD_NAME.isEmpty()) {
		setError(ERR_INVALID_DRIVER_IMPL, inv_impl + not_init.arg("DriverBehaviour::ROW_ID_FIELD_NAME"));
		return false;
	}

	return true;
}

const TQPtrList<Connection> Driver::connectionsList() const
{
	TQPtrList<Connection> clist;
	TQPtrDictIterator<Connection> it( d->connections );
	for( ; it.current(); ++it )
		clist.append( &(*it) );
	return clist;
}

TQString Driver::fileDBDriverMimeType() const
{ return d->fileDBDriverMimeType; }

TQString Driver::defaultFileBasedDriverMimeType()
{ return TQString::fromLatin1("application/x-kexiproject-sqlite3"); }

TQString Driver::defaultFileBasedDriverName()
{
	DriverManager dm;
	return dm.lookupByMime(Driver::defaultFileBasedDriverMimeType()).lower();
}

const KService* Driver::service() const
{ return d->service; }

bool Driver::isFileDriver() const
{ return d->isFileDriver; }

int Driver::features() const
{ return d->features; }

bool Driver::transactionsSupported() const
{ return d->features & (SingleTransactions | MultipleTransactions); }

AdminTools& Driver::adminTools() const
{
	if (!d->adminTools)
		d->adminTools = drv_createAdminTools();
	return *d->adminTools;
}

AdminTools* Driver::drv_createAdminTools() const
{
	return new AdminTools(); //empty impl.
}

TQString Driver::sqlTypeName(int id_t, int /*p*/) const
{
	if (id_t > Field::InvalidType && id_t <= Field::LastType)
		return d->typeNames[(id_t>0 && id_t<=Field::LastType) ? id_t : Field::InvalidType /*sanity*/];

	return d->typeNames[Field::InvalidType];
}

Connection *Driver::createConnection( ConnectionData &conn_data, int options )
{
	clearError();
	if (!isValid())
		return 0;

	if (d->isFileDriver) {
		if (conn_data.fileName().isEmpty()) {
			setError(ERR_MISSING_DB_LOCATION, i18n("File name expected for file-based database driver.") );
			return 0;
		}
	}
//	Connection *conn = new Connection( this, conn_data );
	Connection *conn = drv_createConnection( conn_data );

	conn->setReadOnly(options & ReadOnlyConnection);

	conn_data.driverName = name();
	d->connections.insert( conn, conn );
	return conn;
}

Connection* Driver::removeConnection( Connection *conn )
{
	clearError();
	return d->connections.take( conn );
}

TQString Driver::defaultSQLTypeName(int id_t)
{
	if (id_t>=Field::Null)
		return "Null";
	if (dflt_typeNames.isEmpty()) {
		dflt_typeNames.resize(Field::LastType + 1);
		dflt_typeNames[Field::InvalidType]="InvalidType";
		dflt_typeNames[Field::Byte]="Byte";
		dflt_typeNames[Field::ShortInteger]="ShortInteger";
		dflt_typeNames[Field::Integer]="Integer";
		dflt_typeNames[Field::BigInteger]="BigInteger";
		dflt_typeNames[Field::Boolean]="Boolean";
		dflt_typeNames[Field::Date]="Date";
		dflt_typeNames[Field::DateTime]="DateTime";
		dflt_typeNames[Field::Time]="Time";
		dflt_typeNames[Field::Float]="Float";
		dflt_typeNames[Field::Double]="Double";
		dflt_typeNames[Field::Text]="Text";
		dflt_typeNames[Field::LongText]="LongText";
		dflt_typeNames[Field::BLOB]="BLOB";
	}
	return dflt_typeNames[id_t];
}

bool Driver::isSystemObjectName( const TQString& n ) const
{
	return Driver::isKexiDBSystemObjectName(n);
}

bool Driver::isKexiDBSystemObjectName( const TQString& n )
{
	if (!n.lower().startsWith("kexi__"))
		return false;
	const TQStringList list( Connection::kexiDBSystemTableNames() );
	return list.find(n.lower())!=list.constEnd();
}

bool Driver::isSystemFieldName( const TQString& n ) const
{
	if (!beh->ROW_ID_FIELD_NAME.isEmpty() && n.lower()==beh->ROW_ID_FIELD_NAME.lower())
		return true;
	return drv_isSystemFieldName(n);
}

TQString Driver::valueToSQL( uint ftype, const TQVariant& v ) const
{
	if (v.isNull())
		return "NULL";
	switch (ftype) {
		case Field::Text:
		case Field::LongText: {
			TQString s = v.toString();
			return escapeString(s); //TQString("'")+s.replace( '"', "\\\"" ) + "'";
		}
		case Field::Byte:
		case Field::ShortInteger:
		case Field::Integer:
		case Field::BigInteger:
			return v.toString();
		case Field::Float:
		case Field::Double: {
			if (v.type()==TQVariant::String) {
				//workaround for values stored as string that should be casted to floating-point
				TQString s(v.toString());
				return s.replace(',', ".");
			}
			return v.toString();
		}
//TODO: here special encoding method needed
		case Field::Boolean:
			return TQString::number(v.toInt()?1:0); //0 or 1
		case Field::Time:
			return TQString("\'")+v.toTime().toString(Qt::ISODate)+"\'";
		case Field::Date:
			return TQString("\'")+v.toDate().toString(Qt::ISODate)+"\'";
		case Field::DateTime:
			return dateTimeToSQL( v.toDateTime() );
		case Field::BLOB: {
			if (v.toByteArray().isEmpty())
				return TQString::fromLatin1("NULL");
			if (v.type()==TQVariant::String)
				return escapeBLOB(v.toString().utf8());
			return escapeBLOB(v.toByteArray());
		}
		case Field::InvalidType:
			return "!INVALIDTYPE!";
		default:
			KexiDBDbg << "Driver::valueToSQL(): UNKNOWN!" << endl;
			return TQString();
	}
	return TQString();
}

TQVariant Driver::propertyValue( const TQCString& propName ) const
{
	return d->properties[propName.lower()];
}

TQString Driver::propertyCaption( const TQCString& propName ) const
{
	return d->propertyCaptions[propName.lower()];
}

TQValueList<TQCString> Driver::propertyNames() const
{
	TQValueList<TQCString> names = d->properties.keys();
	qHeapSort(names);
	return names;
}

TQString Driver::escapeIdentifier(const TQString& str, int options) const
{
	TQCString cstr = str.latin1();
	return TQString(escapeIdentifier(cstr, options));
}

TQCString Driver::escapeIdentifier(const TQCString& str, int options) const
{
	bool needOuterQuotes = false;

// Need to use quotes if ...
// ... we have been told to, or ...
	if(options & EscapeAlways)
		needOuterQuotes = true;

// ... or if the driver does not have a list of keywords,
	else if(!d->driverSQLDict)
		needOuterQuotes = true;

// ... or if it's a keyword in Kexi's SQL dialect,
	else if(d->kexiSQLDict->find(str))
		needOuterQuotes = true;

// ... or if it's a keyword in the backends SQL dialect,
// (have already checked !d->driverSQLDict)
	else if((options & EscapeDriver) && d->driverSQLDict->find(str))
		needOuterQuotes = true;

// ... or if the identifier has a space in it...
  else if(str.find(' ') != -1)
		needOuterQuotes = true;

	if(needOuterQuotes && (options & EscapeKexi)) {
		const char quote = '"';
		return quote + TQCString(str).replace( quote, "\"\"" ) + quote;
	}
	else if (needOuterQuotes) {
		const char quote = beh->QUOTATION_MARKS_FOR_IDENTIFIER.latin1();
		return quote + drv_escapeIdentifier(str) + quote;
	} else {
		return drv_escapeIdentifier(str);
	}
}

void Driver::initSQLKeywords(int hashSize) {

	if(!d->driverSQLDict && beh->SQL_KEYWORDS != 0) {
	  d->initDriverKeywords(beh->SQL_KEYWORDS, hashSize);
	}
}

#include "driver.moc"