//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/DataLoaders/AbstractDataLoaderResultModel.cpp
//! @brief     Implements class AbstractDataLoaderResultModel
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/coregui/DataLoaders/AbstractDataLoaderResultModel.h"
#include <QColor>
#include <QIcon>
#include <QVariant>

namespace {
struct Palette {
    static QColor backgroundColorFileContent;
    static QColor backgroundColorRawContent;
    static QColor backgroundColorProcessedContent;
    static QColor backgroundColorErrors;
    static QColor skippedLineTextColor;
};

QColor Palette::backgroundColorFileContent(239, 237, 248);
QColor Palette::backgroundColorRawContent(247, 240, 210);
QColor Palette::backgroundColorProcessedContent(191, 232, 242);
QColor Palette::backgroundColorErrors(247, 140, 146);
QColor Palette::skippedLineTextColor(Qt::lightGray);

} // namespace

AbstractDataLoaderResultModel::AbstractDataLoaderResultModel()
{
    m_warningIcon = QIcon(":/images/warning_16x16.png");
}

int AbstractDataLoaderResultModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
    if (parent.isValid())
        return 0;

    const auto colTypes = {ColumnType::line, ColumnType::fileContent, ColumnType::raw,
                           ColumnType::processed, ColumnType::error};

    int cols = 0;
    for (ColumnType type : colTypes)
        cols += columnCount(type);

    return cols;
}

int AbstractDataLoaderResultModel::rowCount(const QModelIndex& parent) const
{
    return parent.isValid() ? 0 : rowCount();
}

QVariant AbstractDataLoaderResultModel::data(const QModelIndex& index,
                                             int role /*= Qt::DisplayRole*/) const
{
    if (!index.isValid())
        return QVariant();

    const auto colType = columnType(index);

    if (role == Qt::BackgroundRole) {
        switch (colType) {
        case ColumnType::line:
            return Palette::backgroundColorFileContent;
        case ColumnType::fileContent:
            return Palette::backgroundColorFileContent;
        case ColumnType::raw:
            return rowIsSkipped(index) ? QVariant() : Palette::backgroundColorRawContent;
        case ColumnType::processed:
            return rowIsSkipped(index) || rowHasError(index)
                       ? QVariant()
                       : Palette::backgroundColorProcessedContent;
        case ColumnType::error:
            return rowIsSkipped(index) || !rowHasError(index) ? QVariant()
                                                              : Palette::backgroundColorErrors;
        default:
            return QVariant();
        }
    }

    if (role == Qt::ForegroundRole) {
        if (colType == ColumnType::fileContent && rowIsSkipped(index))
            return Palette::skippedLineTextColor;
        return QVariant();
    }

    if (role == Qt::DisplayRole) {
        switch (colType) {
        case ColumnType::line:
            return QString::number(index.row() + 1);

        case ColumnType::fileContent: {
            QString lineContent =
                cellText(colType, index.row(), index.column() - firstSectionOfColumnType(colType));
            lineContent.replace("\t", " --> ");
            return lineContent;
        }

        case ColumnType::raw:
        case ColumnType::error: {
            if (!rowIsSkipped(index))
                return cellText(colType, index.row(),
                                index.column() - firstSectionOfColumnType(colType));
        }

        case ColumnType::processed: {
            if (!rowIsSkipped(index) && !rowHasError(index))
                return cellText(colType, index.row(),
                                index.column() - firstSectionOfColumnType(colType));
        }
        }

        return QVariant();
    }

    if (role == Qt::DecorationRole && (colType == ColumnType::line || colType == ColumnType::error)
        && rowHasError(index))
        return m_warningIcon;

    if (role == Qt::TextAlignmentRole && colType == ColumnType::line)
        return QVariant(Qt::AlignRight | Qt::AlignVCenter);

    if (role == Qt::ToolTipRole && colType == ColumnType::line && rowHasError(index))
        return cellText(ColumnType::error, index.row(), 0);

    return QVariant();
}

QVariant AbstractDataLoaderResultModel::headerData(int section, Qt::Orientation orientation,
                                                   int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        switch (columnType(section)) {
        case ColumnType::line:
            return "Line";
        case ColumnType::fileContent:
            return "File content (text)";
        case ColumnType::raw:
            return QString("Column %1 raw")
                .arg(section - firstSectionOfColumnType(ColumnType::raw) + 1);
        case ColumnType::processed:
            return headerTextOfCalculatedColumn(section
                                                - firstSectionOfColumnType(ColumnType::processed));
        case ColumnType::error:
            return "Parser warnings";
        default:
            return QVariant();
        }
    }

    return QAbstractTableModel::headerData(section, orientation, role);
}

QVector<int> AbstractDataLoaderResultModel::sectionsOfColumnType(ColumnType type) const
{
    QVector<int> sections;
    for (int section = firstSectionOfColumnType(type); section <= lastSectionOfColumnType(type);
         section++)
        if (section >= 0)
            sections << section;

    return sections;
}

AbstractDataLoaderResultModel::ColumnType
AbstractDataLoaderResultModel::columnType(int section) const
{
    const auto colTypes = {ColumnType::line, ColumnType::fileContent, ColumnType::raw,
                           ColumnType::processed, ColumnType::error};

    for (ColumnType type : colTypes) {
        const int firstSection = firstSectionOfColumnType(type);
        if (firstSection < 0)
            continue;

        if (section >= firstSection && section <= lastSectionOfColumnType(type))
            return type;
    }

    return ColumnType::none;
}

AbstractDataLoaderResultModel::ColumnType
AbstractDataLoaderResultModel::columnType(const QModelIndex& index) const
{
    return columnType(index.column());
}

int AbstractDataLoaderResultModel::firstSectionOfColumnType(ColumnType type) const
{
    const int lineColumnCount = columnCount(ColumnType::line);
    const int fileContentColumnCount = columnCount(ColumnType::fileContent);

    if (type == ColumnType::line)
        return lineColumnCount > 0 ? 0 : -1;

    if (type == ColumnType::fileContent)
        return columnCount(ColumnType::fileContent) > 0 ? lineColumnCount : -1;

    if (type == ColumnType::raw) {
        const bool hasRawContent = columnCount(ColumnType::raw) > 0;
        return hasRawContent ? lineColumnCount + fileContentColumnCount : -1;
    }

    if (type == ColumnType::processed) {
        const bool hasProcessedContent = columnCount(ColumnType::processed) > 0;
        return hasProcessedContent
                   ? (lineColumnCount + fileContentColumnCount + columnCount(ColumnType::raw))
                   : -1;
    }

    if (type == ColumnType::error) {
        const bool hasParsingErrors = columnCount(ColumnType::error) > 0;
        return hasParsingErrors
                   ? (lineColumnCount + fileContentColumnCount + columnCount(ColumnType::raw)
                      + columnCount(ColumnType::processed))
                   : -1;
    }

    return -1;
}

int AbstractDataLoaderResultModel::lastSectionOfColumnType(ColumnType type) const
{
    const int firstSection = firstSectionOfColumnType(type);
    if (firstSection == -1)
        return -1;

    return firstSection + columnCount(type) - 1;
}

bool AbstractDataLoaderResultModel::rowIsSkipped(const QModelIndex& index) const
{
    return rowIsSkipped(index.row());
}

bool AbstractDataLoaderResultModel::rowHasError(const QModelIndex& index) const
{
    return rowHasError(index.row());
}
