/*
 * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
 *
 * This file is part of the KGantt library.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "kganttconstraintmodel.h"
#include "kganttconstraintmodel_p.h"


#include <cassert>
#include <algorithm>
#include <functional>

using namespace KGantt;



ConstraintModel::Private::Private()
{
}

void ConstraintModel::Private::addConstraintToIndex( const QModelIndex& idx, const Constraint& c )
{
    IndexType::iterator it = indexMap.find(idx);
    while (it != indexMap.end() && it.key() == idx) {
        // Check if we already have this
        if ( *it == c ) return;
        ++it;
    }

    indexMap.insert( idx, c );
}

void ConstraintModel::Private::removeConstraintFromIndex( const QModelIndex& idx,  const Constraint& c )
{
    IndexType::iterator it = indexMap.find(idx);
    while (it != indexMap.end() && it.key() == idx) {
        if ( c.compareIndexes(*it) ) {
            it =indexMap.erase( it );
        } else {
            ++it;
        }
    }
}


ConstraintModel::ConstraintModel( QObject* parent )
    : QObject( parent ), _d( new Private )
{
    init();
}


ConstraintModel::ConstraintModel( Private* d_ptr, QObject* parent )
    : QObject( parent ), _d( d_ptr )
{
    init();
}


ConstraintModel::~ConstraintModel()
{
    delete _d;
}

#define d d_func()

void ConstraintModel::init()
{
}

namespace {
    struct compare_constraint_indexes_to {
        compare_constraint_indexes_to( const Constraint& c )
            : m_c( c ) {
        }
        bool operator()( const Constraint& c ) const
        {
            return m_c.compareIndexes( c );
        }

        const Constraint& m_c;
    };
}

void ConstraintModel::addConstraint( const Constraint& c )
{
    //qDebug() << "ConstraintModel::addConstraint("<<c<<") (this="<<this<<") items=" << d->constraints.size();
    QList<Constraint>::iterator it = std::find_if ( d->constraints.begin(),
                                                    d->constraints.end(),
                                                    compare_constraint_indexes_to(c) );

    if ( it == d->constraints.end() ) {
        d->constraints.push_back( c );
        d->addConstraintToIndex( c.startIndex(), c );
        d->addConstraintToIndex( c.endIndex(), c );
        Q_EMIT constraintAdded( c );
    } else if ( ( *it ).dataMap() != c.dataMap() || ( *it ).type() != c.type() || ( *it ).relationType() != c.relationType() ) {
        Constraint tmp( *it ); // save to avoid re-entrancy issues
        removeConstraint( tmp );
        d->constraints.push_back( c );
        d->addConstraintToIndex( c.startIndex(), c );
        d->addConstraintToIndex( c.endIndex(), c );
        Q_EMIT constraintAdded( c );
    }
}

bool ConstraintModel::removeConstraint( const Constraint& c )
{
    bool rc = false;

    for (int i = 0; i < d->constraints.count(); i++)
    {
        if (c.compareIndexes(d->constraints.at(i)))
        {
            d->constraints.removeAt(i);
            rc = true;
        }
    }

    if ( rc ) {
        d->removeConstraintFromIndex( c.startIndex(), c );
        d->removeConstraintFromIndex( c.endIndex(), c );
        Q_EMIT constraintRemoved( c );
    }

    return rc;
}

void ConstraintModel::clear()
{
    const QList<Constraint> lst = constraints();
    for ( const Constraint& c : lst ) {
        removeConstraint( c );
    }
}

void ConstraintModel::cleanup()
{
#if 0
    QSet<Constraint> orphans;
    for ( const Constraint& c : std::as_const(d->constraints) ) {
        if ( !c.startIndex().isValid() || !c.endIndex().isValid() ) orphans.insert( c );
    }
    //qDebug() << "Constraint::cleanup() found" << orphans << "orphans";
    d->constraints.subtract( orphans );
#endif
}

QList<Constraint> ConstraintModel::constraints() const
{
    //return d->constraints.toList();
    return d->constraints;
}

QList<Constraint> ConstraintModel::constraintsForIndex( const QModelIndex& idx ) const
{
    // TODO: @Steffen: Please comment on this assert, it's long and not obvious (Johannes)
    // TODO: Afaics indexMap is not used anymore, so remove it (danders)
    //assert( !idx.isValid() || d->indexMap.isEmpty() || !d->indexMap.keys().front().model() || idx.model() == d->indexMap.keys().front().model() );
    if ( !idx.isValid() ) {
        // Because of a Qt bug we need to treat this as a special case
        QSet<Constraint> result;
        for ( const Constraint& c : std::as_const(d->constraints) ) {
            if ( !c.startIndex().isValid() || !c.endIndex().isValid() ) result.insert( c );
        }
        return result.values();
    } else {
        QList<Constraint> result;
        for ( const Constraint& c : std::as_const(d->constraints) ) {
            if ( c.startIndex() == idx || c.endIndex() == idx ) result.push_back( c );
        }
        return result;
    }

    //return d->indexMap.values( idx );
}

bool ConstraintModel::hasConstraint( const Constraint& c ) const
{
    /*
    // Because of a Qt bug we have to search like this
    for ( Constraint c2 : std::as_const(d->constraints) ) {
        if ( c==c2 ) return true;
    }
    return false;
    */
    bool hc = false;

    for (int i = 0; i < d->constraints.count(); i++)
        if (c.compareIndexes(d->constraints.at(i)))
            hc = true;

    return hc;
}

#ifndef QT_NO_DEBUG_STREAM

QDebug operator<<( QDebug dbg, const KGantt::ConstraintModel& model )
{
    dbg << "KGantt::ConstraintModel[ " << static_cast<const QObject*>( &model ) << ": [\n";
    const auto constraints = model.constraints();
    for ( const Constraint& c : constraints ) {
        dbg << "\t" << c << "\n";
    }
    dbg << "]\n";
    return dbg;
}

#endif /* QT_NO_DEBUG_STREAM */

#undef d

#ifndef KDAB_NO_UNIT_TESTS

#include <QStandardItemModel>

#include "unittest/test.h"

std::ostream& operator<<( std::ostream& os, const QModelIndex& idx )
{
    QString str;
    QDebug( &str )<<idx;
#ifdef QT_NO_STL
    os<<str.toLatin1().constData();
#else
    os<<str.toStdString();
#endif
    return os;
}

KDAB_SCOPED_UNITTEST_SIMPLE( KGantt, ConstraintModel, "test" )
{
    QStandardItemModel dummyModel( 100, 100 );
    ConstraintModel model;

    QModelIndex invalidIndex;
    assertEqual( invalidIndex, invalidIndex );

    assertEqual( model.constraints().count(), 0 );

    model.addConstraint( Constraint( QModelIndex(), QModelIndex() ) );
    assertEqual( model.constraints().count(), 1 );

    model.addConstraint( Constraint( QModelIndex(), QModelIndex() ) );
    assertEqual( model.constraints().count(), 1 );

    QPersistentModelIndex idx1 = dummyModel.index( 7, 17, QModelIndex() );
    QPersistentModelIndex idx2 = dummyModel.index( 42, 17, QModelIndex() );

    model.addConstraint( Constraint( idx1, idx2 ) );
    assertEqual( model.constraints().count(), 2 );
    assertTrue( model.hasConstraint( Constraint( idx1, idx2 ) ) );

    assertEqual( model.constraintsForIndex( QModelIndex() ).count(), 1 );

    assertEqual( model.constraints().count(), 2 );
    model.removeConstraint( Constraint( QModelIndex(), QModelIndex() ) );
    assertEqual( model.constraints().count(), 1 );
    assertFalse( model.hasConstraint( Constraint( QModelIndex(), QModelIndex() ) ) );

    model.removeConstraint( Constraint( QModelIndex(), QModelIndex() ) );
    assertEqual( model.constraints().count(), 1 );

    model.removeConstraint( Constraint( idx1, idx2 ) );
    assertEqual( model.constraints().count(), 0 );
    assertFalse( model.hasConstraint( Constraint( idx1, idx2 ) ) );

    model.addConstraint( Constraint( idx1, idx2 ) );
    assertTrue( model.hasConstraint( Constraint( idx1, idx2 ) ) );
    dummyModel.removeRow( 8 );
    assertTrue( model.hasConstraint( Constraint( idx1, idx2 ) ) );
    dummyModel.removeRow( 7 );
    assertTrue( model.hasConstraint( Constraint( idx1, idx2 ) ) );
}

#endif /* KDAB_NO_UNIT_TESTS */

#include "moc_kganttconstraintmodel.cpp"
