#include "table.h"
#include "field.h"
#include "id.h"
#include "reference.h"
#include "database.h"
#include "baseadaptor.h"
#include "id.h"
#include "set.h"

#include <QString>
#include <QStringList>
#include <QSqlQuery>
#include <QDebug>

Table::Table(Database *db,
           const QString &name,
           const QList<Field> &fields,
           const QMap<QString,QVariant> &properties )
{
    m_data = new TableData();
    m_data->m_db = db;
    m_data->m_name = name;
    m_data->m_properties = properties;

    m_data->m_fields[QLatin1String("ID")] = Id(db->db());
    m_data->m_fields[QLatin1String("ID")].setTable(this);
    foreach( Field field, fields )
    {
        m_data->m_fields[ field.name() ] = field;
        m_data->m_fields[ field.name() ].setTable(this);
    }
}

Field Table::id() const
{
    return m_data->m_fields[QLatin1String("ID")];
}

Field Table::operator[] ( const QString &fieldName ) const
{
    return m_data-> m_fields[fieldName];
}

Field &Table::operator[] ( const QString &fieldName )
{
    return m_data->m_fields[fieldName];
}

Field Table::operator[] ( int col ) const
{
    return m_data->m_fields[m_data->m_fields.keys().at(col) ];
}

Field &Table::operator[] ( int col )
{
    return m_data->m_fields[ m_data->m_fields.keys().at(col) ];
}

int Table::cols() const
{
    return m_data->m_fields.keys().size();
}

QStringList Table::fields() const
{
    return m_data->m_fields.keys();
}

QString Table::name() const
{
    return m_data->m_name;
}

QStringList Table::primaryKeys() const
{
    return m_data->m_properties["primarykey"].toStringList();
}

QString Table::create( bool migrate, bool fakeMigrate) const
{
    QString resultingSql;
    QStringList constraints;
    QList<Field> uniqueKeys;
    QList<Field> fields;
    QMap<QString, QMap<QString,QString> > TFK;
    foreach( QString key, m_data->m_fields.keys() )
    {
        Field field = m_data->m_fields[key];
        if ( m_data->m_fields[key].type() == qMetaTypeId<Reference>() )
        {
            Field referenced = m_data->m_fields[key].property("target").value<Field>();
            BaseAdaptor::ConstraintAction onDelete = (BaseAdaptor::ConstraintAction) referenced.property("on_delete").toInt();
            BaseAdaptor::ConstraintAction onUpdate = (BaseAdaptor::ConstraintAction) referenced.property("on_update").toInt();

            constraints << m_data->m_db->db()->TABLE_CONSTRAINT_FOREIGN_KEY(field, referenced, onDelete, onUpdate );

        }
        if( field.property( "check").canConvert<Query>() )
           constraints << m_data->m_db->db()->TABLE_CONSTRAINT_CHECK(*this,field.property("check").value<Query>() );

        if( field.isUnique() )
            uniqueKeys.append( field );

        fields.append( field );
    }

/*

                constraint_name = self._db._adapter.constraint_name(self._tablename, field.name)
                if hasattr(self,'_primarykey'):
                    rtablename,rfieldname = ref.split('.')
                    rtable = self._db[rtablename]
                    rfield = rtable[rfieldname]
                    # must be PK reference or unique
                    if rfieldname in rtable._primarykey or rfield.unique:
                        ftype = self._db._adapter.types[rfield.type[:9]] %dict(length=rfield.length)
                        # multicolumn primary key reference?
                        if not rfield.unique and len(rtable._primarykey)>1 :
                            # then it has to be a table level FK
                            if rtablename not in TFK:
                                TFK[rtablename] = {}
                            TFK[rtablename][rfieldname] = field.name
                        else:
                            ftype = ftype + \
                                self._db._adapter.types['reference FK'] %dict(\
                                constraint_name=constraint_name,
                                table_name=self._tablename,
                                field_name=field.name,
                                foreign_key='%s (%s)'%(rtablename, rfieldname),
                                on_delete_action=field.ondelete)
                else:
                    ftype = self._db._adapter.types[field.type[:9]]\
                        % dict(table_name=self._tablename,
                               field_name=field.name,
                               constraint_name=constraint_name,
                               foreign_key=referenced + ('(%s)' % self._db[referenced].fields[0]),
                               on_delete_action=field.ondelete)
            elif field.type[:7] == 'decimal':
                precision, scale = [int(x) for x in field.type[8:-1].split(',')]
                ftype = self._db._adapter.types[field.type[:7]] % \
                    dict(precision=precision,scale=scale)
            elif not field.type in self._db._adapter.types:
                raise SyntaxError, 'Field: unknown field type: %s for %s' % \
                    (field.type, field.name)
            else:
                ftype = self._db._adapter.types[field.type]\
                     % dict(length=field.length)
            if not field.type[:10] in ['id', 'reference ']:
                if field.notnull:
                    ftype += ' NOT NULL'
                if field.unique:
                    ftype += ' UNIQUE'

            # add to list of fields
            sql_fields[field.name] = ftype

            if field.default:
                not_null = self._db._adapter.NOT_NULL(field.default,field.type)
                sql_fields_aux[field.name] = ftype.replace('NOT NULL',not_null)
            else:
                sql_fields_aux[field.name] = ftype

            fields.append('%s %s' % (field.name, ftype))
        other = ';'

        # backend-specific extensions to fields
        if self._db._dbname == 'mysql':
            if not self._primerykey:
                fields.append('PRIMARY KEY(%s)' % self.fields[0])
            other = ' ENGINE=InnoDB CHARACTER SET utf8;'

        fields = ',\n    '.join(fields)
        for rtablename in TFK:
            rfields = TFK[rtablename]
            pkeys = self._db[rtablename]._primarykey
            fkeys = [ rfields[k] for k in pkeys ]
            fields = fields + ',\n    ' + \
                     self._db._adapter.types['reference TFK'] %\
                     dict(table_name=self._tablename,
                     field_name=', '.join(fkeys),
                     foreign_table=rtablename,
                     foreign_key=', '.join(pkeys),
                     on_delete_action=field.ondelete)

        if hasattr(self,'_primarykey'):
            query = '''CREATE TABLE %s(\n    %s,\n    %s) %s''' % \
               (self._tablename, fields, self._db._adapter.PRIMARY_KEY(', '.join(self._primarykey),other))
        else:
            query = '''CREATE TABLE %s(\n    %s\n)%s''' % \
                (self._tablename, fields, other)

        if self._db._uri[:10] == 'sqlite:///':
            path_encoding = sys.getfilesystemencoding() or \
                locale.getdefaultlocale()[1]
            dbpath = self._db._uri[9:self._db._uri.rfind('/')]\
                .decode('utf8').encode(path_encoding)
        else:
            dbpath = self._db._adapter.folder
        if not migrate:
            return query
        elif self._db._uri[:14] == 'sqlite:memory:':
            self._dbt = None
        elif isinstance(migrate, str):
            self._dbt = os.path.join(dbpath, migrate)
        else:
            self._dbt = os.path.join(dbpath, '%s_%s.table' \
                     % (md5_hash(self._db._uri), self._tablename))
        if self._dbt:
            self._loggername = os.path.join(dbpath, 'sql.log')
            logfile = open(self._loggername, 'a')
        else:
            logfile = None
        if not self._dbt or not os.path.exists(self._dbt):
            if self._dbt:
                logfile.write('timestamp: %s\n'
                               % datetime.datetime.today().isoformat())
                logfile.write(query + '\n')
            if not fake_migrate:
                self._db._adapter.create_sequence_and_triggers(query,self)
                self._db.commit()
            if self._dbt:
                tfile = open(self._dbt, 'w')
                portalocker.lock(tfile, portalocker.LOCK_EX)
                cPickle.dump(sql_fields, tfile)
                portalocker.unlock(tfile)
                tfile.close()
            if self._dbt:
                if fake_migrate:
                    logfile.write('faked!\n')
                else:
                    logfile.write('success!\n')
        else:
            tfile = open(self._dbt, 'r')
            portalocker.lock(tfile, portalocker.LOCK_SH)
            sql_fields_old = cPickle.load(tfile)
            portalocker.unlock(tfile)
            tfile.close()
            if sql_fields != sql_fields_old:
                self._migrate(sql_fields, sql_fields_old,
                              sql_fields_aux, logfile,
                              fake_migrate=fake_migrate)

        return query
*/
    QStringList fieldStrings;
    foreach( Field field, fields )
            fieldStrings << field.create();

    if( !uniqueKeys.isEmpty() )
        constraints << m_data->m_db->db()->TABLE_CONSTRAINT_UNIQUE( uniqueKeys );

    QString other;
    if( constraints.isEmpty() )
    {
        resultingSql = QString(QLatin1String("CREATE TABLE %1( %2 )%3"))
                       .arg(m_data->m_name)
                       .arg(fieldStrings.join(QLatin1String(", ")))
                       .arg(other);
    }
    else
    {
        resultingSql = QString(QLatin1String("CREATE TABLE %1( %2, %3 )%4"))
                       .arg(m_data->m_name)
                       .arg(fieldStrings.join(QLatin1String(", ")))
                       .arg(constraints.join(QLatin1String(", ")))
                       .arg(other);
    }
    return resultingSql;
}

bool Table::hasColumn( const QString &colname ) const
{
    return m_data->m_fields.contains( colname );
}

const Database *Table::db() const
{
    return m_data->m_db;
}

QVariant Table::insert( const QMap<QString,QVariant> &values ) const
{
    QStringList ts = m_data->m_fields.keys();
    foreach( QString field, values.keys() )
    {
        if ( !ts.contains( field ))
        {
            // Set last error...
            return QVariant();
        }
    }

    QString sql = m_data->m_db->db()->INSERT( m_data->m_name, values );
    qDebug() << sql;
    QSqlQuery query = m_data->m_db->db()->connection().exec( sql );
    if ( query.isValid() )
    {
        // Set last error...
        return query.lastInsertId();
    }
    else
        return QVariant();
}

bool Table::insertInto( const QStringList &fields, const Query &source ) const
{

}

bool updateValues( const Query &fieldSelection, const QMap<QString,QVariant> &fieldValues )
{

}

bool Table::updateValue( const Query &fieldSelection, const QString &field, const QVariant &value )
{

}

QMap<QString,QVariant> Table::values( const Query &fieldSelection ) const
{

}

QVariant Table::value( const Query &fieldSelection, const QString &field ) const
{

}

Set Table::select( const Query &whereClause, const QMap<QString,QVariant> &options) const
{
    return Set(m_data->m_db, m_data->m_fields.values(), whereClause, options);
}

Set Table::select( const QStringList &fields, const Query &whereClause, const QMap<QString,QVariant> &options ) const
{
    QList<Field> selectedFields;
    foreach( QString field, fields)
        selectedFields << m_data->m_fields[field];

    return Set(m_data->m_db, selectedFields, whereClause, options);
}

