/* This file is part of the KDE project
   Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>

   This library 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 library 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 library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "KDbConnection.h"
#include "KDbDriver.h"
#include "KDbIndexSchema.h"
#include "KDbRelationship.h"
#include "KDbTableSchema.h"
#include "kdb_debug.h"

class Q_DECL_HIDDEN KDbIndexSchema::Private
{
public:
    Private()
        : tableSchema(nullptr)
        , isPrimary(false)
        , isUnique(false)
        , isAutoGenerated(false)
        , isForeignKey(false)
    {
    }
    ~Private()
    {
        /* It's a list of relationships to the table (of this index), i.e. any such
         * relationship in which the table is at 'master' side will be cleared and
         * relationships will be destroyed.
         * So all these relationships must be detached from details-side, corresponding
         * indices.
         */
        for(KDbRelationship* rel : masterOwnedRelationships) {
            if (rel->detailsIndex()) {
                rel->detailsIndex()->detachRelationship(rel);
            }
        }
        qDeleteAll(masterOwnedRelationships);
    }

    //! table on that index is built
    KDbTableSchema *tableSchema;

    /*! A set of master relationships for the table (of this index),
     this index is a master key for these relationships
     and therefore - owner of these */
    QSet<KDbRelationship*> masterOwnedRelationships;

    /*! A list of master relationships that are not owned by this schema */
    QList<const KDbRelationship*> masterRelationships;

    /*! A list of relationships to table (of this index) */
    QList<const KDbRelationship*> detailsRelationships;

    bool isPrimary;
    bool isUnique;
    bool isAutoGenerated;
    bool isForeignKey;
};

KDbIndexSchema::KDbIndexSchema()
        : KDbFieldList(false)//fields are not owned by KDbIndexSchema object
        , KDbObject(KDb::IndexObjectType)
        , d(new Private)
{
}

KDbIndexSchema::KDbIndexSchema(const KDbIndexSchema& index, KDbTableSchema* parentTable)
        : KDbFieldList(false)//fields are not owned by KDbIndexSchema object
        , KDbObject(static_cast<const KDbObject&>(index))
        , d(new Private)
{
    d->isPrimary = index.isPrimaryKey();
    d->isUnique = index.isUnique();
    d->isAutoGenerated = index.isAutoGenerated();
    d->isForeignKey = index.isForeignKey();
    // deep copy the field references
    for(KDbField *f : *index.fields()) {
        KDbField *parentTableField = parentTable->field(f->name());
        if (!parentTableField) {
            kdbWarning() << "Could not find field" << f->name() << "in parentTable. Empty index will be created!";
            KDbFieldList::clear();
            break;
        }
        (void)KDbFieldList::addField(f);
    }

//! @todo copy relationships!
// Reference::List m_refs_to; //! list of references to table (of this index)
// Reference::List m_refs_from; //! list of references from the table (of this index),
//         //! this index is foreign key for these references
//         //! and therefore - owner of these
}

KDbIndexSchema::~KDbIndexSchema()
{
    delete d;
}

void KDbIndexSchema::setTable(KDbTableSchema *table)
{
    if (this->table()) {
        kdbWarning() << "Table is already assigned to this index";
        return;
    }
    if (table) {
        d->tableSchema = table;
    }
}

bool KDbIndexSchema::addField(KDbField *field)
{
    if (!d->tableSchema || field->table() != d->tableSchema) {
        kdbWarning() << (field ? field->name() : QString())
        << "WARNING: field does not belong to the same table"
        << (field && field->table() ? field->table()->name() : QString())
        << "as index!";
        return false;
    }
    return KDbFieldList::addField(field);
}

const KDbTableSchema* KDbIndexSchema::table() const
{
    return d->tableSchema;
}

KDbTableSchema* KDbIndexSchema::table()
{
    return d->tableSchema;
}

QList<const KDbRelationship*> KDbIndexSchema::masterRelationships() const
{
    return d->masterRelationships;
}

QList<const KDbRelationship*> KDbIndexSchema::detailsRelationships() const
{
    return d->detailsRelationships;
}

bool KDbIndexSchema::isAutoGenerated() const
{
    return d->isAutoGenerated;
}

void KDbIndexSchema::setAutoGenerated(bool set)
{
    d->isAutoGenerated = set;
}

bool KDbIndexSchema::isPrimaryKey() const
{
    return d->isPrimary;
}

void KDbIndexSchema::setPrimaryKey(bool set)
{
    d->isPrimary = set;
    if (d->isPrimary)
        d->isUnique = true;
}

bool KDbIndexSchema::isUnique() const
{
    return d->isUnique;
}

void KDbIndexSchema::setUnique(bool set)
{
    d->isUnique = set;
    if (!d->isUnique)
        d->isPrimary = false;
}

bool KDbIndexSchema::isForeignKey() const
{
    return d->isForeignKey;
}

void KDbIndexSchema::setForeignKey(bool set)
{
    d->isForeignKey = set;
    if (d->isForeignKey) {
        setUnique(false);
    }
    if (fieldCount() == 1) {
        fields()->first()->setForeignKey(true);
    }
}

KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbIndexSchema& index)
{
    dbg.nospace() << QLatin1String("INDEX");
    dbg.space() << static_cast<const KDbObject&>(index) << '\n';
    dbg.space() << (index.isForeignKey() ? "FOREIGN KEY" : "");
    dbg.space() << (index.isAutoGenerated() ? "AUTOGENERATED" : "");
    dbg.space() << (index.isPrimaryKey() ? "PRIMARY" : "");
    dbg.space() << ((!index.isPrimaryKey()) && index.isUnique() ? "UNIQUE" : "");
    dbg.space() << static_cast<const KDbFieldList&>(index);
    return dbg.space();
}

void KDbIndexSchema::attachRelationship(KDbRelationship *rel)
{
    attachRelationship(rel, true);
}

void KDbIndexSchema::attachRelationship(KDbRelationship *rel, bool ownedByMaster)
{
    if (!rel)
        return;
    if (rel->masterIndex() == this) {
        if (ownedByMaster) {
            if (!d->masterOwnedRelationships.contains(rel)) {
                d->masterOwnedRelationships.insert(rel);
            }
        } else {//not owned
            if (!d->masterRelationships.contains(rel)) {
                d->masterRelationships.append(rel);
            }
        }
    } else if (rel->detailsIndex() == this) {
        if (!d->detailsRelationships.contains(rel)) {
            d->detailsRelationships.append(rel);
        }
    }
}

void KDbIndexSchema::detachRelationship(KDbRelationship *rel)
{
    if (!rel)
        return;
    d->masterOwnedRelationships.remove(rel);   //for sanity
    d->masterRelationships.takeAt(d->masterRelationships.indexOf(rel));   //for sanity
    d->detailsRelationships.takeAt(d->detailsRelationships.indexOf(rel));   //for sanity
}
