/*
 * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB.  All rights reserved.
 *
 * This file is part of the KD Chart library.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "KChartDatasetProxyModel.h"

#include "KChartMath_p.h"

#include <QtDebug>


using namespace KChart;

DatasetProxyModel::DatasetProxyModel(QObject* parent)
    : QSortFilterProxyModel( parent )
{
}

QModelIndex DatasetProxyModel::buddy( const QModelIndex& index ) const
{
    return index;
}

Qt::ItemFlags DatasetProxyModel::flags( const QModelIndex& index ) const
{
    return sourceModel()->flags( mapToSource( index ) );
}

void DatasetProxyModel::setDatasetRowDescriptionVector(
    const DatasetDescriptionVector& configuration )
{
    Q_ASSERT_X( sourceModel(), "DatasetProxyModel::setDatasetRowDescriptionVector",
                "A source model must be set before the selection can be configured." );
    initializeDatasetDecriptors( configuration, sourceModel()->rowCount(mRootIndex),
                                 mRowSrcToProxyMap,  mRowProxyToSrcMap );
    clear(); // clear emits layoutChanged()
}

void DatasetProxyModel::setDatasetColumnDescriptionVector(
    const DatasetDescriptionVector& configuration )
{
    Q_ASSERT_X( sourceModel(), "DatasetProxyModel::setDatasetColumnDescriptionVector",
                "A source model must be set before the selection can be configured." );
    initializeDatasetDecriptors( configuration, sourceModel()->columnCount(mRootIndex),
                                 mColSrcToProxyMap, mColProxyToSrcMap );
    clear(); // clear emits layoutChanged()
}

void DatasetProxyModel::setDatasetDescriptionVectors(
    const DatasetDescriptionVector& rowConfig,
    const DatasetDescriptionVector& columnConfig )
{
    setDatasetRowDescriptionVector( rowConfig );
    setDatasetColumnDescriptionVector( columnConfig );
}

QModelIndex DatasetProxyModel::index( int row, int column,
                                      const QModelIndex &parent ) const
{
    return mapFromSource( sourceModel()->index( mapProxyRowToSource(row),
                                                mapProxyColumnToSource(column),
                                                parent ) );
}

QModelIndex DatasetProxyModel::parent( const QModelIndex& child ) const
{
//    return mapFromSource( sourceModel()->parent( child ) );
    return mapFromSource( sourceModel()->parent( mapToSource( child ) ) );
}

QModelIndex DatasetProxyModel::mapFromSource( const QModelIndex & sourceIndex ) const
{
    Q_ASSERT_X( sourceModel(), "DatasetProxyModel::mapFromSource", "A source "
                "model must be set before the selection can be configured." );

    if ( !sourceIndex.isValid() ) return sourceIndex;

    if ( mRowSrcToProxyMap.isEmpty() && mColSrcToProxyMap.isEmpty() )
    {
        return createIndex( sourceIndex.row(), sourceIndex.column(),
                            sourceIndex.internalPointer() );
    } else {
        int row = mapSourceRowToProxy( sourceIndex.row() );
        int column = mapSourceColumnToProxy( sourceIndex.column() );
        return createIndex( row, column, sourceIndex.internalPointer() );
    }
}

QModelIndex DatasetProxyModel::mapToSource( const QModelIndex& proxyIndex ) const
{
    Q_ASSERT_X( sourceModel(), "DatasetProxyModel::mapToSource", "A source "
                "model must be set before the selection can be configured." );

    if ( !proxyIndex.isValid() ) return proxyIndex;
    if ( mRowSrcToProxyMap.isEmpty() && mColSrcToProxyMap.isEmpty() )
    {
        return sourceModel()->index( proxyIndex.row(),  proxyIndex.column(), mRootIndex );
    } else {
        int row = mapProxyRowToSource( proxyIndex.row() );
        int column = mapProxyColumnToSource( proxyIndex.column() );
        return sourceModel()->index( row, column, mRootIndex );
    }
}

bool DatasetProxyModel::filterAcceptsRow( int sourceRow,
                                          const QModelIndex & ) const
{
    if ( mRowSrcToProxyMap.isEmpty() )
    {   // no row mapping set, all rows are passed down:
        return true;
    } else {
        Q_ASSERT( sourceModel() );
        Q_ASSERT( mRowSrcToProxyMap.size() == sourceModel()->rowCount(mRootIndex) );
        if ( mRowSrcToProxyMap[sourceRow] == -1 )
        {   // this row is explicitly not accepted:
            return false;
        } else {
            Q_ASSERT( mRowSrcToProxyMap[sourceRow] >= 0
                      && mRowSrcToProxyMap[sourceRow] < mRowSrcToProxyMap.size() );
            return true;
        }
    }
}

bool DatasetProxyModel::filterAcceptsColumn( int sourceColumn,
                                             const QModelIndex & ) const
{
    if ( mColSrcToProxyMap.isEmpty() )
    {   // no column mapping set up yet, all columns are passed down:
        return true;
    } else {
        Q_ASSERT( sourceModel() );
        Q_ASSERT( mColSrcToProxyMap.size() == sourceModel()->columnCount(mRootIndex) );
        if ( mColSrcToProxyMap[sourceColumn] == -1 )
        {   // this column is explicitly not accepted:
            return false;
        } else {
            Q_ASSERT( mColSrcToProxyMap[sourceColumn] >= 0
                      && mColSrcToProxyMap[sourceColumn] < mColSrcToProxyMap.size() );
            return true;
        }
    }
}

int DatasetProxyModel::mapProxyRowToSource( const int& proxyRow ) const
{
    if ( mRowProxyToSrcMap.isEmpty() )
    {   // if no row mapping is set, we pass down the row:
        return proxyRow;
    } else {
        Q_ASSERT( proxyRow >= 0 && proxyRow < mRowProxyToSrcMap.size() );
        return mRowProxyToSrcMap[ proxyRow ];
    }
}

int DatasetProxyModel::mapProxyColumnToSource( const int& proxyColumn ) const
{
    if ( mColProxyToSrcMap.isEmpty() )
    {   // if no column mapping is set, we pass down the column:
        return proxyColumn;
    } else {
        Q_ASSERT( proxyColumn >= 0 && proxyColumn < mColProxyToSrcMap.size() );
        return mColProxyToSrcMap[ proxyColumn ];
    }
}

int DatasetProxyModel::mapSourceRowToProxy( const int& sourceRow ) const
{
    if ( mRowSrcToProxyMap.isEmpty() )
    {
        return sourceRow;
    } else {
        Q_ASSERT( sourceRow >= 0 && sourceRow < mRowSrcToProxyMap.size() );
        return mRowSrcToProxyMap[sourceRow];
    }
}

int DatasetProxyModel::mapSourceColumnToProxy( const int& sourceColumn ) const
{
    if ( mColSrcToProxyMap.isEmpty() )
    {
        return sourceColumn;
    } else {
        Q_ASSERT( sourceColumn >= 0 && sourceColumn < mColSrcToProxyMap.size() );
        return mColSrcToProxyMap.at( sourceColumn ) ;
    }
}

void DatasetProxyModel::resetDatasetDescriptions()
{
    mRowSrcToProxyMap.clear();
    mRowProxyToSrcMap.clear();
    mColSrcToProxyMap.clear();
    mColProxyToSrcMap.clear();
    clear();
}

QVariant DatasetProxyModel::data(const QModelIndex &index, int role) const
{
   return sourceModel()->data( mapToSource( index ), role );
}

bool DatasetProxyModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
    return sourceModel()->setData( mapToSource( index ), value, role );
}

QVariant DatasetProxyModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
    if ( orientation == Qt::Horizontal )
    {
        if ( mapProxyColumnToSource ( section ) == -1 )
        {
            return QVariant();
        } else {
            return sourceModel()->headerData( mapProxyColumnToSource( section ), orientation,  role );
        }
    } else {
        if ( mapProxyRowToSource ( section ) == -1 )
        {
            return QVariant();
        } else {
            return sourceModel()->headerData( mapProxyRowToSource ( section ), orientation, role );
        }
    }
}

void DatasetProxyModel::initializeDatasetDecriptors(
    const DatasetDescriptionVector& inConfiguration,
    const int sourceCount,
    DatasetDescriptionVector& outSourceToProxyMap,
    DatasetDescriptionVector& outProxyToSourceMap )
{
    // in the current mapping implementation, the proxy-to-source map is
    // identical to the configuration vector:
    outProxyToSourceMap = inConfiguration;
    outSourceToProxyMap.fill( -1,  sourceCount );

    for ( int index = 0; index < inConfiguration.size(); ++index ) {
        // make sure the values in inConfiguration point to columns in the
        // source model:

        if ( inConfiguration[index] == -1 ) {
            continue;
        }

        Q_ASSERT_X( inConfiguration[ index ] >= 0 && inConfiguration[ index ] < sourceCount,
                    "DatasetProxyModel::initializeDatasetDecriptors",
                    "column index outside of source model" );
        Q_ASSERT_X( outSourceToProxyMap[ inConfiguration[ index ] ] == -1 ,
                    "DatasetProxyModel::initializeDatasetDecriptors",
                    "no duplicates allowed in mapping configuration, mapping has to be reversible" );

        outSourceToProxyMap[ inConfiguration[ index ] ] = index;
    }
}

void DatasetProxyModel::setSourceModel(QAbstractItemModel *m)
{
    if ( sourceModel() ) {
        disconnect( sourceModel(),  SIGNAL( layoutChanged() ),
                    this, SLOT( resetDatasetDescriptions() ) );
    }
    QSortFilterProxyModel::setSourceModel( m );
    mRootIndex = QModelIndex();
    if ( m ) {
        connect( m,  SIGNAL( layoutChanged() ),
                 this, SLOT( resetDatasetDescriptions() ) );
        connect( m, SIGNAL( layoutChanged() ), this, SIGNAL( layoutChanged() ) );
    }
    resetDatasetDescriptions();
}

void DatasetProxyModel::setSourceRootIndex(const QModelIndex& rootIdx)
{
    mRootIndex = rootIdx;
    resetDatasetDescriptions();
}

