#include "baseadaptor.h"
#include "expression.h"
#include "field.h"
#include "query.h"
#include "table.h"
#include "reference.h"
#include "foreignkeyvalue.h"

#include <QStringList>
#include <QSqlDatabase>
#include <QDataStream>
#include <QByteArray>
#include <QDateTime>
#include <QSqlField>
#include <QSqlDriver>
#include <QUuid>

BaseAdaptor::BaseAdaptor( const Database *db ) : m_db(db)
{
    m_connectionName = QUuid::createUuid().toString();
}

BaseAdaptor::~BaseAdaptor()
{
    QSqlDatabase::removeDatabase( connectionName() );
}

QStringList BaseAdaptor::tables( const QVariant &other ) const
{
    QStringList tableNames;
    if( other.canConvert<Field>() )
    {
        Field field = other.value<Field>();
        tableNames << field.tableName();
    }

    if( other.canConvert<Query>() )
    {
        Query query = other.value<Query>();
        foreach( QVariant arg, query.args() )
            tableNames.append( tables( arg ));
    }

    if( other.canConvert<Expression>() )
    {
        Expression expression = other.value<Expression>();
        foreach( QVariant arg, expression.args() )
            tableNames.append( tables( arg ) );
    }

    return tableNames;
}

QString BaseAdaptor::representCustom( const QVariant &value ) const
{
    Q_UNUSED(value);
    return QString();
}

QString BaseAdaptor::represent( const QVariant &value) const
{
    QString result = representCustom( value );
    if( result != QString() )
        return result;

    QSqlField field(QString(),value.type() );
    field.setValue( value );
    return connection().driver()->formatValue(field,false);
}

QString BaseAdaptor::expand( const QVariant& other ) const
{
    if( other.canConvert<ForeignKeyValue>() )
    {
        return qVariantValue<ForeignKeyValue>(other).toString();
    }

    if( other.canConvert<Field>() )
    {
        return qVariantValue<Field>( other );
    }

    if( other.canConvert<Expression>() )
    {
        return qVariantValue<Expression>( other ).exec();
    }

    if( other.canConvert<Query>() )
    {
        return qVariantValue<Query>( other ).exec();
    }

    if ( other.canConvert( QVariant::List ) )
    {
        QStringList result;
        foreach( QVariant item, other.toList() )
        {
            result << represent( item );
        }

        return result.join(",");
    }

    return represent( other );
}

QString BaseAdaptor::mathOperation( const QVariantList &args, const QString &op) const
{
    QVariant arg1 = args.at(0);
    QVariant arg2 = args.at(1);
    QString sql = "( ";
    sql += expand( arg1 );
    sql += " " + op + " ";
    sql += expand( arg2 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::ADD( const QVariantList &args ) const
{
    return mathOperation( args, "+");
}

QString BaseAdaptor::DIV( const QVariantList &args ) const
{
    return mathOperation( args, "/");
}

QString BaseAdaptor::SUB( const QVariantList &args ) const
{
    return mathOperation( args, "-");
}
QString BaseAdaptor::DESC( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QString sql = expand( arg1 );
    sql += " DESC";
    return sql;
}
QString BaseAdaptor::COMMA( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QVariant arg2 = args.at(1);
    QString sql = expand( arg1 );
    sql += ", ";
    sql += expand(arg2);
    return sql;
}
QString BaseAdaptor::MUL( const QVariantList &args ) const
{
    return mathOperation( args, "*");
}
QString BaseAdaptor::GT( const QVariantList& args ) const
{
    return mathOperation( args, ">");
}
QString BaseAdaptor::GE( const QVariantList& args ) const
{
    return mathOperation( args, ">=");
}
QString BaseAdaptor::EQ( const QVariantList& args ) const
{
    if ( args.size() == 1 )
    {
        QVariant arg = args.at(0);
        QString result = "( ";
        result += expand( arg );
        result += " IS NULL )";
        return result;
    }
    else
        return mathOperation( args, "=");
}
QString BaseAdaptor::NE( const QVariantList& args ) const
{
    if ( args.size() == 1 )
    {
        QVariant arg = args.at(0);
        QString result = "( ";
        result += expand( arg );
        result += " IS NOT NULL)";
        return result;
    }
    else
        return mathOperation( args, "<>");
}
QString BaseAdaptor::LT( const QVariantList& args ) const
{
    return mathOperation( args, "<");
}
QString BaseAdaptor::LE( const QVariantList& args ) const
{
    return mathOperation( args, "<=");
}
QString BaseAdaptor::LIKE( const QVariantList& args ) const
{
    QVariant arg0 = args.at(0);
    QVariant arg1 = args.at(1);

    QString result = "( ";
    result += expand( arg0 );
    result += " LIKE ";
    result += expand( arg1 );
    result += " )";
    return result;
}
QString BaseAdaptor::BELONGS( const QVariantList& args ) const
{
    QVariant arg0 = args.at(0);
    QVariant arg1 = args.at(1);

    QString result = "( ";
    result += expand( arg0 );
    result += " IN ( ";

    if( arg0.canConvert(QVariant::String) )
    {
        result += arg1.toString();
    }
    else
    {
        result += expand( arg1 );
    }

    result += " ) )";
    return result;
}
QString BaseAdaptor::AND( const QVariantList& args ) const
{
    QVariant arg1 = args.at(0);
    QVariant arg2 = args.at(1);
    QString sql = "( ";
    sql += expand( arg1 );
    sql += " AND ";
    sql += expand( arg2 );
    sql += " )";

    return sql;
}
QString BaseAdaptor::OR( const QVariantList& args ) const
{
    QVariant arg1 = args.at(0);
    QVariant arg2 = args.at(1);
    QString sql = "( ";
    sql += expand( arg1 );
    sql += " OR ";
    sql += expand( arg2 );
    sql += " )";

    return sql;
}
QString BaseAdaptor::NOT( const QVariantList& args ) const
{
    QVariant arg1 = args.at(0);
    QString sql = "( NOT ";
    sql += expand( arg1 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::LOWER( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QString sql = "LOWER( ";
    sql += expand( arg1 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::UPPER( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QString sql = "UPPER( ";
    sql += expand( arg1 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::EXTRACT( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QString arg2 = args.at(1).toString();

    QString sql = "EXTRACT( '";
    sql += arg2;
    sql += "' FROM ";
    sql += expand( arg1 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::AGGREGATE( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QString arg2 = args.at(1).toString();

    QString sql = arg2 + " ( ";
    sql += expand( arg1 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::ON( const QVariantList &args ) const
{
    QVariant arg1 = args.at(0);
    QVariant arg2 = args.at(1);
    QString sql = "( ";
    sql += expand( arg1 );
    sql += " ON ";
    sql += expand( arg2 );
    sql += " )";

    return sql;
}

QString BaseAdaptor::RANDOM() const
{
    return "RANDOM()";
}

QString BaseAdaptor::LEFT_JOIN() const
{
    return "LEFT JOIN";
}


QString BaseAdaptor::DELETE(const QString &table, const Query &where  ) const
{
    QString whereString;
    if( where.isValid() )
    {
        whereString = " WHERE " + expand( where );
    }

    return "DELETE FROM " + table + " " + whereString + ";";
}

QString BaseAdaptor::DROP( const QString &table ) const
{
    return "DROP TABLE " + table + ";";
}

QString BaseAdaptor::INSERT( const QString &table, const QMap<QString,QVariant> &values ) const
{
    QStringList keys = values.keys();
    QStringList insertValues;
    QString sql = "INSERT INTO ";
    sql += table;

    foreach( QString key, keys )
    {
        insertValues << expand( values[key] );
    }

    sql += "(" + keys.join(",") + ") VALUES (" + insertValues.join(",") + ");";
    return sql;
}

QString BaseAdaptor::UPDATE( const QString &table, const QMap<Field,QVariant> &values, const Query &where ) const
{
    QString whereString;
    QStringList updateValues;
    if( where.isValid() )
    {
        whereString = " WHERE " + expand( where );
    }

    foreach( Field key, values.keys() )
    {
        updateValues << ( key.name() + "=" + expand( values[key] ) );
    }

    return "UPDATE " + table + " SET " + updateValues.join(",") + whereString + ";";
}

QString BaseAdaptor::SELECT( const QList<Field> &fields, const Query &where, const QMap<QString,QVariant> &options) const
{
    QList<Field> fieldList;
    QStringList tablenames = tables(where);
    if( fields.isEmpty() )
    {
        foreach( QString table, tablenames )
            foreach( QString field, m_db->table(table).fields())
           fieldList.append( m_db->table(table)[field] );
    }
    else
    {
        foreach( Field field, fields )
        {
            tablenames.append( tables(field) );
        }
        fieldList = fields;
    }

    tablenames.removeDuplicates();

    QStringList expandedFields;
    QString fieldTemplate = QLatin1String("%1 AS \"%2\"");
    foreach( Field field, fieldList )
    {
        expandedFields << fieldTemplate
                .arg( expand( field ))
                .arg(field.scopedName());
    }

    QString fieldsClause = expandedFields.join(",");

    QString whereClause;
    if( where.isValid() )
        whereClause = " WHERE " + expand( where );

    QString orderBy;
    QString selectClause;
    QString tableClause;
    QVariant limitBy;

    if ( options.contains("distinct") )
    {
        if ( options["distinct"].toBool() == true )
            selectClause += " DISTINCT ";
        else if ( options["distinct"].toString() != QString() )
        {
            selectClause += " DISTINCT ON (" + options["distinct"].toString() + ") ";
        }
    }

    if ( options.contains("groupby") )
    {
        orderBy += " GROUP BY " + options["groupby"].toString() + " ";
        if ( options.contains("having") )
            orderBy += " HAVING " + options["having"].toString() + " ";
    }

    if ( options.contains("orderby") )
    {
        QVariant orderCols;

        if( options["orderby"].type() == QVariant::List ||
            options["orderby"].type() == QVariant::StringList )
        {
            //xorify?
            orderCols = options["orderby"].toList();
        }
        else
        {
            orderCols = options["orderby"];
        }

        if ( orderCols.toString() == "<random>" )
        {
            orderBy += " ORDER BY " + RANDOM();
        }
        else
        {
            orderBy += " ORDER BY " + expand( orderCols );
        }
    }

    if ( options.contains("left") )
    {
        QVariant join = options["left"];
        if( join.type() != QVariant::List )
            join = QVariantList() << join;
        QString command = LEFT_JOIN();
        QStringList joint;
        foreach( QVariant t, join.toList() )
        {
            if ( !t.canConvert<Expression>() )
            {
                joint.append( tables(t) );
            }
        }
        QStringList joinont;
        foreach( QVariant t, join.toList() )
        {
            if ( t.canConvert<Expression>() )
            {
                joinont.append( tables(t) );
            }
        }
        QStringList excluded = tablenames;
        foreach( QString t, joint + joinont )
        {
            excluded.removeAll(t);
        }
        tableClause = excluded.join(", ");
        if( !joint.isEmpty() )
            tableClause += " " + command + joint.join(", ");
        foreach( QString t, joinont )
            tableClause += " " + command + " " + t;
    }
    else
    {
        tableClause = tablenames.join(", ");
    }

    if ( options.contains("limitby") )
    {
        if ( !options.contains("orderby") && !tablenames.isEmpty() )
        {
            //sql_o += ' ORDER BY %s' % ', '.join(['%s.%s'%(t,x) for t in tablenames for x in (self.db[t]._primarykey or ['id'])])
        }
        limitBy = options["limitby"];
    }

    return SELECT_LIMITBY(selectClause, fieldsClause, tableClause, whereClause, orderBy, limitBy );
}

QString BaseAdaptor::SELECT_LIMITBY( const QString &selectClause, const QString &fieldsClause,
                                    const QString &tablesClause, const QString &whereClause,
                                    const QString &orderBy, const QVariant &limitby  ) const
{
    QString limitClause;
    if( limitby.isValid() )
    {
        QVariantList limits = limitby.toList();
        int upperLimit = limits.at(1).toInt();
        int lowerLimit = limits.at(0).toInt();
        limitClause = " LIMIT " +
                      QString::number( upperLimit - lowerLimit ) +
                      " OFFSET " +
                      QString::number( lowerLimit );
    }
    return "SELECT " + selectClause +
            " " + fieldsClause +
            " FROM " + tablesClause +
            whereClause + orderBy + limitClause;
}

QStringList BaseAdaptor::TRUNCATE( const QString &table ) const
{
    return QStringList() << "TRUNCATE TABLE " + table + ";";
}

QString BaseAdaptor::COUNT( const QString &table, const Query &where ) const
{
    QString whereClause;
    QString tableClause = tables( where ).join(",");
    if ( where.isValid() )
        whereClause = " WHERE " + expand( where );

    return "SELECT count(*) FROM " + tableClause + whereClause;
}

QString BaseAdaptor::DEFAULT( const QVariant &defaultValue ) const
{
    return " DEFAULT " + represent( defaultValue );
}

QString BaseAdaptor::NOT_NULL( bool notNull ) const
{
    if( notNull )
        return " NOT NULL ";
    else
        return QString();
}

QString BaseAdaptor::PRIMARY_KEY( ) const
{
        return " PRIMARY KEY AUTOINCREMENT ";
}

QString BaseAdaptor::TABLE_CONSTRAINT_FOREIGN_KEY( const Field &source, const Field &target, ConstraintAction onDelete, ConstraintAction onUpdate ) const
{
        QString name = constraintName( source.tableName(), source.name() ) + QLatin1String("_FK");
        QString resultSql =  QLatin1String( "CONSTRAINT %1 FOREIGN KEY (%2) REFERENCES %3(%4)");
        switch( onDelete )
        {
        case SetNull:
            resultSql += QLatin1String(" ON DELETE SET NULL ");
            break;
        case SetDefault:
            resultSql += QLatin1String(" ON DELETE SET DEFAULT ");
            break;
        case Cascade:
            resultSql += QLatin1String(" ON DELETE CASCADE ");
            break;
        case Restrict:
            resultSql += QLatin1String(" ON DELETE RESTRICT ");
            break;
        case None:
            break;
        }

        switch( onUpdate )
        {
        case SetNull:
            resultSql += QLatin1String(" ON UPDATE SET NULL ");
            break;
        case SetDefault:
            resultSql += QLatin1String(" ON UPDATE SET DEFAULT ");
            break;
        case Cascade:
            resultSql += QLatin1String(" ON UPDATE CASCADE ");
            break;
        case Restrict:
            resultSql += QLatin1String(" ON UPDATE RESTRICT ");
            break;
        case None:
            break;
        }
        return resultSql
                .arg(name)
                .arg(source.formattedName())
                .arg(target.formattedTableName())
                .arg(target.formattedName());
}

QString BaseAdaptor::TABLE_CONSTRAINT_CHECK( const Table &source, const Query &clause ) const
{
    QString name = constraintName( source.name(), QString() ) + QLatin1String("_CHK");
    QString resultSql =  QLatin1String( "CONSTRAINT %1 CHECK (%2)");
    return resultSql.arg(name).arg(expand(clause));
}

QString BaseAdaptor::TABLE_CONSTRAINT_UNIQUE( const QList<Field> &fields  ) const
{
    if( fields.isEmpty() )
        return QString();

    QStringList escapedKeys;
    foreach( Field field, fields)
        escapedKeys << field.formattedName();
    return QString(QLatin1String("CONSTRAINT %1 UNIQUE ( %2 )"))
            .arg( constraintName( fields[0].tableName(), fields[0].name() ) )
            .arg( escapedKeys.join(QLatin1String(", ")));
}

QString BaseAdaptor::constraintName(  const QString &tableName, const QString &fieldName  ) const
{
     return tableName + "_" + fieldName + "__constraint";
}

bool BaseAdaptor::open()
{
    return connection().open();
}

void BaseAdaptor::close()
{
    connection().close();
}

QString BaseAdaptor::connectionName() const
{
    return m_connectionName;
}

QString BaseAdaptor::formatDatabaseString( const QString &source, const QString &key, const QString &value ) const
{
    QMap<QString,QString> values;
    values[key] = value;
    return formatDatabaseString(source, values);
}

QString BaseAdaptor::formatDatabaseString( const QString &source, const QMap<QString,QString> &values ) const
{
    QString result = source;

    foreach( QString key, values.keys())
        result.replace(QString(QLatin1String("%(%1)")).arg(key), values[key]);

    return result;
}

QString BaseAdaptor::quoteFieldName( const Field &field ) const
{
    if( field.tableName().isEmpty() )
    {
        return field.name();
    }
    else
    {
        return QString(QLatin1String("%1.%2"))
                .arg(field.tableName())
                .arg(field.name());
    }
}

QString BaseAdaptor::quoteFieldName( const QString &name ) const
{
    return name;
}

