/*
 * optionstreemodel.h
 * Copyright (C) 2006  Remko Troncon
 *
 * 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#include "optionstreemodel.h"

#include <QStringList>

#include "optionstree.h"


// Enable this if you have Trolltech Labs' ModelTest and are not going
// to distribute the source or binary. You need to include modeltest.pri
// somewhere too.
//#define HAVE_MODELTEST

#ifdef HAVE_MODELTEST
#include <modeltest.h>
#endif

OptionsTreeModel::OptionsTreeModel(OptionsTree* tree, QObject* parent)
        : QAbstractItemModel(parent),
        tree_(tree),
        flat_(false),
        nextIdx(0)
{
    connect(tree_, SIGNAL(optionChanged(const QString&)), SLOT(optionChanged(const QString&)));
    connect(tree_, SIGNAL(optionAboutToBeInserted(const QString&)), SLOT(optionAboutToBeInserted(const QString&)));
    connect(tree_, SIGNAL(optionInserted(const QString&)), SLOT(optionInserted(const QString&)));
    connect(tree_, SIGNAL(optionAboutToBeRemoved(const QString&)), SLOT(optionAboutToBeRemoved(const QString&)));
    connect(tree_, SIGNAL(optionRemoved(const QString&)), SLOT(optionRemoved(const QString&)));

#ifdef HAVE_MODELTEST
    new ModelTest(this, this);
#endif
}

void OptionsTreeModel::setFlat(bool b)
{
    if (flat_ != b) {
        beginResetModel();
        flat_ = b;
        endResetModel();
    }
}


/**
 * Get the parent option of @a option
 * @param option the option name to be splitted
 * @return the part of option until the last dot (or empty)
 */
QString OptionsTreeModel::getParentName(const QString &option) const
{
    QString parentname;
    int dot_index = option.lastIndexOf('.');
    if (dot_index != -1) {
        parentname = option.left(dot_index);
    }
    return parentname;
}



/**
 * Get index of given @a option
 * @param option the option to retrieve the index for
 * @param sec Section the new index should point to
 * @return a QModelIndex to @a option
 */
QModelIndex OptionsTreeModel::index(const QString &option, Section sec) const
{
    if (option.isEmpty()) {
        return QModelIndex();
    }

    if (flat_) {
        QStringList options = tree_->getChildOptionNames("",false,false);
        options.sort();
        int row = options.indexOf(option);
        return createIndex(row, sec, nameToIndex(options.at(row)));
    } else {
        QString parentname(getParentName(option));

        QStringList children = tree_->getChildOptionNames(parentname,true,true);
        children.sort();
        int row = children.indexOf(option);

        return createIndex(row, sec, nameToIndex(option));
    }
}


Qt::ItemFlags OptionsTreeModel::flags(const QModelIndex& index) const
{
    if (!index.isValid()) {
        // Root item
        return 0;
    }
    Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    if ((index.column() == Value) && !internalNode(indexToOptionName(index)))
        f |= Qt::ItemIsEditable;
    return f;
}

int OptionsTreeModel::rowCount(const QModelIndex& parent) const
{
    if ((Section)parent.column() == Name || !parent.isValid()) {
        if (flat_) {
            return (parent.isValid() ? 0 : tree_->getChildOptionNames("",false,false).count());
        } else {
            QString option;
            if (parent.isValid())
                option = indexToOptionName(parent);
            return tree_->getChildOptionNames(option,true,true).count();
        }
    }
    return 0;
}

int OptionsTreeModel::columnCount(const QModelIndex&) const
{
    return 4;
}

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

    QString option = indexToOptionName(index);
    Section section = (Section) index.column();
    if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) {
        if (section == Name) {
            if (flat_) {
                return option;
            } else {
                int dot_index = option.lastIndexOf('.');
                return option.mid(dot_index + 1);
            }
        }
        else if (section == Comment) {
            return tree_->getComment(option);
        } else if (!tree_->isInternalNode(option)) {
            if (section == Type)
                return tree_->getOption(option).typeName();
            else if (section == Value) {
                if (role == Qt::DisplayRole) {
                    return QVariant(tree_->getOption(option).toString().section('\n', 0, 0)); // Show only first line
                } else {
                    return tree_->getOption(option);//.toString();
                }
            }
        }
    } else if (role == Qt::ToolTipRole) {
        if (!tree_->isInternalNode(option)) {
            return tree_->getComment(option);
        }
    }
    return QVariant();
}

QVariant OptionsTreeModel::headerData(int s, Qt::Orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    Section section = (Section) s;
    switch (section) {
        case Name: return tr("Name");
        case Type: return QString(tr("Type"));
        case Value: return QString(tr("Value"));
        case Comment: return QString(tr("Comment"));
        default:
            return QVariant();
    }
}

QModelIndex OptionsTreeModel::index(int row, int column, const QModelIndex & parent) const
{
    if  (column < 0  || column >= SectionBound || row < 0) {
        return QModelIndex();
    }
    int id = 0;
    QStringList options;
    if (flat_) {
        options = tree_->getChildOptionNames("",false,false);
    } else {
        QString parent_option;
        if (parent.isValid()) {
            parent_option = indexToOptionName(parent);
        }
        options = tree_->getChildOptionNames(parent_option,true,true);
    }
    if (row >= options.size()) {
        return QModelIndex();
    }
    options.sort();
    id = nameToIndex(options.at(row));
    return createIndex(row,column,id);
}

QModelIndex OptionsTreeModel::parent(const QModelIndex& modelindex) const
{
    if (!modelindex.isValid() || flat_)
        return QModelIndex();

    QString option = indexToOptionName(modelindex);

    QString parent_option = getParentName(option);

    return index(parent_option);
}

bool OptionsTreeModel::setData ( const QModelIndex & index, const QVariant & value, int role)
{
    QString option = indexToOptionName(index);
    if ((role != Qt::EditRole) || ((Section) index.column() != Value) || internalNode(option)) {
        return false;
    }
    QVariant current = tree_->getOption(option);
    QVariant newval = value;
    if (!newval.canConvert(current.type())) {
        qWarning("Sorry don't know how to do that!");
        return false;
    }
    newval.convert(current.type());
    tree_->setOption(option, newval);
    return true;
}




void OptionsTreeModel::optionAboutToBeInserted(const QString& option)
{
    QString parentname(getParentName(option));

    // FIXME? handle cases when parent doesn't exist either.

    QModelIndex parent(index(parentname));

    QStringList children = tree_->getChildOptionNames(parentname,true,true);
    children << option;
    children.sort();
    int row = children.indexOf(option);

    emit beginInsertRows(parent, row, row);

}

void OptionsTreeModel::optionInserted(const QString& option)
{
    Q_UNUSED(option)
    endInsertRows ();
}

void OptionsTreeModel::optionAboutToBeRemoved(const QString& option)
{
    QString parentname(getParentName(option));

    QModelIndex parent(index(parentname));

    QStringList children = tree_->getChildOptionNames(parentname,true,true);
    children.sort();
    int row = children.indexOf(option);

    if (row != -1) {
        realRemove.push(true);
        emit beginRemoveRows(parent, row, row);
    } else {
        realRemove.push(false);
    }
}
void OptionsTreeModel::optionRemoved(const QString& option)
{
    Q_UNUSED(option)
    if (realRemove.pop()) endRemoveRows ();
}


void OptionsTreeModel::optionChanged(const QString& option)
{
    // only need to notify about options the view can possibly know anything about.
    if (nameMap.contains(option)) {
        QModelIndex modelindex(index(option, Value));
        emit dataChanged(modelindex, modelindex);
    }
}
bool OptionsTreeModel::internalNode(QString option) const
{
    return tree_->isInternalNode(option);
}

int OptionsTreeModel::nameToIndex(QString name) const
{
    if (!nameMap.contains(name)) {
        int idx = nextIdx++;
        //qDebug() << "adding " << name << " as " << idx;
        nameMap[name] = idx;
        indexMap[idx] = name;
        return idx;
    } else {
        return nameMap[name];
    }
}

QString OptionsTreeModel::indexToOptionName(QModelIndex idx) const
{
    return indexMap[idx.internalId()];
}
