/***************************************************************************
    File                 : MatrixModel.cpp
    Project              : SciDAVis
    Description          : Model for the access to a Matrix
    --------------------------------------------------------------------
    Copyright            : (C) 2008-2009 Tilman Benkert (thzs*gmx.net)
                           (replace * with @ in the email addresses)

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *  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, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
 *   Boston, MA  02110-1301  USA                                           *
 *                                                                         *
 ***************************************************************************/

#include "Matrix.h"
#include "matrix/future_Matrix.h"
#include "matrix/MatrixModel.h"
#include <QString>
#include <QBrush>
#include <QColor>

MatrixModel::MatrixModel(future::Matrix *matrix) : QAbstractItemModel(0), d_matrix(matrix)
{
    connect(d_matrix, SIGNAL(columnsAboutToBeInserted(int, int)), this,
            SLOT(handleColumnsAboutToBeInserted(int, int)));
    connect(d_matrix, SIGNAL(columnsInserted(int, int)), this,
            SLOT(handleColumnsInserted(int, int)));
    connect(d_matrix, SIGNAL(columnsAboutToBeRemoved(int, int)), this,
            SLOT(handleColumnsAboutToBeRemoved(int, int)));
    connect(d_matrix, SIGNAL(columnsRemoved(int, int)), this, SLOT(handleColumnsRemoved(int, int)));
    connect(d_matrix, SIGNAL(rowsAboutToBeInserted(int, int)), this,
            SLOT(handleRowsAboutToBeInserted(int, int)));
    connect(d_matrix, SIGNAL(rowsInserted(int, int)), this, SLOT(handleRowsInserted(int, int)));
    connect(d_matrix, SIGNAL(rowsAboutToBeRemoved(int, int)), this,
            SLOT(handleRowsAboutToBeRemoved(int, int)));
    connect(d_matrix, SIGNAL(rowsRemoved(int, int)), this, SLOT(handleRowsRemoved(int, int)));
    connect(d_matrix, SIGNAL(dataChanged(int, int, int, int)), this,
            SLOT(handleDataChanged(int, int, int, int)));
    connect(d_matrix, SIGNAL(coordinatesChanged()), this, SLOT(handleCoordinatesChanged()));
    connect(d_matrix, SIGNAL(formatChanged()), this, SLOT(handleFormatChanged()));
}

MatrixModel::~MatrixModel() { }

Qt::ItemFlags MatrixModel::flags(const QModelIndex &index) const
{
    if (index.isValid())
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
    else
        return Qt::ItemIsEnabled;
}

QVariant MatrixModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    int row = index.row();
    int col = index.column();

    switch (role) {
    case Qt::ToolTipRole:
    case Qt::EditRole:
    case Qt::DisplayRole:
        return QVariant(d_matrix->text(row, col));
    case Qt::BackgroundRole:
        return QVariant(QBrush(
                QColor(0xff, 0xff, 0x77))); // yellow color to distinguish a matrix from a table
    }

    return QVariant();
}

QVariant MatrixModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    QString result;
    switch (orientation) {
    case Qt::Horizontal:
        switch (role) {
        case Qt::DisplayRole:
        case Qt::ToolTipRole:
            result += QString::number(section + 1) + QString(" (");
            double diff = d_matrix->xEnd() - d_matrix->xStart();
            double step = 0.0;
            if (d_matrix->columnCount() > 1)
                step = diff / double(d_matrix->columnCount() - 1);
            result += QLocale().toString(d_matrix->xStart() + double(section) * step,
                                         d_matrix->numericFormat(), d_matrix->displayedDigits());

            result += QString(")");
            return QVariant(result);
        }
        break;
    case Qt::Vertical:
        switch (role) {
        case Qt::DisplayRole:
        case Qt::ToolTipRole:
            result += QString::number(section + 1) + QString(" (");
            double diff = d_matrix->yEnd() - d_matrix->yStart();
            double step = 0.0;
            if (d_matrix->rowCount() > 1)
                step = diff / double(d_matrix->rowCount() - 1);
            // TODO: implement decent double == 0 check
            if (diff < 1e-10)
                result += QLocale().toString(d_matrix->yStart(), d_matrix->numericFormat(),
                                             d_matrix->displayedDigits());
            else
                result +=
                        QLocale().toString(d_matrix->yStart() + double(section) * step,
                                           d_matrix->numericFormat(), d_matrix->displayedDigits());

            result += QString(")");
            return QVariant(result);
        }
    }
    return QVariant();
}

int MatrixModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return d_matrix->rowCount();
}

int MatrixModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return d_matrix->columnCount();
}

bool MatrixModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid())
        return false;

    int row = index.row();
    int column = index.column();

    if (role == Qt::EditRole) {
        static_cast<::Matrix *>(d_matrix->view())->setText(row, column, value.toString());
        return true;
    }
    return false;
}

QModelIndex MatrixModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return createIndex(row, column);
}

QModelIndex MatrixModel::parent(const QModelIndex &child) const
{
    Q_UNUSED(child)
    return QModelIndex();
}

void MatrixModel::handleColumnsAboutToBeInserted(int before, int count)
{
    beginInsertColumns(QModelIndex(), before, before + count - 1);
}

void MatrixModel::handleColumnsInserted(int first, int count)
{
    Q_UNUSED(first)
    Q_UNUSED(count)
    endInsertColumns();
}

void MatrixModel::handleColumnsAboutToBeRemoved(int first, int count)
{
    beginRemoveColumns(QModelIndex(), first, first + count - 1);
}

void MatrixModel::handleColumnsRemoved(int first, int count)
{
    Q_UNUSED(first)
    Q_UNUSED(count)
    endRemoveColumns();
}

void MatrixModel::handleRowsAboutToBeInserted(int before, int count)
{
    beginInsertRows(QModelIndex(), before, before + count - 1);
}

void MatrixModel::handleRowsInserted(int first, int count)
{
    Q_UNUSED(first)
    Q_UNUSED(count)
    endInsertRows();
}

void MatrixModel::handleRowsAboutToBeRemoved(int first, int count)
{
    beginRemoveRows(QModelIndex(), first, first + count - 1);
}

void MatrixModel::handleRowsRemoved(int first, int count)
{
    Q_UNUSED(first)
    Q_UNUSED(count)
    endRemoveRows();
}

void MatrixModel::handleDataChanged(int top, int left, int bottom, int right)
{
    emit dataChanged(index(top, left), index(bottom, right));
}

void MatrixModel::handleCoordinatesChanged()
{
    emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
    emit headerDataChanged(Qt::Vertical, 0, rowCount() - 1);
}

void MatrixModel::handleFormatChanged()
{
    handleCoordinatesChanged();
    handleDataChanged(0, 0, rowCount() - 1, columnCount() - 1);
}
