/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtWidgets>

#include "treeitem.h"
#include "treemodel.h"

// Formatting helpers
namespace {
    QString formatNumber(float value, int decimals = 2) {
        static const QLocale locale("es_ES");
        return locale.toString(value, 'f', decimals);
    }

    QString formatCurrency(float value) {
        return formatNumber(value) + " €";
    }

    QString formatPercentage(float value) {
        return formatNumber(value) + " %";
    }
}

//! [0]
TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
    : QAbstractItemModel(parent)
{
    QVector<QVariant> rootData;
    foreach (QString header, headers)
        rootData << header;

    rootItem = new TreeItem(rootData);
    //setupModelData(data.split(QString("\n")), rootItem);

    // Insertar 20 filas vacias por defecto:
    for (int i = 0; i < 20; i++)
    {
        rootItem->insertChildren(rootItem->childCount(), 1, rootItem->columnCount());
        for (int column = 0; column < rootItem->columnCount(); ++column)
            rootItem->child(rootItem->childCount() - 1)->setData(column, QVariant());
    }
}
//! [0]

//! [1]
TreeModel::~TreeModel()
{
    delete rootItem;
}
//! [1]

//! [2]
int TreeModel::columnCount(const QModelIndex & /* parent */) const
{
    return rootItem->columnCount();
}
//! [2]

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

    TreeItem *item = getItem(index);
    const int col = index.column();

    switch (role)
    {
        case Qt::DisplayRole:
            return displayData(item, col);

        case Qt::EditRole:
            return editData(item, col);

        case Qt::ForegroundRole:
            return foregroundForItem(item, col);

        case Qt::TextAlignmentRole:
            return alignmentForColumn(col);

        case Qt::FontRole:
            /*
            if(item->childCount() > 0)
            {
                QFont font = QFont("Helvetica", 9, QFont::Bold);
                return QVariant::fromValue(font);
            }
            else
            */
            return QVariant();

        case Qt::BackgroundRole:
        {
            //if (item->childCount() > 0)
            //    return QVariant::fromValue(QColor(Qt::lightGray));
            return QVariant();
        }

        case Qt::CheckStateRole:
            /*
            if (index.column() == 14)
                return (QSqlQueryModel::data(index).toInt() != 0) ? Qt::Checked : Qt::Unchecked;
            else*/
                return QVariant();

        case Qt::SizeHintRole:
        {
            int he = 24;
            return QSize(80, he);
        }

        case Qt::DecorationRole:
            return decorationForItem(item, col);

        default:
            break;
    }

    return item->data(role);
}

QVariant TreeModel::displayData(TreeItem *item, int column) const
{
    const QString type = item->data(13).toString();
    const QVariant rawData = item->data(column);
    float value = rawData.isValid() ? rawData.toFloat() : 0.0f;

    switch (column)
    {
        case 4:
        case 5:
            return formatNumber(value);

        case 7:
        case 8:
        case 11:
        case 12:
            return formatCurrency(value);

        case 9:
        case 10:
            return !type.isEmpty() ? formatPercentage(value) : rawData;

        default:
            return rawData;
    }
}

QVariant TreeModel::editData(TreeItem *item, int column) const
{
    return item->data(column);
}

QVariant TreeModel::foregroundForItem(TreeItem *item, int column) const
{
    if (!item->parent() || !item->parent()->parent())
        return (column == 1) ? QColor(Qt::darkBlue) : QVariant();

    return (column == 1) ? QColor(Qt::blue) : QColor(Qt::gray);
}

QVariant TreeModel::alignmentForColumn(int column) const
{
    switch (column) {
    case 4:
    case 5:
    case 7:
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
        return int(Qt::AlignRight | Qt::AlignTop);
    default:
        return int(Qt::AlignLeft | Qt::AlignTop);
    }
}

QVariant TreeModel::decorationForItem(TreeItem *item, int column) const
{
    /*
    0 Sin clasificar
    1 Mano de obra
    2 Maquinaria
    3 Materiales
    4 Componentes adicionales de residuo
    5 Clasificación de residuo
    ....
    */

    if (column != 0)
        return QVariant();

    static const QMap<QString, QString> iconMap = {
        {"CO", ":/resources/icons/box.svg"},
        {"MO", ":/resources/icons/helmet.svg"},
        {"MQ", ":/resources/icons/gear.svg"},
        {"MT", ":/resources/icons/blocks.svg"},
        {"%",  ":/resources/icons/percentage.svg"}
    };

    const QString type = item->data(13).toString();
    return iconMap.contains(type) ? QIcon(iconMap[type]) : QVariant();
}

//! [3]
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;

    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    TreeItem *item = getItem(index);
    const int col = index.column();
    const bool isLeaf = (item->childCount() == 0);

    // Editable columns
    static const QVector<int> leafEditable = {0, 1, 2, 3, 4, 6, 7, 9, 10};
    static const QVector<int> branchEditable = {0, 1, 2, 3, 4, 6, 9, 10};

    if ((isLeaf && leafEditable.contains(col)) ||
        (!isLeaf && branchEditable.contains(col))) {
        flags |= Qt::ItemIsEditable;
    }

    return flags;

    /*
    if (!index.isValid())
        return Qt::NoItemFlags;

    Qt::ItemFlags flags = QAbstractItemModel::flags(index);
    TreeItem *item = getItem(index);

    if(item->childCount() == 0)
    {
        switch (index.column())
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 6:
            case 7:
            case 9:
            case 10:
            {
                flags |= Qt::ItemIsEditable;
            }
        }
    }
    else
    {
        switch (index.column())
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 6:
            case 9:
            case 10:
            {
                flags |= Qt::ItemIsEditable;
            }
        }
    }

    return flags;*/
}
//! [3]

//! [4]
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
    if (index.isValid())
    {
        TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
        if (item)
            return item;
    }
    return rootItem;
}
//! [4]

QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
                               int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        return rootItem->data(section);

    return QVariant();
}

//! [5]
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() != 0)
        return QModelIndex();
//! [5]

//! [6]
    TreeItem *parentItem = getItem(parent);
    TreeItem *childItem = parentItem->child(row);

    return childItem ? createIndex(row, column, childItem) : QModelIndex();
}
//! [6]

bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent)
{
    beginInsertColumns(parent, position, position + columns - 1);
    const bool success = rootItem->insertColumns(position, columns);
    endInsertColumns();
    return success;
}

bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    TreeItem *parentItem = getItem(parent);
    beginInsertRows(parent, position, position + rows - 1);
    const bool success = parentItem->insertChildren(position, rows, rootItem->columnCount());
    endInsertRows();
    return success;
}

//! [7]
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = getItem(index);
    TreeItem *parentItem = childItem->parent();

    return (parentItem == rootItem) ? QModelIndex() :
               createIndex(parentItem->childNumber(), 0, parentItem);
}
//! [7]

bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent)
{
    beginRemoveColumns(parent, position, position + columns - 1);
    const bool success = rootItem->removeColumns(position, columns);
    endRemoveColumns();

    if (rootItem->columnCount() == 0)
        removeRows(0, rowCount());

    return success;
}

bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    TreeItem *parentItem = getItem(parent);
    beginRemoveRows(parent, position, position + rows - 1);
    const bool success = parentItem->removeChildren(position, rows);
    endRemoveRows();
    return success;
}

//! [8]
int TreeModel::rowCount(const QModelIndex &parent) const
{
    return getItem(parent)->childCount();
}
//! [8]

bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (!index.isValid() || role != Qt::EditRole)
        return false;

    TreeItem *item = getItem(index);
    const int col = index.column();

    if (!item->setData(col, value))
        return false;

    emit dataChanged(index, index, {role});

    switch (col) {
        case 0:
            if (item->childCount() > 0)
                codify(item);
            break;

        case 4:
            updateQuantities(item, index.parent());
            break;

        case 7:
        case 9:
        case 10:
            updateCalculations(item, col);
            break;
    }

    return true;
}

void TreeModel::codify(TreeItem *item)
{
    for (int i = 0; i < item->childCount(); ++i) {
        TreeItem *child = item->child(i);
        child->setData(0, QString("%1.%2").arg(item->data(0).toString()).arg(i + 1));
        if (child->childCount() > 0)
            codify(child);
    }
}

void TreeModel::updateQuantities(TreeItem *item, const QModelIndex &parent)
{
    if (parent.isValid()) {
        TreeItem *parentItem = getItem(parent);
        item->setData(5, item->data(4).toFloat() * parentItem->data(5).toFloat());
    } else {
        item->setData(5, item->data(4));
    }
    calculateRow(item);
}

void TreeModel::updateCalculations(TreeItem *item, int column)
{
    if (item->childCount() > 0)
        setTreeValues(item, column);
    calculateRow(item);
}

void TreeModel::setTreeValues(TreeItem *item, int column)
{
    for(int i = 0; i < item->childCount(); ++i)
    {
        TreeItem * chil = item->child(i);
        chil->setData(column, item->data(column));
        if(chil->childCount() > 0)
            setTreeValues(chil, column);
    }
}

void TreeModel::calculateParent(TreeItem *item)
{
    if (!item || !item->parent()) return;

    float pcu = 0, pct = 0, pvt = 0;
    for(int i = 0; i<item->childCount(); ++i)
    {
        TreeItem * child = item->child(i);
        pcu += child->data(4).toFloat() * child->data(7).toFloat();
        pct += child->data(8).toFloat();
        pvt += child->data(11).toFloat();
    }
    item->setData(7, pcu);
    item->setData(8, pct);
    item->setData(11, pvt);
    item->setData(12, pvt - pct);

    if(item->parent() && item->parent()->parent())
        calculateParent(item->parent());
}

void TreeModel::calculateRow(TreeItem *item)
{
    if(item->childCount() > 0)
    {
        for(int i = 0; i<item->childCount(); ++i)
        {
            TreeItem * child = item->child(i);
            child->setData(5, child->data(4).toFloat() * item->data(5).toFloat());
            calculateRow(child);
        }
    }
    else
    {
        float cu = item->data(4).toFloat();
        float ct = item->data(5).toFloat();
        float pcu = item->data(7).toFloat();
        float pct = ct * pcu;
        float pro = item->data(9).toFloat();
        float dis = item->data(10).toFloat();
        float pvt = pct * (1 + pro / 100) * (1 - dis / 100);

        item->setData(8, pct);
        item->setData(11, pvt);
        item->setData(12, pvt - pct);
    }

    if(item->parent() && item->parent()->parent())
        calculateParent(item->parent());

}

bool TreeModel::setHeaderData(int section, Qt::Orientation orientation,
                              const QVariant &value, int role)
{
    if (role != Qt::EditRole || orientation != Qt::Horizontal)
        return false;

    bool result = rootItem->setData(section, value);

    if (result)
        emit headerDataChanged(orientation, section, section);

    return result;
}

void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
    QList<TreeItem*> parents;
    QList<int> indentations;
    parents << parent;
    indentations << 0;

    int number = 0;

    while (number < lines.count())
    {
        int position = 0;
        while (position < lines[number].length())
        {
            if (lines[number].at(position) != ' ')
                break;
            ++position;
        }

        QString lineData = lines[number].mid(position).trimmed();

        if (!lineData.isEmpty())
        {
            // Read the column data from the rest of the line.
            QStringList columnStrings = lineData.split("\t", Qt::SkipEmptyParts);
            QVector<QVariant> columnData;
            for (int column = 0; column < columnStrings.count(); ++column)
                columnData << columnStrings[column];

            if (position > indentations.last())
            {
                // The last child of the current parent is now the new parent
                // unless the current parent has no children.

                if (parents.last()->childCount() > 0)
                {
                    parents << parents.last()->child(parents.last()->childCount()-1);
                    indentations << position;
                }
            }
            else
            {
                while (position < indentations.last() && parents.count() > 0)
                {
                    parents.pop_back();
                    indentations.pop_back();
                }
            }

            // Append a new item to the current parent's list of children.
            TreeItem *parent = parents.last();
            parent->insertChildren(parent->childCount(), 1, rootItem->columnCount());
            for (int column = 0; column < columnData.size(); ++column)
                parent->child(parent->childCount() - 1)->setData(column, columnData[column]);
        }

        ++number;
    }
}

